Compare commits
544 Commits
0.1.0-andr
...
master
Author | SHA1 | Date |
---|---|---|
Laura K | b572fdfd47 | |
Laura K | 94b9f930ce | |
莯凛 | 01ec93dd27 | |
biroder | ca5361cc58 | |
poly000 | 08f086bfc4 | |
poly000 | 1f288e2a39 | |
poly000 | 0e0cd66564 | |
József Sallai | 9c95b20f5c | |
Alula | 7630a9b60e | |
Alula | ae909878c4 | |
Alula | 7785513e8b | |
Alula | 7f33f7b6c8 | |
József Sallai | 6c9b4d9a54 | |
Edward Stuckey | af9947b931 | |
Laura K | b275816e76 | |
Laura K | 8e3ccea8a1 | |
biroder | 76ec4771b3 | |
biroder | 3b77cdf0c5 | |
biroder | 397cd8f584 | |
biroder | 21c255efb4 | |
Sobakin | e1fb118910 | |
Laura K | c56bd2e8ae | |
periwinkle | e09fbf5c63 | |
biroder | c4cffd54a8 | |
biroder | 99f13a746e | |
biroder | 690c97e44d | |
biroder | 311ca8b12f | |
biroder | 152e31966a | |
biroder | b4ede5bcad | |
biroder | a5f49c07e4 | |
biroder | fc66b84d8f | |
biroder | 87cc7f12e3 | |
biroder | e9cf548cc2 | |
biroder | 3af1d61e5b | |
Edward Stuckey | 3b14adf949 | |
biroder | 46710dd31a | |
biroder | 10e4d3efff | |
biroder | 2b1411787b | |
biroder | 0e33bcaaf9 | |
biroder | 45443dfa23 | |
biroder | 06ae269b7b | |
periwinkle | 12a305becb | |
periwinkle | bd203cfddb | |
periwinkle | c2ad3dd643 | |
biroder | 3468bcf5fd | |
biroder | 21221d80e7 | |
József Sallai | a45c630116 | |
biroder | 73295a6351 | |
biroder | ab87862646 | |
József Sallai | 5f24ee52b0 | |
biroder | 02e1763e1d | |
biroder | 44c6af2146 | |
periwinkle | d1d008edda | |
periwinkle | 4d7dfd0266 | |
biroder | b70f0007b1 | |
biroder | f7148edd96 | |
biroder | 425a26b3a0 | |
periwinkle | f6caffd624 | |
biroder | b294f65656 | |
karnak | a5ed1a370e | |
karnak | f07228895c | |
periwinkle | b3007c10e3 | |
periwinkle | 7caff84e04 | |
Alula | dcdb4e9d39 | |
biroder | 596c7b8aff | |
biroder | ed2c5f510a | |
biroder | 0ba5aad8af | |
biroder | 50ff888141 | |
biroder | 72e74e2113 | |
biroder | 47a43467d0 | |
biroder | 19e0da519f | |
biroder | 721a0c907a | |
biroder | 2a3341e3c5 | |
biroder | 63f903104a | |
biroder | 277f5d957b | |
biroder | 9409bb35fe | |
biroder | 9e09d56b76 | |
biroder | 40767c021b | |
biroder | 0e164a44df | |
biroder | 38338ae551 | |
biroder | dbb72ff6c1 | |
biroder | 0a45332f3f | |
biroder | aa27680baf | |
biroder | 67fb32499f | |
biroder | 5821f06928 | |
biroder | 5f93658f0f | |
biroder | cd671cce48 | |
biroder | 2a162d948f | |
biroder | 72a2ada0b2 | |
biroder | 0ccbbefbed | |
biroder | bc91d9f366 | |
Alula | beb290dafb | |
biroder | 5f7bae8f4f | |
biroder | 11eafb8d03 | |
biroder | f99b452073 | |
Alula | 51834be404 | |
Alula | b2fb548584 | |
biroder | 1633eef3dd | |
biroder | e57c9cdb27 | |
biroder | 07b1550cf4 | |
biroder | a15fd4d190 | |
biroder | b8af9aed25 | |
biroder | 8992889e94 | |
biroder | f91e793062 | |
biroder | 3fbe94ecd1 | |
biroder | 5bd0dcd564 | |
József Sallai | 41b840d13f | |
József Sallai | 95e09ded99 | |
József Sallai | 6bccf59f5b | |
József Sallai | 890c0596ed | |
József Sallai | b22ca8b35e | |
József Sallai | e77241cd56 | |
József Sallai | 3dbe56690a | |
József Sallai | 1810bf6d5b | |
József Sallai | d6715bccea | |
alula | 30c85c2f8b | |
biroder | 979909faa8 | |
Alula | 90df8faa7a | |
Daedliy | 6d3c127912 | |
Alula | 515a0a7fe7 | |
Alula | 7fe96780de | |
Alula | f1e5d11b2a | |
Alula | 3739e9f170 | |
József Sallai | 3c79582fed | |
József Sallai | a187942cff | |
József Sallai | 4ae085f7e2 | |
József Sallai | 153b1b4962 | |
Alula | 2774eae66f | |
Alula | 4928ce4682 | |
Alula | 64290ae5a3 | |
Alula | 1457aa3caa | |
Alula | ee58c1c8af | |
Alula | a5bb130e73 | |
Alula | bbd20a003b | |
Alula | ef1c2a5930 | |
Alula | 074af609bc | |
Alula | efa8a47b8d | |
Alula | bc3616d073 | |
Devon W | 510442490e | |
József Sallai | 0a7fd4dc47 | |
József Sallai | 4fa7069e82 | |
József Sallai | 212d7b915b | |
József Sallai | cb95db1e89 | |
József Sallai | 334e64f499 | |
József Sallai | 90e58649a7 | |
Alula | e845c87738 | |
alula | 2cb36de715 | |
alula | c2a8bf52e9 | |
József Sallai | e975a75ec4 | |
Alula | 5854735392 | |
József Sallai | 356f4230b5 | |
József Sallai | 4be3dd518b | |
József Sallai | 5ed2d40e23 | |
alula | a246d1a2f9 | |
Alula | e567dd6296 | |
Alula | 066389a29f | |
Alula | 67979a03ea | |
Alula | f91513edd2 | |
Alula | 5d92cafe67 | |
Alula | d87bbf2b46 | |
Alula | 2860938b9a | |
Alula | e74b586dd1 | |
alula | 0fca898c54 | |
Alula | df467e8764 | |
Alula | 17e1156850 | |
Alula | 9fd04ed47a | |
Alula | c9e6dd7181 | |
dawnDus | 0330cf3b2b | |
dawnDus | 2be1c422d6 | |
Awesomegamer6566 | 0f79474aae | |
dawnDus | 713e704b9b | |
dawnDus | d42632f973 | |
dawnDus | 57b2be5211 | |
Alula | 3a756e0ac4 | |
József Sallai | 6607d2fc15 | |
József Sallai | 8ebe210105 | |
József Sallai | f0949f49cf | |
dawnDus | 2b9a0198cb | |
József Sallai | 8684dd8448 | |
Awesomegamer6566 | 72c268647f | |
Daedliy | e9d2099f42 | |
Sallai József | f7d635a3d7 | |
Sallai József | 029d6d52e4 | |
Sallai József | dec913dd65 | |
Sallai József | 028c60157d | |
dawnDus | 3d86995feb | |
Sallai József | 670e6891c1 | |
dawnDus | a25dc297ef | |
Sallai József | 4ed7ba66b8 | |
Sallai József | b1b3b131e2 | |
Sallai József | f1b3c680c9 | |
Sallai József | ca1fa7b7c0 | |
dawnDus | 3cc9d75681 | |
dawnDus | 92bc887663 | |
Sallai József | 59da01b7b9 | |
Sallai József | e07207b40c | |
Daedliy | 1688f4bcbc | |
Sallai József | 8c70e1a13d | |
Sallai József | b1d578c0b4 | |
Sallai József | 290068dd37 | |
Sallai József | 7b359ae4c1 | |
Sallai József | 56631201c8 | |
dawnDus | 81755d4ad9 | |
Sallai József | f1542246c6 | |
Sallai József | b29c375a7a | |
Sallai József | bfd9c8c343 | |
Sallai József | 1883045f75 | |
Sallai József | 4cfbcc50ac | |
Sallai József | f74ec19cb5 | |
Sallai József | c68fedaa50 | |
Sallai József | 8a4201f381 | |
Sallai József | ffaf12cca8 | |
Sallai József | 03e9c9db0c | |
dawnDus | c9ba05c948 | |
Sallai József | 914555eac0 | |
Sallai József | fee63f2600 | |
alula | 2a792db797 | |
Sallai József | a598f31716 | |
Sallai József | ef040a393c | |
Sallai József | 4a6b2c4400 | |
Sallai József | 398f610c09 | |
Sallai József | fb4ac0dae8 | |
Sallai József | 0415f917f8 | |
Sallai József | 6f95e6109c | |
dawnDus | cdd3a37754 | |
József Sallai | f00f35ca2c | |
Sallai József | eee0f9eff9 | |
Sallai József | 84d9dbf877 | |
Sallai József | 9932b1209f | |
dawnDus | 6d8e58090a | |
Sallai József | 93e567981e | |
József Sallai | 7693a4ff20 | |
Daedliy | 86f89e7522 | |
Sallai József | 3297b151ad | |
Sallai József | aaecafcb72 | |
Sallai József | 444539405a | |
Sallai József | 2177382b5a | |
Sallai József | a1d0f2dc63 | |
Sallai József | 75b077c772 | |
Sallai József | d8636bc693 | |
József Sallai | 9d7c63571d | |
Sallai József | 3faf99b535 | |
Sallai József | 1440a91fba | |
dawnDus | 6d08eb716e | |
dawnDus | 69fdc7d3d2 | |
IruzzArcana | 18a7670248 | |
József Sallai | d7face2544 | |
dawnDus | 987c857b1c | |
dawnDus | 3f8c66db0f | |
dawnDus | dc2476c9dd | |
dawnDus | 8a2e9fa569 | |
dawndus | 588b0d53dd | |
dawnDus | 7c07986b5d | |
dawnDus | 02a9cac305 | |
dawnDus | af39130fed | |
dawnDus | 2d2e712eab | |
dawnDus | acad65d233 | |
Alula | 24762a1c45 | |
dawnDus | 75f5e9f364 | |
dawnDus | 7e793e09a8 | |
dawnDus | 7dcf30f854 | |
dawnDus | 5fdd9676cb | |
dawnDus | f672ff6f24 | |
dawnDus | e4ec69b6dc | |
Daedliy | 934df79f85 | |
dawnDus | c97ed04fea | |
dawnDus | daea35381b | |
dawnDus | 28a3f160c3 | |
dawnDus | b94b20bf76 | |
dawnDus | b626472f10 | |
dawnDus | 0c97d554ae | |
dawnDus | 9b572190de | |
Daedliy | 90900f01e1 | |
dawnDus | adfc768a8f | |
dawnDus | 11f335acc4 | |
Alula | ca1d7a8642 | |
dawnDus | 339f822a80 | |
dawnDus | 68318e3a69 | |
dawnDus | 2d9840c901 | |
dawnDus | 858abae42d | |
Daedliy | 730d1cb5d8 | |
dawndus | 7c8f2ac60a | |
dawnDus | 2bce0136ff | |
dawnDus | 8b31d0a9ab | |
dawnDus | 299ef053f4 | |
dawnDus | 77fdd19ec2 | |
dawnDus | 55e80b4c69 | |
dawnDus | b0958749f6 | |
Alula | a0df539b7b | |
dawnDus | fb17edea7a | |
dawnDus | 11454183a1 | |
dawnDus | 0dbee1e854 | |
dawnDus | 890c297437 | |
Alula | e27d555bc6 | |
alula | 51d384aee8 | |
Daedliy | 06709ae031 | |
dawnDus | ecdc84fba7 | |
dawnDus | 8fbab06192 | |
megumin | bd00c8a372 | |
Alula | d69e158b41 | |
Alula | 91f6f19914 | |
Alula | fb253b7573 | |
alula | 04fecdeee0 | |
dawnDus | 2e1188d854 | |
Alula | cd8a23391a | |
dawnDus | 703413dcb6 | |
Alula | f60440f877 | |
Alula | 42edfd7fd6 | |
alula | 6408ffcf21 | |
alula | 01d88c2d33 | |
dawnDus | 675e321d8f | |
dawnDus | 99d7ef67e0 | |
dawnDus | 9dba30d360 | |
dawnDus | 1e7da276ab | |
József Sallai | 7e3fef8d41 | |
József Sallai | 1795d71b37 | |
dawnDus | 500f53bebb | |
dawnDus | ad2beacf40 | |
dawnDus | c0efcbef71 | |
József Sallai | 2dadbdb905 | |
dawnDus | 8fd0a814e9 | |
dawnDus | 38ea01d605 | |
dawnDus | 5795015059 | |
Daedliy | df2663d9fb | |
dawnDus | 2415d74a46 | |
dawnDus | f50760d9c6 | |
dawnDus | ef99809c95 | |
dawnDus | b2ae281483 | |
alula | 42f35b673d | |
dawnDus | b079488c27 | |
dawnDus | 06b4aeead9 | |
dawnDus | 4b0b667ed5 | |
József Sallai | d2a671e04c | |
dawnDus | 62efbf0cc3 | |
József Sallai | 465825797e | |
dawnDus | e1f1dd4554 | |
dawnDus | cb7b2bd402 | |
dawnDus | 067bcc5c8b | |
József Sallai | 05b9d9ebe0 | |
József Sallai | bc56271174 | |
József Sallai | d45e611466 | |
dawnDus | 0db6b60251 | |
dawnDus | 657be6159e | |
dawnDus | 4b5d70ea3f | |
dawnDus | bba0313824 | |
dawnDus | 15010e54c2 | |
dawnDus | 0387a450ce | |
József Sallai | 71b39cdadc | |
dawnDus | 4055fef911 | |
dawnDus | 7a580fdf44 | |
Alula | 4cdadfc505 | |
Alula | b6712409ab | |
Alula | 7f35dbf19e | |
Alula | b673d5a33d | |
Sallai József | 657b73aefc | |
Sallai József | 0c33795356 | |
Sallai József | 6b7b6b7032 | |
Alula | a6272476ba | |
Alula | 3867dd58c4 | |
Alula | 390cc45153 | |
Alula | c5e73fac91 | |
Alula | 765d520d70 | |
Alula | fc2f26db91 | |
dawnDus | efd1729ce5 | |
dawnDus | 74a5cddeaf | |
Alula | 464ea6f194 | |
Alula | e216110864 | |
dawnDus | d61602b7bb | |
dawnDus | 287e06e24b | |
dawnDus | b29fe87e76 | |
dawnDus | ccd4030dc1 | |
dawnDus | 9b3e2837b7 | |
dawnDus | 7db42e86e6 | |
dawnDus | f26f019584 | |
dawnDus | befac5db85 | |
Alula | 49d14b58a3 | |
Alula | 39171cc9a9 | |
Alula | 19dad43d7a | |
Alula | 1c2eaae4d1 | |
dawnDus | a2ebaaaab6 | |
dawnDus | 5909fedf33 | |
dawnDus | 5ee580cb35 | |
dawnDus | 1070e67af4 | |
József Sallai | 8a94c841c6 | |
dawnDus | 5cf63660ef | |
dawnDus | 41bf965937 | |
dawnDus | 5ed2883954 | |
dawnDus | d49c261a17 | |
dawnDus | 0d20b1a209 | |
Alula | 44f478be75 | |
dawnDus | b771005816 | |
dawnDus | c43d822c44 | |
dawnDus | 6a8d0bfd22 | |
dawnDus | e6632a845d | |
dawnDus | 38efa5ded9 | |
dawnDus | d7a25f2681 | |
dawnDus | dfcf2e2f3f | |
dawnDus | 1fe00d25c6 | |
Alula | 37cb574907 | |
Alula | 3cd95b4427 | |
Alula | 680294def8 | |
Alula | 99c4798bed | |
Alula | e109db81e6 | |
Alula | c82c65c39f | |
dawnDus | c4f1c60e35 | |
dawnDus | b457f5dd6f | |
dawnDus | 8cd22b932b | |
dawnDus | 2e3245c654 | |
dawnDus | fe3e60ecbf | |
Alula | 8e2088adb4 | |
Alula | c127ee4bd4 | |
Alula | 3374f13c2b | |
Alula | e09ea37bda | |
Alula | 338dbe4bc8 | |
Alula | 7bc8e18310 | |
dawnDus | c722582ff2 | |
dawnDus | 0369b37d10 | |
dawnDus | e53d4c7f43 | |
dawnDus | 79d28822e8 | |
dawndus | 693155ca6a | |
dawnDus | 2223358991 | |
dawnDus | a359a756d0 | |
dawnDus | 32526f92f8 | |
dawndus | bd0762f812 | |
dawnDus | 88fdb7b0ce | |
dawnDus | ff79957145 | |
dawnDus | 807cc305b9 | |
dawnDus | d3d77b58e3 | |
dawnDus | ff5cf7359b | |
dawnDus | 9f530ce6a5 | |
dawnDus | 5725948f85 | |
dawnDus | db50f67876 | |
dawnDus | a02284c439 | |
dawnDus | 6e7191e5d3 | |
dawnDus | 654cbfb814 | |
dawnDus | b99cb8a34d | |
dawnDus | 7448ce0e59 | |
dawnDus | fba36467ea | |
dawnDus | b7f226b322 | |
dawnDus | 698d694c43 | |
dawnDus | 3b307c7c9d | |
dawnDus | 95fd91b8a0 | |
dawnDus | 6302258817 | |
dawnDus | d32cd87532 | |
dawnDus | 7b5d4c367c | |
dawnDus | e1b33aa0e9 | |
Alula | 12d7758ea7 | |
Alula | a098095c13 | |
Alula | 59b2e9954a | |
dawnDus | bb07919505 | |
Alula | 4c20234d5e | |
Alula | 0e06113738 | |
alula | 3054d07213 | |
dawnDus | 1c7e4c9f65 | |
Alula | b880fee8e7 | |
Alula | dba6789b0a | |
Alula | dcd33d943e | |
Alula | 4325dcad86 | |
Alula | d6df4640ab | |
Alula | bc3906e39d | |
dawnDus | 203bacb1a0 | |
dawnDus | eadeedae6b | |
dawnDus | af1f9f5d89 | |
Daedliy | ce6de7c0a0 | |
dawnDus | 6226df68ca | |
dawndus | fb5a72c565 | |
dawnDus | c9f11d6c9f | |
dawnDus | b181f9293f | |
dawnDus | c63c520553 | |
dawnDus | 53fdb890b1 | |
dawnDus | dd8f2dcf67 | |
dawnDus | 924f23154b | |
Vinícius Miguel | f676b73c0e | |
dawnDus | 46045c2a7e | |
Marcin Puc | c8115df285 | |
alula | 529a1c122d | |
Daedliy | df297819fe | |
Daedliy | 3d1ebf76a3 | |
alula | 824ba2c287 | |
alula | 8786787249 | |
alula | ef1d2a320e | |
Alula | 4f00d439f3 | |
Alula | 1b702d1a5a | |
alula | 58406aae71 | |
Alula | 1ae5ecdbbf | |
alula | 87792972d0 | |
Alula | 87bab5fca9 | |
dawnDus | 27003f4e64 | |
dawnDus | 2fed0928d8 | |
dawnDus | 375c72cfbd | |
dawnDus | cd049fd378 | |
dawnDus | fa0695b546 | |
alula | e57bf29703 | |
dawndus | 85b1b71a0f | |
dawnDus | 6cef63bd0e | |
dawnDus | b492be2203 | |
dawnDus | 39267048c7 | |
dawnDus | d5743ac972 | |
dawnDus | 1f8690263b | |
dawnDus | 10b7b5536d | |
Alula | 89771844cc | |
Alula | 0804fe86ce | |
Alula | e27cde67c7 | |
alula | be91851952 | |
dawnDus | c4d80eecb3 | |
Alula | 4c8ec46864 | |
dawndus | 87ddcc1324 | |
dawnDus | e7b666b4cc | |
dawnDus | 6339a612b5 | |
dawnDus | cb52935d9f | |
dawnDus | 25a098145d | |
dawnDus | af6e36ef3b | |
Alula | 7d92b55b58 | |
Alula | 124b2e2c82 | |
alula | d572aebe32 | |
Alula | 0cad0b0762 | |
Alula | 27b439f2cb | |
Alula | 3e6adbe3c1 | |
Alula | 7ac4346012 | |
Alula | 75a1b3f9f4 | |
Alula | 4a91448067 | |
Alula | d484e8a183 | |
Alula | 4d6768c015 | |
Alula | b80f57ae49 | |
Alula | 4f3312d9ca | |
Alula | a10a5f138c | |
Alula | 94514d3b68 | |
dawndus | d15fd43c84 | |
dawndus | 77fc4ca5b9 | |
Alula | 3d912f6fbb | |
Alula | c8dc2d3443 | |
Alula | d09e0f8a91 | |
Alula | cdfa550110 | |
Alula | ef84379b62 | |
Alula | e2afafdfa3 | |
Alula | ebe3c2f2af | |
Alula | b7680019ee | |
Alula | 575dcc7a6d | |
Alula | 3b1a5f149e | |
Alula | ac58602ed0 | |
Alula | 4134d4754e | |
Alula | 5a6f7dec59 | |
alula | 00309a73f0 | |
dawnDus | 9b4fd7d1a0 |
130
.appveyor.yml
|
@ -1,23 +1,44 @@
|
|||
version: "0.1.0.{build}-{branch}"
|
||||
version: "0.101.0-{build}-{branch}"
|
||||
|
||||
skip_commits:
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- app/
|
||||
- drsandroid/
|
||||
- drshorizon/
|
||||
|
||||
environment:
|
||||
global:
|
||||
PROJECT_NAME: doukutsu-rs
|
||||
matrix:
|
||||
- channel: nightly
|
||||
- channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
target_name: win64
|
||||
arch_name: x86_64
|
||||
job_name: windows-x64
|
||||
appveyor_build_worker_image: Visual Studio 2019
|
||||
# - channel: stable
|
||||
# target: i686-pc-windows-msvc
|
||||
# target_name: win32
|
||||
# job_name: windows-x32
|
||||
- channel: nightly
|
||||
- channel: stable
|
||||
target: i686-pc-windows-msvc
|
||||
target_name: win32
|
||||
arch_name: i686
|
||||
job_name: windows-x32
|
||||
appveyor_build_worker_image: Visual Studio 2019
|
||||
- channel: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
target_name: linux
|
||||
job_name: linux-x64
|
||||
appveyor_build_worker_image: Ubuntu
|
||||
- channel: stable
|
||||
target: x86_64-apple-darwin
|
||||
target_name: macos
|
||||
target_name: mac-intel
|
||||
job_name: mac-x64
|
||||
appveyor_build_worker_image: macos
|
||||
appveyor_build_worker_image: macos-monterey
|
||||
- channel: stable
|
||||
target: aarch64-apple-darwin
|
||||
target_name: mac-m1
|
||||
job_name: mac-arm64
|
||||
appveyor_build_worker_image: macos-monterey
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
@ -38,49 +59,100 @@ for:
|
|||
- cargo -vV
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cache\sccache -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cargo -> Cargo.toml'
|
||||
- 'target -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cache'
|
||||
- '%USERPROFILE%\.cargo\bin'
|
||||
- '%USERPROFILE%\.cargo\registry\index'
|
||||
- '%USERPROFILE%\.cargo\registry\cache'
|
||||
- '%USERPROFILE%\.cargo\git\db'
|
||||
- '%USERPROFILE%\.rustup'
|
||||
- 'target'
|
||||
|
||||
build_script:
|
||||
- set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION%
|
||||
#- set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION%
|
||||
- if "%APPVEYOR_REPO_TAG%" == "true" (set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_REPO_TAG_NAME%) else (set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION%)
|
||||
- set CARGO_INCREMENTAL=1
|
||||
- cargo build --release --bin doukutsu-rs
|
||||
- mkdir release
|
||||
- copy target\release\doukutsu-rs.exe release
|
||||
- copy LICENSE release\LICENSE
|
||||
- copy target\release\doukutsu-rs.exe release\doukutsu-rs.%arch_name%.exe
|
||||
- cd release
|
||||
- appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip
|
||||
- 7z x ../game-data.zip
|
||||
- rename game-data-master data
|
||||
- 7z a ../doukutsu-rs_%target_name%.zip *
|
||||
- appveyor PushArtifact ../doukutsu-rs_%target_name%.zip
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- appveyor_build_worker_image: macos
|
||||
|
||||
- appveyor_build_worker_image: macos-monterey
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
if ($env:APPVEYOR_REPO_TAG -eq "true")
|
||||
{
|
||||
Update-AppveyorBuild -Version "$env:APPVEYOR_REPO_TAG_NAME"
|
||||
}
|
||||
|
||||
install:
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -yv --default-toolchain $channel
|
||||
- export PATH=$PATH:$HOME/.cargo/bin
|
||||
- rustup update
|
||||
- rustup default $channel
|
||||
- rustup target add $target
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- cargo install cargo-bundle --force
|
||||
|
||||
cache:
|
||||
- '$HOME/.cache'
|
||||
- '$HOME/.cargo/bin'
|
||||
- '$HOME/.cargo/registry/index'
|
||||
- '$HOME/.cargo/registry/cache'
|
||||
- '$HOME/.cargo/git/db'
|
||||
- '$HOME/.rustup'
|
||||
- 'target'
|
||||
|
||||
build_script:
|
||||
#- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- if [ "$APPVEYOR_REPO_TAG" = "true" ]; then export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_REPO_TAG_NAME; else export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION; fi
|
||||
- CARGO_INCREMENTAL=1 cargo bundle --release --target $target
|
||||
- mkdir release
|
||||
- cp LICENSE ./release/LICENSE
|
||||
- cp -a target/$target/release/bundle/osx/doukutsu-rs.app ./release/doukutsu-rs.app
|
||||
- cd release
|
||||
- codesign -s - -f ./doukutsu-rs.app/Contents/MacOS/doukutsu-rs
|
||||
- 7z a ../doukutsu-rs_$target_name.zip *
|
||||
- appveyor PushArtifact ../doukutsu-rs_$target_name.zip
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- appveyor_build_worker_image: Ubuntu
|
||||
|
||||
install:
|
||||
- sudo apt-get update && sudo apt-get -y install libasound2-dev libudev-dev libgl1-mesa-dev pkg-config
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -yv --default-toolchain $channel --default-host $target
|
||||
- export PATH=$PATH:$HOME/.cargo/bin
|
||||
- rustup update
|
||||
- rustup default $channel
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- cargo install cargo-bundle
|
||||
|
||||
cache:
|
||||
- '$HOME/.cache/sccache -> Cargo.toml'
|
||||
- '$HOME/.cargo -> Cargo.toml'
|
||||
- 'target -> Cargo.toml'
|
||||
- '$HOME/.cache'
|
||||
- '$HOME/.cargo/bin'
|
||||
- '$HOME/.cargo/registry/index'
|
||||
- '$HOME/.cargo/registry/cache'
|
||||
- '$HOME/.cargo/git/db'
|
||||
- '$HOME/.rustup'
|
||||
- 'target'
|
||||
|
||||
build_script:
|
||||
- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip
|
||||
- 7z x ../game-data.zip
|
||||
- mv game-data-master data
|
||||
- cargo bundle --release
|
||||
#- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- if [ "$APPVEYOR_REPO_TAG" = "true" ]; then export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_REPO_TAG_NAME; else export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION; fi
|
||||
- RUSTFLAGS="-C link-arg=-s" CARGO_INCREMENTAL=1 cargo build --release --bin doukutsu-rs
|
||||
- mkdir release
|
||||
- cp -a target/release/bundle/osx/doukutsu-rs.app ./release/doukutsu-rs.app
|
||||
- cp LICENSE ./release/LICENSE
|
||||
- cp -a target/release/doukutsu-rs ./release/doukutsu-rs.x86_64.elf
|
||||
- cd release
|
||||
- 7z a ../doukutsu-rs_$target_name.zip *
|
||||
- appveyor PushArtifact ../doukutsu-rs_$target_name.zip
|
||||
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- cpp-rewrite
|
||||
- horizon-os
|
||||
paths-ignore:
|
||||
- '.gitignore'
|
||||
- '.github/*'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- 'drshorizon/**'
|
||||
- 'res/**'
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
VERSION: "0.101.0"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux x86_64
|
||||
os: ubuntu-latest
|
||||
channel: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
target_name: linux-x64
|
||||
arch_name: x86_64
|
||||
- name: Windows x64
|
||||
os: windows-latest
|
||||
channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
target_name: windows-x64
|
||||
arch_name: x86_64
|
||||
- name: Windows x32
|
||||
os: windows-latest
|
||||
channel: stable
|
||||
target: i686-pc-windows-msvc
|
||||
target_name: windows-x32
|
||||
arch_name: i686
|
||||
- name: macOS x64 (Intel Macs)
|
||||
os: macos-latest
|
||||
channel: stable
|
||||
target: x86_64-apple-darwin
|
||||
target_name: mac-x64
|
||||
- name: macOS ARM64 (M1 Macs)
|
||||
os: macos-latest
|
||||
channel: stable
|
||||
target: aarch64-apple-darwin
|
||||
target_name: mac-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: sudo apt install libasound2-dev libudev-dev libgl1-mesa-dev
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
target
|
||||
key: ${{ matrix.target_name }}-cargo
|
||||
|
||||
- name: Setup rust toolchain
|
||||
run: |
|
||||
rustup default ${{ matrix.channel }}
|
||||
rustup target add ${{ matrix.target }}
|
||||
|
||||
rustc -vV
|
||||
cargo -vV
|
||||
|
||||
if [ "${{ runner.os }}" == "macOS" ]; then
|
||||
cargo install cargo-bundle
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="{{ github.ref_name }}"
|
||||
elif [ "${{ github.ref_name }}" == "master"]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-$((${{ github.run_number }} + 654))"
|
||||
else
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
mkdir release
|
||||
cp LICENSE release
|
||||
|
||||
if [ "${{ runner.os }}" == "macOS" ]; then
|
||||
CARGO_INCREMENTAL=1 cargo bundle --release --target ${{ matrix.target }}
|
||||
cp -a ./target/${{ matrix.target }}/release/bundle/osx/doukutsu-rs.app release/doukutsu-rs.app
|
||||
codesign -s - -f ./release/doukutsu-rs.app/Contents/MacOS/doukutsu-rs
|
||||
elif [ "${{ runner.os }}" == "Windows" ]; then
|
||||
CARGO_INCREMENTAL=1 cargo build --release --bin doukutsu-rs --target ${{ matrix.target }}
|
||||
cp ./target/${{ matrix.target }}/release/doukutsu-rs.exe release/doukutsu-rs.${{ matrix.arch_name }}.exe
|
||||
elif [ "${{ runner.os }}" == "Linux" ]; then
|
||||
RUSTFLAGS="-C link-args=-s" CARGO_INCREMENTAL=1 cargo build --release --bin doukutsu-rs
|
||||
cp -a ./target/release/doukutsu-rs release/doukutsu-rs.${{ matrix.arch_name }}.elf
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: doukutsu-rs_${{ matrix.target_name }}
|
||||
path: ./release/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save cache
|
||||
if: ${{ github.ref_name == 'master' || github.ref_type == 'tag' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
target
|
||||
key: ${{ matrix.target_name }}-cargo
|
||||
|
||||
build_android:
|
||||
name: Android build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
APP_OUTPUTS_DIR: "app/app/build/outputs/apk/release"
|
||||
strategy:
|
||||
fail-fast: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
~/.gradle
|
||||
app/app/.cxx
|
||||
app/app/build
|
||||
drsandroid/target
|
||||
key: android-cargo
|
||||
|
||||
- name: Setup rust toolchain
|
||||
run: |
|
||||
rustup default stable
|
||||
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
|
||||
rustc -vV
|
||||
cargo -vV
|
||||
cargo install cargo-ndk
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="{{ github.ref_name }}"
|
||||
elif [ "${{ github.ref_name }}" == "master"]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-$((${{ github.run_number }} + 654))"
|
||||
else
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
cd app
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease
|
||||
|
||||
- name: Sign app
|
||||
run: |
|
||||
BUILD_TOOLS=$ANDROID_HOME/build-tools/33.0.0
|
||||
|
||||
echo "${{ secrets.ANDROID_SIGNING_KEYSTORE }}" | base64 --decode > keystore.jks
|
||||
if [ "${{ secrets.ANDROID_SIGNING_KEY_PASS }}" != "" ]; then
|
||||
$BUILD_TOOLS/apksigner sign --ks ./keystore.jks --ks-key-alias "${{ secrets.ANDROID_SIGNING_ALIAS }}" --ks-pass "pass:${{ secrets.ANDROID_SIGNING_KEYSTORE_PASS }}" --key-pass "pass:${{ secrets.ANDROID_SIGNING_KEY_PASS }}" --out $APP_OUTPUTS_DIR/app-signed.apk $APP_OUTPUTS_DIR/app-release-unsigned.apk
|
||||
else
|
||||
$BUILD_TOOLS/apksigner sign --ks ./keystore.jks --ks-key-alias "${{ secrets.ANDROID_SIGNING_ALIAS }}" --ks-pass "pass:${{ secrets.ANDROID_SIGNING_KEYSTORE_PASS }}" --out $APP_OUTPUTS_DIR/app-signed.apk $APP_OUTPUTS_DIR/app-release-unsigned.apk
|
||||
fi
|
||||
|
||||
rm keystore.jks
|
||||
|
||||
- name: Prepare artifact
|
||||
run: |
|
||||
mkdir release
|
||||
mv $APP_OUTPUTS_DIR/app-signed.apk release/doukutsu-rs.apk
|
||||
cp LICENSE ./release
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: doukutsu-rs_android
|
||||
path: ./release/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save cache
|
||||
if: ${{ github.ref_name == 'master' || github.ref_type == 'tag' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
~/.gradle
|
||||
app/app/.cxx
|
||||
app/app/build
|
||||
drsandroid/target
|
||||
key: android-cargo
|
||||
|
||||
update_metadata:
|
||||
name: Update metadata
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref_type != 'tag' && always() }}
|
||||
needs: [build, build_android]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: doukutsu-rs/metadata
|
||||
token: ${{ secrets.METADATA_USER_TOKEN }}
|
||||
|
||||
- name: Update metadata
|
||||
id: metadata
|
||||
run: |
|
||||
export FILE="./metadata/nightly.json"
|
||||
if [ "${{ github.ref_name }}" == "master" ]; then
|
||||
export VERSION="${{ env.VERSION }}-$((${{ github.run_number }} + 654))"
|
||||
else
|
||||
export VERSION="${{ env.VERSION }}-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
if [ "${{ needs.build.result }}" == "success" ]; then
|
||||
node ./metadata.js --os linux --arch x86_64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_linux-x64.zip $FILE
|
||||
node ./metadata.js --os windows --arch x86_64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_windows-x64.zip $FILE
|
||||
node ./metadata.js --os windows --arch i686 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_windows-x32.zip $FILE
|
||||
node ./metadata.js --os macos --arch x64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_mac-x64.zip $FILE
|
||||
node ./metadata.js --os macos --arch arm64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_mac-arm64.zip $FILE
|
||||
fi
|
||||
|
||||
if [ "${{ needs.build_android.result }}" == "success" ]; then
|
||||
node ./metadata.js --os android --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_android.zip $FILE
|
||||
fi
|
||||
|
||||
echo "file=$FILE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload metadata
|
||||
run: |
|
||||
git config user.name ${{ vars.METADATA_USER_NAME }}
|
||||
git config user.email ${{ vars.METADATA_USER_EMAIL }}
|
||||
|
||||
git add ${{ steps.metadata.outputs.file }}
|
||||
git commit -m "Update nightly builds metadata(CI)"
|
||||
git push
|
|
@ -1,185 +1,46 @@
|
|||
name: Release
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-linux-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install libasound2-dev libudev-dev pkg-config
|
||||
|
||||
- name: Cache Cargo registry
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-stable-cargo-registry
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-cargo-registry-
|
||||
|
||||
- name: Cache Cargo index
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-stable-cargo-index
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-cargo-index-
|
||||
|
||||
- name: Cache Cargo build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-stable-target
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-target
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install latest stable Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --all --release && strip target/release/doukutsu-rs
|
||||
|
||||
- name: Copy executable to root directory
|
||||
run: cp target/release/doukutsu-rs doukutsu-rs.x86_64
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: doukutsu-rs_linux_x86_64
|
||||
path: doukutsu-rs.x86_64
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: doukutsu-rs.x86_64
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-windows-x86:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Cache Cargo registry
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-stable-cargo-registry
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-cargo-registry-
|
||||
|
||||
- name: Cache Cargo index
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-stable-cargo-index
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-cargo-index-
|
||||
|
||||
- name: Cache Cargo build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-stable-target
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-target
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install latest stable Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: i686-pc-windows-msvc
|
||||
default: true
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --target i686-pc-windows-msvc --all --release
|
||||
|
||||
- name: Copy executable to root directory
|
||||
run: cp target/i686-pc-windows-msvc/release/doukutsu-rs.exe doukutsu-rs.x86.exe
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: doukutsu-rs_windows_x86
|
||||
path: doukutsu-rs.x86.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: doukutsu-rs.x86.exe
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-windows-x64:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Cache Cargo registry
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-stable-cargo-registry
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-cargo-registry-
|
||||
|
||||
- name: Cache Cargo index
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-stable-cargo-index
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-cargo-index-
|
||||
|
||||
- name: Cache Cargo build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-stable-target
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-target
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install latest stable Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --all --release
|
||||
|
||||
- name: Copy executable to root directory
|
||||
run: cp target/release/doukutsu-rs.exe doukutsu-rs.x86_64.exe
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: doukutsu-rs_windows_x64
|
||||
path: doukutsu-rs.x86_64.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: doukutsu-rs.x86_64.exe
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- released
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
update_metadata:
|
||||
name: Update metadata
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: doukutsu-rs/metadata
|
||||
token: ${{ secrets.METADATA_USER_TOKEN }}
|
||||
- name: Update metadata
|
||||
id: metadata
|
||||
run: |
|
||||
export VERSION="${{ github.event.release.tag_name }}"
|
||||
export FILE="./metadata/stable.json"
|
||||
|
||||
node ./metadata.js --os windows --arch x86_64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.windows.x86_64.$VERSION.exe $FILE
|
||||
node ./metadata.js --os windows --arch i686 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.windows.i686.$VERSION.exe $FILE
|
||||
node ./metadata.js --os macos --arch x86_64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.macos.x86_64.$VERSION.zip $FILE
|
||||
node ./metadata.js --os macos --arch arm64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.macos.arm64.$VERSION.zip $FILE
|
||||
node ./metadata.js --os linux --arch x86_64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.linux.x86_64.$VERSION.elf $FILE
|
||||
node ./metadata.js --os android --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.android.$VERSION.apk $FILE
|
||||
|
||||
echo "file=$FILE" >> "$GITHUB_OUTPUT"
|
||||
- name: Upload metadata
|
||||
run: |
|
||||
git config user.name ${{ vars.METADATA_USER_NAME }}
|
||||
git config user.email ${{ vars.METADATA_USER_EMAIL }}
|
||||
|
||||
git add ${{ steps.metadata.outputs.file }}
|
||||
git commit -m "Update stable builds metadata(CI)"
|
||||
git push
|
||||
|
||||
|
|
@ -2,13 +2,16 @@
|
|||
.vscode/
|
||||
|
||||
# Cave Story (copyrighted) data files
|
||||
data/
|
||||
/data/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Shader binary files
|
||||
*.dksh
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
@ -60,3 +63,5 @@ ehthumbs_vista.db
|
|||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
*.log
|
||||
|
|
99
Cargo.toml
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "doukutsu-rs"
|
||||
description = "A re-implementation of Cave Story (Doukutsu Monogatari) engine"
|
||||
version = "0.1.0"
|
||||
authors = ["Alula"]
|
||||
edition = "2018"
|
||||
version = "0.101.0"
|
||||
authors = ["Alula", "dawnDus"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
|
||||
[lib]
|
||||
#crate-type = ["lib", "cdylib"]
|
||||
crate-type = ["lib"]
|
||||
|
||||
[[bin]]
|
||||
|
@ -19,83 +19,102 @@ required-features = ["exe"]
|
|||
[profile.release]
|
||||
lto = "off"
|
||||
panic = "abort"
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
split-debuginfo = "packed"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
codegen-units = 256
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "doukutsu-rs"
|
||||
identifier = "io.github.doukutsu_rs"
|
||||
version = "0.1.0"
|
||||
version = "0.101.0"
|
||||
resources = ["data"]
|
||||
copyright = "Copyright (c) 2020-2021 Alula and contributors"
|
||||
copyright = "Copyright (c) 2020-2023 doukutsu-rs contributors"
|
||||
category = "Game"
|
||||
osx_minimum_system_version = "10.12"
|
||||
|
||||
[features]
|
||||
default = ["default-base", "backend-sdl", "render-opengl", "exe"]
|
||||
default-base = ["scripting-lua", "ogg-playback", "netplay"]
|
||||
default = ["default-base", "backend-sdl", "render-opengl", "exe", "webbrowser", "discord-rpc"]
|
||||
default-base = ["ogg-playback"]
|
||||
ogg-playback = ["lewton"]
|
||||
backend-sdl = ["sdl2", "sdl2-sys"]
|
||||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||
backend-horizon = []
|
||||
render-opengl = []
|
||||
scripting-lua = ["lua-ffi"]
|
||||
netplay = ["tokio", "serde_cbor"]
|
||||
discord-rpc = ["discord-rich-presence"]
|
||||
netplay = ["serde_cbor"]
|
||||
editor = []
|
||||
hooks = ["libc"]
|
||||
exe = []
|
||||
android = []
|
||||
|
||||
[dependencies]
|
||||
#cpal = { path = "./3rdparty/cpal" }
|
||||
#glutin = { path = "./3rdparty/glutin/glutin", optional = true }
|
||||
#lua-ffi = { path = "./3rdparty/luajit-rs", optional = true }
|
||||
#winit = { path = "./3rdparty/winit", optional = true, default_features = false, features = ["x11"] }
|
||||
bitvec = "0.20"
|
||||
#sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
#sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] }
|
||||
#cpal = { path = "./3rdparty/cpal" }
|
||||
byteorder = "1.4"
|
||||
case_insensitive_hashmap = "1.0.0"
|
||||
chrono = "0.4"
|
||||
cpal = { git = "https://github.com/doukutsu-rs/cpal.git", rev = "4218ff23242834d36bcdcc0c2e3883985c15b5e0" }
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
|
||||
cpal = { git = "https://github.com/doukutsu-rs/cpal", rev = "9d269d8724102404e73a61e9def0c0cbc921b676" }
|
||||
directories = "3"
|
||||
funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105
|
||||
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
|
||||
imgui = "0.8.0"
|
||||
image = { version = "0.23", default-features = false, features = ["png", "bmp"] }
|
||||
discord-rich-presence = { version = "0.2", optional = true }
|
||||
downcast = "0.11"
|
||||
encoding_rs = "0.8.33"
|
||||
fern = "0.6.2"
|
||||
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "2dd95f042e6e090d36f577cbea125560dd99bd27", optional = true, default_features = false, features = ["x11"] }
|
||||
imgui = "0.8"
|
||||
image = { version = "0.24", default-features = false, features = ["png", "bmp"] }
|
||||
itertools = "0.10"
|
||||
lazy_static = "1.4.0"
|
||||
lewton = { version = "0.10.2", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
lazy_static = "1.4"
|
||||
lewton = { version = "0.10", optional = true }
|
||||
log = "0.4"
|
||||
lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "e0b2ff5960f7ef9974aa9675cebe4907bee0134f", optional = true }
|
||||
num-derive = "0.3.2"
|
||||
num-traits = "0.2.12"
|
||||
paste = "1.0.0"
|
||||
sdl2 = { version = "=0.34.2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
sdl2-sys = { version = "=0.34.2", optional = true, features = ["bundled", "static-link"] }
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
open = "3.2"
|
||||
paste = "1.0"
|
||||
pelite = { version = ">=0.9.2", default-features = false, features = ["std"] }
|
||||
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "f2f1e29a416bcc22f2faf411866db2c8d9536308", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "f2f1e29a416bcc22f2faf411866db2c8d9536308", optional = true, features = ["bundled", "static-link"] }
|
||||
rc-box = "1.2.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_derive = "1"
|
||||
serde_cbor = { version = "0.11.2", optional = true }
|
||||
serde_cbor = { version = "0.11", optional = true }
|
||||
serde_json = "1.0"
|
||||
simple_logger = { version = "1.13" }
|
||||
strum = "0.20"
|
||||
strum_macros = "0.20"
|
||||
tokio = { version = "1.12.0", features = ["net"], optional = true }
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
# remove and replace when drain_filter is in stable
|
||||
vec_mut_scan = "0.4"
|
||||
webbrowser = "0.5.5"
|
||||
winit = { git = "https://github.com/alula/winit.git", rev = "6acf76ff192dd8270aaa119b9f35716c03685f9f", optional = true, default_features = false, features = ["x11"] }
|
||||
webbrowser = { version = "0.8.6", optional = true }
|
||||
winit = { git = "https://github.com/doukutsu-rs/winit.git", rev = "878f206d19af01b0977277929eee5e32667453c0", optional = true, default_features = false, features = ["x11"] }
|
||||
xmltree = "0.10"
|
||||
|
||||
#[build-dependencies]
|
||||
#gl_generator = { version = "0.14.0", optional = true }
|
||||
#hack to not link SDL_image on Windows(causes a linker error)
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "f2f1e29a416bcc22f2faf411866db2c8d9536308", optional = true, features = ["image", "unsafe_textures", "bundled", "static-link"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.7"
|
||||
objc = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.3"
|
||||
ndk-glue = "0.3"
|
||||
ndk-sys = "0.2"
|
||||
jni = "0.19"
|
||||
ndk = "0.7"
|
||||
ndk-glue = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
jni = "0.20"
|
||||
|
||||
[target.'cfg(target_os = "horizon")'.dependencies]
|
||||
#deko3d = { path = "./3rdparty/deko3d" }
|
||||
deko3d = { git = "https://github.com/doukutsu-rs/deko3d-rs", branch = "master" }
|
||||
|
|
22
LICENSE
|
@ -1,7 +1,21 @@
|
|||
Copyright 2020 Alula
|
||||
MIT/doukutsu-rs License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Copyright 2020 doukutsu-rs contributors.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
The Software cannot be redistributed bundled with data files taken from any commercial port
|
||||
released by Nicalis Inc. without their explicit permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
249
README.md
|
@ -1,59 +1,240 @@
|
|||
# doukutsu-rs
|
||||
![doukutsu-rs](./res/sue_crab_banner_github.png)
|
||||
|
||||
![Release](https://github.com/doukutsu-rs/doukutsu-rs/workflows/Release/badge.svg)
|
||||
|
||||
[Download latest Nightly builds](https://github.com/doukutsu-rs/doukutsu-rs/actions) (Requires being logged in to GitHub)
|
||||
|
||||
A re-implementation of Cave Story (Doukutsu Monogatari) engine written in [Rust](https://www.rust-lang.org/).
|
||||
|
||||
**The project is still incomplete and not fully playable yet.** [Click here to see the current status of the project](https://github.com/doukutsu-rs/doukutsu-rs/issues/10).
|
||||
A fully playable re-implementation of the Cave Story (Doukutsu Monogatari) engine written
|
||||
in [Rust](https://www.rust-lang.org/).
|
||||
|
||||
[Join the Discord server](https://discord.gg/fbRsNNB)
|
||||
|
||||
[![CI](https://github.com/doukutsu-rs/doukutsu-rs/actions/workflows/ci.yml/badge.svg?branch=master)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master?preview)
|
||||
|
||||
- [Get nightly builds](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master?preview) (recommended for now, has latest fixes and improvements)
|
||||
|
||||
Permalinks to latest builds from `master` branch:
|
||||
|
||||
- [Windows (64-bit)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_windows-x64.zip)
|
||||
- [Windows (32-bit)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_windows-x32.zip)
|
||||
- [macOS (Intel, 64-bit, 10.14+)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_mac-x64.zip)
|
||||
- [macOS (Apple M1, 11.0+)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_mac-arm64.zip)
|
||||
- [Linux (64-bit)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_linux-x64.zip)
|
||||
- [Android (armv7/arm64/x86)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_android.zip)
|
||||
|
||||
- [Get stable/beta builds from GitHub Releases](https://github.com/doukutsu-rs/doukutsu-rs/releases)
|
||||
|
||||
> [!NOTE]
|
||||
> macOS note: If you get a `"doukutsu-rs" can't be opened` message, right-click doukutsu-rs.app and click open.
|
||||
|
||||
> [!NOTE]
|
||||
> If you get issues with Epic Games Store version, scroll down for instructions.
|
||||
|
||||
#### Data files
|
||||
|
||||
This repository does not contain any copyrighted files.
|
||||
In order to work doukutsu-rs needs to be paired with supported data files. This repository does not contain any data
|
||||
files.
|
||||
|
||||
For better user experience, pre-built binaries are distributed with slightly modified freeware game files.
|
||||
doukutsu-rs works fine with freeware data files or [NXEngine(-evo)](https://github.com/nxengine/nxengine-evo) or from a
|
||||
supported copy of [Cave Story+](https://www.nicalis.com/games/cavestory+).
|
||||
|
||||
*doukutsu-rs* should work fine with pre-extracted and tweaked data files from [this repository](https://github.com/doukutsu-rs/game-data), [NXEngine(-evo)](https://github.com/nxengine/nxengine-evo) extracted freeware data files and [Cave Story+](https://www.nicalis.com/games/cavestory+) data files.
|
||||
<details>
|
||||
<summary>How to set up data files on Android</summary>
|
||||
|
||||
If your phone has an app called **"Files"**:
|
||||
|
||||
Vanilla Cave Story does not work yet because some important data files have been embedded inside the executable. and we don't have a loader/extractor implemented yet.
|
||||
1. Launch this app.
|
||||
2. Press **☰** on the top left corner.
|
||||
3. Tap on **"doukutsu-rs game data"**.
|
||||
4. Copy your game data files to the opened folder.
|
||||
|
||||
##### Where to get them?
|
||||
|
||||
If your phone does not have this app:
|
||||
|
||||
1. Install the **"Material Files"** app from *Hai Zhang* and launch it([Google Play](https://play.google.com/store/apps/details?id=me.zhanghai.android.files) | [F-Droid](https://f-droid.org/en/packages/me.zhanghai.android.files/) | [Github Releases](https://github.com/zhanghai/MaterialFiles/releases)).
|
||||
2. Press **☰** on the top left corner.
|
||||
3. Press **"+ Add storage"**.
|
||||
4. In the window that pops up, press **"External storage"**.
|
||||
5. Press **☰** on the top left corner.
|
||||
6. Tap on **"doukutsu-rs game data"**.
|
||||
7. Press the large blue button at the bottom labelled **"USE THIS FOLDER"**.
|
||||
8. Then click on **☰** in the top left corner again and open.
|
||||
9. Tap on **"files"** above **"+ Add storage"**.
|
||||
10. Copy your game data files to the opened folder.
|
||||
</details>
|
||||
|
||||
#### Supported game editions and data file acquisition guides
|
||||
|
||||
**Freeware**
|
||||
|
||||
- https://github.com/doukutsu-rs/game-data - Freeware game data distributed with CI builds, based on those two below.
|
||||
- ~~https://github.com/Clownacy/CSE2/archive/enhanced.zip - copy `game_english/data` from archive to the runtime directory (place you run the executable from, usually project root)~~
|
||||
- https://github.com/nxengine/nxengine-evo/releases/download/v2.6.4/NXEngine-v2.6.4-Win32.zip - copy `NXEngine-evo-2.6.4-xxx/data` from the archive to runtime directory
|
||||
doukutsu-rs works out of the box when it's placed in the same directory as the original Doukutsu.exe executable. On the initial
|
||||
startup, doukutsu-rs will automatically extract the additional resources that are embedded in the vanilla game into the `data`
|
||||
directory. Until that is done, both doukutsu-rs and the vanilla executable have to exist in the directory.
|
||||
|
||||
<details>
|
||||
<summary>Example root directory</summary>
|
||||
|
||||
![example root directory with doukutsu-rs and vanilla Cave Story](https://i.imgur.com/3dJ7WMB.png)
|
||||
|
||||
</details>
|
||||
|
||||
**Cave Story+**
|
||||
|
||||
- SDL version (first released in 2011 on Steam)
|
||||
- PC release (Steam) - Copy `data` folder from installation directory ([guide for Steam](https://steamcommunity.com/sharedfiles/filedetails/?id=760447682)) to the runtime directory.
|
||||
- PC release (Epic Games Store) - Essentially the same thing as with Steam version.
|
||||
- PC release (Humble Bundle) - Essentially the same thing as with Steam version.
|
||||
- KAGE version (first released in 2017 on Switch)
|
||||
- PC release - Sadly not there yet but Tyrone mentioned it's coming in 2021.
|
||||
- Switch release - (tested only with eShop version) Extract `data` directory from romfs. Requires a hacked console and a recent and legal copy of the game. If you don't know how, look in Google how to exactly do that because the methods really differ.
|
||||
doukutsu-rs can be used as drop-in replacement for `CaveStory+.exe`. No modifications to game files are needed.
|
||||
|
||||
#### Mandatory screenshots
|
||||
**Original version (first released in 2011 on Steam)** - expand for instructions
|
||||
|
||||
**Freeware data files:**
|
||||
<details>
|
||||
<summary>Steam release (Win/Mac/Linux)</summary>
|
||||
|
||||
![Japanese Freeware](https://i.imgur.com/eZ0V5rK.png)
|
||||
The `data` folder is in the same place across all platforms.
|
||||
|
||||
**Cave Story+ data files:**
|
||||
If you want to use doukutsu-rs as a substitute for Mac version of Cave Story+ (which at moment of writing doesn't work
|
||||
on 10.15+ anymore), do the following:
|
||||
|
||||
![CS+ with enhanced graphics](https://i.imgur.com/YaPAs70.png)
|
||||
1. Find the doukutsu-rs executable:
|
||||
- In AppVeyor builds, it's in `doukutsu-rs.app/Contents/MacOS/doukutsu-rs`
|
||||
- In your own builds, it's in `target/(release|debug)/doukutsu-rs`
|
||||
2. Open Steam Library, select `Cave Story+`, press the `Manage` button (gear icon) and select `Properties...`
|
||||
3. Select `Local Files` and press `Browse...`.
|
||||
4. Open the `Cave Story+.app` bundle and navigate to `Contents/MacOS` directory.
|
||||
5. Rename the `Cave Story+` executable to something else or delete it.
|
||||
6. Copy the doukutsu-rs executable and rename it to `Cave Story+`.
|
||||
7. Launch the game from Steam and enjoy!
|
||||
|
||||
![image](https://user-images.githubusercontent.com/53099651/155904982-eb6032d8-7a4d-4af7-ae6f-b69041ecfaa4.png)
|
||||
|
||||
</details>
|
||||
|
||||
> [!WARNING]
|
||||
> **EPIC GAMES STORE VERSION WARNING**
|
||||
>
|
||||
> Nicalis for some reason ships a stray `opengl32.dll` DLL from Windows 7 with the Epic Games Store copies of Cave Story+.
|
||||
>
|
||||
> However as the game is 32-bit and the dll is 64-bit it has no effect on the original version, but as it's a core Windows DLL and doukutsu-rs ships 64-bit builds and uses OpenGL, it's makes the game crash on startup.
|
||||
>
|
||||
> The fix is to simply delete `opengl32.dll`, as it's not used anyway.
|
||||
|
||||
<details>
|
||||
<summary>Epic Games Store</summary>
|
||||
|
||||
Check your default installation directory.
|
||||
|
||||
![image](https://user-images.githubusercontent.com/53099651/155905035-0080eace-bd98-4cf5-9628-c98334ea768c.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>GOG</summary>
|
||||
|
||||
Check your default installation directory.
|
||||
|
||||
![image](https://user-images.githubusercontent.com/53099651/155906494-1e53f174-f12f-41be-ab53-8745cdf735b5.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Humble Bundle</summary>
|
||||
|
||||
The archive from Humble Bundle contains the necessary `data` folder, in the same folder as `CaveStory+.exe`.
|
||||
|
||||
![image](https://user-images.githubusercontent.com/96957561/156861929-7fa03951-442b-4277-b673-474189411103.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>WiiWare</summary>
|
||||
|
||||
1. [Dump Your WiiWare `.wad`](https://wii.guide/dump-wads.html)
|
||||
2. [Extract and decompress the `data` folder](https://docs.google.com/document/d/1hDNDgNl0cUDlFOQ_BUOq3QCGb7S0xfUxRoob-hfM-DY)
|
||||
Example of a [valid uncompressed `data` folder](https://user-images.githubusercontent.com/53099651/159585593-43fead24-b041-48f4-8332-be50d712310d.png)
|
||||
|
||||
</details>
|
||||
|
||||
**Remastered version (first released in 2017 on Switch)**
|
||||
|
||||
> [!NOTE]
|
||||
> This version is **incompatible** with saves from the original version.
|
||||
>
|
||||
> Interchanging the save files may result in spawning in wrong locations, softlocks, graphical glitches, or other issues.
|
||||
|
||||
<details>
|
||||
<summary>Nintendo Switch</summary>
|
||||
|
||||
Extract the `data` folder (contained in `romfs`) from your console using tool such as [nxdumptool](https://github.com/DarkMatterCore/nxdumptool).
|
||||
|
||||
**Important notes:**
|
||||
|
||||
- doukutsu-rs doesn't rely on the original ROM or executable, you just need the data files, go to `RomFS options` menu to just extract the files to SD card so you don't need to do any extra steps.
|
||||
- Ensure you're dumping the files **with update included** (`Use update/DLC` option), as 1.0 isn't supported.
|
||||
|
||||
**Nintendo Switch homebrew port specific info**
|
||||
|
||||
If you're running the homebrew port (drshorizon.nro) on your Switch, you can avoid the dumping step, doukutsu-rs will
|
||||
automatically detect and mount the data files if you run it over Cave Story+ in Title Override mode (hold `R` while starting CS+ and launch d-rs from hbmenu).
|
||||
|
||||
You can put your own data files in `/switch/doukutsu-rs/data` directory on SD Card, which will be overlayed over RomFS if
|
||||
you run it in setup described above.
|
||||
|
||||
</details>
|
||||
|
||||
#### Controls
|
||||
|
||||
Same controls as the default for freeware and Cave Story+ keyboard.
|
||||
|
||||
To change, use the control customization menu or edit `doukutsu-rs\data\settings.json` within your user directory.
|
||||
|
||||
| | P1 | P2 |
|
||||
| ------------------------- | --------- | --------- |
|
||||
| Movement | `← ↑ ↓ →` | `, L . /` |
|
||||
| Jump | `Z` | `B` |
|
||||
| Shoot | `X` | `N` |
|
||||
| Cycle Weapon | `A and S` | `G and H` |
|
||||
| Inventory / Skip cutscene | `Q` | `T` |
|
||||
| Map | `W` | `Y` |
|
||||
| Strafe | `LShift` | `RShift` |
|
||||
|
||||
- `Alt + Enter` - Toggle Fullscreen
|
||||
- `F2` (While paused) - Quick Restart
|
||||
|
||||
#### Screenshots
|
||||
|
||||
<details>
|
||||
<summary>Freeware</summary>
|
||||
|
||||
![JP Freeware 2](https://user-images.githubusercontent.com/53099651/155924461-c63afc93-a41f-4cfd-ac9f-8f021cebcb04.png)
|
||||
|
||||
![Toroko Fight Freeware](https://user-images.githubusercontent.com/53099651/155924215-d492907a-ed0e-4323-bd46-61745b8fb32a.png)
|
||||
|
||||
![No Lighting Freeware](https://user-images.githubusercontent.com/53099651/155923814-621cf29e-bb20-4680-a96d-f049aaef1f88.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Original CS+</summary>
|
||||
|
||||
![CS+ Sand Zone](https://user-images.githubusercontent.com/53099651/155923620-db230077-0df5-4de4-b086-be6b4dcbc6df.png)
|
||||
|
||||
![CS+ Showoff Outer Wall](https://user-images.githubusercontent.com/53099651/155920013-3967cd03-8d69-4fc5-8f1d-fe659ff2e953.png)
|
||||
|
||||
![CS+ Challenge](https://user-images.githubusercontent.com/53099651/155919381-7e8159a0-a7cf-461a-8be2-2ce864631299.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Remastered CS+</summary>
|
||||
|
||||
![Balcony Switch](https://user-images.githubusercontent.com/53099651/155918810-063c0f06-2d48-485f-8367-6337525deab7.png)
|
||||
|
||||
![Dogs Switch](https://github.com/doukutsu-rs/doukutsu-rs/assets/6276139/30ba01ae-375d-4488-98c4-98e3e8c7f187)
|
||||
|
||||
![Almond Switch](https://github.com/doukutsu-rs/doukutsu-rs/assets/6276139/42d4b6a3-4fc5-4aaf-9535-462c4c484dc7)
|
||||
|
||||
![Hell Switch](https://user-images.githubusercontent.com/53099651/155918602-62268274-c529-41c2-a87e-0c31e7874b94.png)
|
||||
|
||||
</details>
|
||||
|
||||
#### Credits
|
||||
|
||||
- Studio Pixel/Nicalis for Cave Story
|
||||
- [ggez](https://github.com/ggez/ggez) - we took few bits from it while moving away to SDL2.
|
||||
- [@ClayHanson_](https://twitter.com/ClayHanson_) - for letting us use his .pxchar skin format from Cave Story Multiplayer mod.
|
||||
- [Cave Story Tribute Site](https://cavestory.org) - has lots of useful resources related to the game.
|
||||
- Studio Pixel/Nicalis for Cave Story
|
||||
- [@Daedily](https://twitter.com/Daedliy) - brand artwork (Icon / Banner / Server), screenshots for this guide.
|
||||
- [ggez](https://github.com/ggez/ggez) - parts of it are used in `crate::framework`, notably the VFS code.
|
||||
- [Clownacy](https://github.com/Clownacy) - widescreen camera code.
|
||||
- [LunarLambda for organism](https://gitdab.com/LunarLambda/organism) - which is being used by us as `.org` playback engine.
|
||||
- [@uselesscalcium](https://twitter.com/uselesscalcium) - Android port icon artwork
|
||||
- [LunarLambda for organism](https://github.com/doukutsu-rs/organism) - used as basis for our Organya playback engine.
|
||||
- [Zoroyoshi](http://z.apps.atjp.jp/k12x10/) - k12x10 font we use as built-in font.
|
||||
|
|
|
@ -231,3 +231,4 @@ fabric.properties
|
|||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,android
|
||||
app/release/
|
|
@ -4,21 +4,23 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
ndkVersion "22.1.7171670"
|
||||
namespace "io.github.doukutsu_rs"
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
ndkVersion "25.2.9519653"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.doukutsu_rs"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
targetSdkVersion 33
|
||||
versionCode 2
|
||||
versionName "0.101.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86', 'arm64-v8a', 'armeabi-v7a'
|
||||
abiFilters 'arm64-v8a'
|
||||
stl = "c++_shared"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
|
@ -41,6 +43,15 @@ android {
|
|||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86', 'arm64-v8a', 'armeabi-v7a'
|
||||
stl = "c++_shared"
|
||||
}
|
||||
}
|
||||
debug {
|
||||
jniDebuggable true
|
||||
renderscriptDebuggable true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,28 +69,28 @@ android {
|
|||
path "src/main/cpp/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
excludes += "**/dummy.so"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.support:support-annotations:28.0.0'
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
implementation 'com.android.support:design:28.0.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
|
||||
implementation 'android.arch.navigation:navigation-fragment:1.0.0'
|
||||
implementation 'android.arch.navigation:navigation-ui:1.0.0'
|
||||
implementation 'androidx.annotation:annotation:1.5.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'androidx.core:core:1.9.0'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
}
|
||||
|
||||
println("cargo target: ${project.buildDir.getAbsolutePath()}/rust-target")
|
||||
println("ndk dir: ${android.ndkDirectory}")
|
||||
|
||||
cargoNdk {
|
||||
targets = [
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64"
|
||||
]
|
||||
librariesNames = ["libdrsandroid.so"]
|
||||
//targetDirectory = "${project.buildDir.getAbsolutePath()}/rust-target"
|
||||
module = "../drsandroid/"
|
||||
extraCargoEnv = ["ANDROID_NDK_HOME": android.ndkDirectory]
|
||||
extraCargoBuildArguments = []
|
||||
|
@ -88,9 +99,17 @@ cargoNdk {
|
|||
buildTypes {
|
||||
release {
|
||||
buildType = "release"
|
||||
targets = [
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
debug {
|
||||
buildType = "debug"
|
||||
targets = [
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.github.doukutsu_rs">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application android:allowBackup="true" android:extractNativeLibs="true" android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:appCategory="game"
|
||||
android:description="@string/app_description"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:isGame="true"
|
||||
android:label="@string/app_name"
|
||||
android:resizeableActivity="false"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Doukutsurs">
|
||||
<activity android:name=".DownloadActivity" android:label="Download" android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.Doukutsurs.NoActionBar"></activity>
|
||||
<activity android:name=".MainActivity" android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="doukutsu-rs" android:launchMode="standard" android:screenOrientation="sensorLandscape">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:exported="true"
|
||||
android:label="doukutsu-rs"
|
||||
android:launchMode="standard"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.Doukutsurs.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="drsandroid" />
|
||||
</activity>
|
||||
|
||||
<provider android:name=".DoukutsuDocumentsProvider" android:authorities="${documentsAuthority}"
|
||||
android:exported="true" android:grantUriPermissions="true"
|
||||
<activity
|
||||
android:name=".DownloadActivity"
|
||||
android:label="Download"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.Doukutsurs.NoActionBar" />
|
||||
|
||||
<activity
|
||||
android:name=".GameActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="standard"
|
||||
android:screenOrientation="sensorLandscape">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="drsandroid" />
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".DoukutsuDocumentsProvider"
|
||||
android:authorities="${documentsAuthority}"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Sets the minimum version of CMake required to build your native library.
|
||||
# This ensures that a certain set of CMake features is available to
|
||||
# your build.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(doukutsu-rs)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
# Copy shared STL files to Android Studio output directory so they can be
|
||||
# packaged in the APK.
|
||||
|
@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.10)
|
|||
function(configure_shared_stl lib_path so_base)
|
||||
message("Configuring STL ${so_base} for ${ANDROID_ABI}")
|
||||
configure_file(
|
||||
"${ANDROID_NDK}/sources/cxx-stl/${lib_path}/libs/${ANDROID_ABI}/lib${so_base}.so"
|
||||
"${ANDROID_NDK}/toolchains/llvm/prebuilt/${ANDROID_HOST_TAG}/sysroot/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/lib${so_base}.so"
|
||||
"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${so_base}.so"
|
||||
COPYONLY)
|
||||
endfunction()
|
||||
|
@ -50,4 +50,4 @@ endif()
|
|||
# and CMake builds them for you. When you build your app, Gradle
|
||||
# automatically packages shared libraries with your APK.
|
||||
|
||||
#add_library(dummy SHARED dummy.cpp)
|
||||
add_library(dummy SHARED dummy.cpp)
|
||||
|
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 178 KiB |
|
@ -0,0 +1,16 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.Window;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
public class ActivityUtils {
|
||||
public static void hideSystemBars(Activity activity) {
|
||||
Window window = activity.getWindow();
|
||||
WindowInsetsControllerCompat windowInsetsController =
|
||||
WindowCompat.getInsetsController(window, window.getDecorView());
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
|
||||
}
|
||||
}
|
|
@ -1,21 +1,31 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Path;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
||||
private final static String[] DEFAULT_ROOT_PROJECTION =
|
||||
|
@ -54,7 +64,7 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
row.add(Root.COLUMN_ROOT_ID, id);
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
row.add(Root.COLUMN_TITLE,
|
||||
getContext().getString(R.string.document_provider_name));
|
||||
getContext().getString(R.string.app_name));
|
||||
row.add(Root.COLUMN_MIME_TYPES, "*/*");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, file.getFreeSpace());
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
|
||||
|
@ -95,7 +105,9 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
pushFile(result, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result.setNotificationUri(getContext().getContentResolver(), DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, parentDocumentId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -134,7 +146,16 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Couldn't create file: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_INSERT);
|
||||
} else {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
@ -147,8 +168,38 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
}
|
||||
|
||||
deleteRecursive(file);
|
||||
// todo refresh this shit
|
||||
// getContext().getContentResolver().refresh()
|
||||
|
||||
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_DELETE);
|
||||
} else {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) throws FileNotFoundException {
|
||||
if (parentDocumentId == null) {
|
||||
parentDocumentId = getContext().getFilesDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
File childFile = new File(childDocumentId);
|
||||
if (!childFile.exists()) {
|
||||
throw new FileNotFoundException(childFile.getAbsolutePath()+" doesn't exist");
|
||||
} else if (!isChildDocument(parentDocumentId, childDocumentId)) {
|
||||
throw new FileNotFoundException(childDocumentId+" is not child of "+parentDocumentId);
|
||||
}
|
||||
|
||||
LinkedList<String> path = new LinkedList<>();
|
||||
while (childFile != null && isChildDocument(parentDocumentId, childFile.getAbsolutePath())) {
|
||||
path.addFirst(childFile.getAbsolutePath());
|
||||
childFile = childFile.getParentFile();
|
||||
}
|
||||
|
||||
return new Path(parentDocumentId, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,11 +238,26 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
File newPath = new File(file.getParentFile().getAbsolutePath() + "/" + displayName);
|
||||
|
||||
try {
|
||||
Files.move(file.toPath(), newPath.toPath());
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Files.move(file.toPath(), newPath.toPath());
|
||||
} else {
|
||||
if (!file.renameTo(newPath)) {
|
||||
throw new IOException("Couldn't rename file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_UPDATE);
|
||||
} else {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
return newPath.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
@ -205,8 +271,18 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
deleteRecursive(f);
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (!f.getAbsolutePath().equals(f.getCanonicalPath())) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ package io.github.doukutsu_rs;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Handler;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
|
@ -18,6 +19,7 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
private ProgressBar progressBar;
|
||||
private DownloadThread downloadThread;
|
||||
private String basePath;
|
||||
private Handler handler = new Handler();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -26,7 +28,9 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
setContentView(R.layout.activity_download);
|
||||
txtProgress = findViewById(R.id.txtProgress);
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
basePath = getFilesDir().getAbsolutePath() + "/data/";
|
||||
ActivityUtils.hideSystemBars(this);
|
||||
|
||||
basePath = getFilesDir().getAbsolutePath() + "/";
|
||||
|
||||
downloadThread = new DownloadThread();
|
||||
downloadThread.start();
|
||||
|
@ -39,23 +43,37 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private class DownloadThread extends Thread {
|
||||
private static final String DOWNLOAD_URL = "https://github.com/doukutsu-rs/game-data/archive/refs/heads/master.zip";
|
||||
private final ArrayList<DownloadEntry> urls = new ArrayList<>();
|
||||
|
||||
private final ArrayList<String> filesWhitelist = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.filesWhitelist.add("data/");
|
||||
this.filesWhitelist.add("Doukutsu.exe");
|
||||
|
||||
// DON'T SET `true` VALUE FOR TRANSLATIONS
|
||||
this.urls.add(new DownloadEntry(R.string.download_entries_base, "https://www.cavestory.org/downloads/cavestoryen.zip", true));
|
||||
|
||||
for (DownloadEntry entry : this.urls) {
|
||||
this.download(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void download(DownloadEntry downloadEntry) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(DOWNLOAD_URL);
|
||||
URL url = new URL(downloadEntry.url);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IllegalStateException("Bad HTTP response code: " + connection.getResponseCode());
|
||||
throw new IllegalStateException(getString(R.string.download_status_error_http, connection.getResponseCode()));
|
||||
}
|
||||
|
||||
int fileLength = connection.getContentLength();
|
||||
if (fileLength == 0) {
|
||||
progressBar.setIndeterminate(true);
|
||||
handler.post(() -> progressBar.setIndeterminate(true));
|
||||
}
|
||||
|
||||
byte[] zipFile;
|
||||
|
@ -78,10 +96,10 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
if (last + 1000 >= now) {
|
||||
int speed = (int) ((downloaded - downloadedLast) / 1024.0);
|
||||
String text = (fileLength > 0)
|
||||
? String.format(Locale.ENGLISH, "Downloading... %d%% (%d/%d KiB, %d KiB/s)", downloaded * 100 / fileLength, downloaded / 1024, fileLength / 1024, speed)
|
||||
: String.format(Locale.ENGLISH, "Downloading... --%% (%d KiB, %d KiB/s)", downloaded / 1024, speed);
|
||||
? getString(R.string.download_status_downloading, downloadEntry.name, downloaded * 100 / fileLength, downloaded / 1024, fileLength / 1024, speed)
|
||||
: getString(R.string.download_status_downloading_null, downloadEntry.name, downloaded / 1024, speed);
|
||||
|
||||
txtProgress.setText(text);
|
||||
handler.post(() -> txtProgress.setText(text));
|
||||
|
||||
downloadedLast = downloaded;
|
||||
last = now;
|
||||
|
@ -94,46 +112,90 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
new File(basePath).mkdirs();
|
||||
try (ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zipFile))) {
|
||||
ZipEntry entry;
|
||||
byte[] buffer = new byte[4096];
|
||||
while ((entry = in.getNextEntry()) != null) {
|
||||
String entryName = entry.getName();
|
||||
this.unpack(zipFile, downloadEntry.isBase);
|
||||
|
||||
// strip prefix
|
||||
if (entryName.startsWith("game-data-master/")) {
|
||||
entryName = entryName.substring("game-data-master/".length());
|
||||
}
|
||||
handler.post(() -> txtProgress.setText(getString(R.string.download_status_done)));
|
||||
|
||||
txtProgress.setText("Unpacking: " + entryName);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
new File(basePath + entryName).mkdirs();
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(basePath + entryName)) {
|
||||
int count;
|
||||
while ((count = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
txtProgress.setText("Done!");
|
||||
|
||||
Intent intent = new Intent(DownloadActivity.this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
|
||||
startActivity(intent);
|
||||
DownloadActivity.this.finish();
|
||||
handler.post(() -> {
|
||||
Intent intent = new Intent(DownloadActivity.this, GameActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
DownloadActivity.this.finish();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
if (txtProgress != null) txtProgress.setText(e.getMessage());
|
||||
handler.post(() -> {
|
||||
if (txtProgress != null)
|
||||
txtProgress.setText(e.getMessage());
|
||||
});
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void unpack(byte[] zipFile, boolean isBase) throws IOException {
|
||||
ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zipFile));
|
||||
ZipEntry entry;
|
||||
byte[] buffer = new byte[4096];
|
||||
while ((entry = in.getNextEntry()) != null) {
|
||||
String entryName = entry.getName();
|
||||
|
||||
// strip prefix
|
||||
if (entryName.startsWith("CaveStory/")) {
|
||||
entryName = entryName.substring("CaveStory/".length());
|
||||
}
|
||||
|
||||
if (!this.entryInWhitelist(entryName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
final String s = entryName;
|
||||
handler.post(() -> txtProgress.setText(
|
||||
getString(R.string.download_status_unpacking, s)
|
||||
));
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
new File(basePath + entryName).mkdirs();
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(basePath + entryName)) {
|
||||
int count;
|
||||
while ((count = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean entryInWhitelist(String entry) {
|
||||
for (String file : this.filesWhitelist) {
|
||||
if (entry.startsWith(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadEntry {
|
||||
public String name; //e.g. "Polish translation", "Base data files"
|
||||
public String url;
|
||||
public boolean isBase = false;
|
||||
|
||||
DownloadEntry(String name, String url, boolean isBase) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
this.isBase = isBase;
|
||||
}
|
||||
|
||||
DownloadEntry(int name, String url, boolean isBase) {
|
||||
this.name = getString(name);
|
||||
this.url = url;
|
||||
this.isBase = isBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.SensorManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class GameActivity extends NativeActivity {
|
||||
private int[] displayInsets = new int[]{0, 0, 0, 0};
|
||||
private OrientationEventListener listener;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActivityUtils.hideSystemBars(this);
|
||||
|
||||
listener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
GameActivity.this.updateCutouts();
|
||||
}
|
||||
};
|
||||
|
||||
if (listener.canDetectOrientation()) {
|
||||
listener.enable();
|
||||
} else {
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (listener != null) {
|
||||
listener.disable();
|
||||
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
private void updateCutouts() {
|
||||
this.displayInsets[0] = 0;
|
||||
this.displayInsets[1] = 0;
|
||||
this.displayInsets[2] = 0;
|
||||
this.displayInsets[3] = 0;
|
||||
|
||||
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (insets != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], insets.getStableInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[1], insets.getStableInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[2], insets.getStableInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[3], insets.getStableInsetBottom());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.P) {
|
||||
android.view.DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
if (cutout != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], cutout.getSafeInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[0], cutout.getSafeInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[0], cutout.getSafeInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[0], cutout.getSafeInsetBottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void openDir(String path) {
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, path);
|
||||
|
||||
File file = new File(path);
|
||||
if (!file.isDirectory()) {
|
||||
Toast.makeText(getApplicationContext(), R.string.dir_not_found, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setDataAndType(uri, DocumentsContract.Document.MIME_TYPE_DIR);
|
||||
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch(ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.no_app_found_to_open_dir, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,119 +1,50 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.NativeActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.WindowInsets;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class MainActivity extends NativeActivity {
|
||||
private int[] displayInsets = new int[]{0, 0, 0, 0};
|
||||
private OrientationEventListener listener;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
ActivityUtils.hideSystemBars(this);
|
||||
|
||||
File f = new File(getFilesDir().getAbsolutePath() + "/data/");
|
||||
String[] list = f.list();
|
||||
if (!f.exists() || (list != null && list.length == 0)) {
|
||||
|
||||
messageBox("Missing data files", "No data files found, would you like to download them?", () -> {
|
||||
messageBox(getString(R.string.missing_data_title), getString(R.string.missing_data_desc), () -> {
|
||||
Intent intent = new Intent(this, DownloadActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
this.finish();
|
||||
});
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
listener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
MainActivity.this.updateCutouts();
|
||||
}
|
||||
};
|
||||
|
||||
if (listener.canDetectOrientation()) {
|
||||
listener.enable();
|
||||
}, this::launchGame);
|
||||
} else {
|
||||
listener = null;
|
||||
launchGame();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (listener != null) {
|
||||
listener.disable();
|
||||
|
||||
listener = null;
|
||||
}
|
||||
private void launchGame() {
|
||||
Intent intent = new Intent(this, GameActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
this.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
private void updateCutouts() {
|
||||
this.displayInsets[0] = 0;
|
||||
this.displayInsets[1] = 0;
|
||||
this.displayInsets[2] = 0;
|
||||
this.displayInsets[3] = 0;
|
||||
|
||||
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (insets != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], insets.getStableInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[1], insets.getStableInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[2], insets.getStableInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[3], insets.getStableInsetBottom());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.P) {
|
||||
android.view.DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
if (cutout != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], cutout.getSafeInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[0], cutout.getSafeInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[0], cutout.getSafeInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[0], cutout.getSafeInsetBottom());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void messageBox(String title, String message, Runnable callback) {
|
||||
private void messageBox(String title, String message, Runnable yesCallback, Runnable noCallback) {
|
||||
this.runOnUiThread(() -> {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(message);
|
||||
alert.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||
callback.run();
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.no, (dialog, whichButton) -> {
|
||||
// hide
|
||||
});
|
||||
alert.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> yesCallback.run());
|
||||
alert.setNegativeButton(android.R.string.no, (dialog, whichButton) -> noCallback.run());
|
||||
alert.setCancelable(false);
|
||||
alert.show();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -21,7 +22,7 @@
|
|||
android:id="@+id/txtTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Downloading game data"
|
||||
android:text="@string/download_title"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
|
||||
|
||||
|
@ -39,4 +40,4 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 34 KiB |
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#8DA5F8</color>
|
||||
<color name="ic_launcher_background">#FD7F44</color>
|
||||
</resources>
|
|
@ -1,4 +1,21 @@
|
|||
<resources>
|
||||
<string name="app_name">doukutsu-rs</string>
|
||||
<string name="document_provider_name">doukutsu-rs game data</string>
|
||||
<string name="app_name" translatable="false">doukutsu-rs</string>
|
||||
<string name="app_description">A faithful and open-source remake of Cave Story engine written in Rust.</string>
|
||||
|
||||
<string name="missing_data_title">Missing data files</string>
|
||||
<string name="missing_data_desc">No data files found, would you like to download them?</string>
|
||||
|
||||
<string name="download_title">Downloading game data</string>
|
||||
<string name="download_status_error_http">Bad HTTP response code: %d</string>
|
||||
<!-- Downloading {entry_name}… {progress}% ({downloaded_size}/{total_size} KiB, {speed} KiB/s) -->
|
||||
<string name="download_status_downloading">Downloading %1$s… %2$d%% (%3$d/%4$d KiB, %5$d KiB/s)</string>
|
||||
<string name="download_status_downloading_null">Downloading %1$s… --%% (%2$d KiB, %3$d KiB/s)</string>
|
||||
<string name="download_status_unpacking">Unpacking: %s</string>
|
||||
<string name="download_status_done">Done!</string>
|
||||
|
||||
<!-- Look {entry_name} on 9th line -->
|
||||
<string name="download_entries_base">base game files</string>
|
||||
|
||||
<string name="dir_not_found">Dir not found</string>
|
||||
<string name="no_app_found_to_open_dir">No app found to open dir</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.2.0"
|
||||
classpath "com.android.tools.build:gradle:7.3.1"
|
||||
classpath "gradle.plugin.com.github.willir.rust:plugin:0.3.4"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
@ -19,7 +19,7 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Wed Feb 17 23:16:31 CET 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
34
build.rs
|
@ -1,7 +1,7 @@
|
|||
use std::env;
|
||||
|
||||
// #[cfg(feature = "generate-gl")]
|
||||
// use gl_generator::{Api, Fallbacks, Profile, Registry};
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winres;
|
||||
|
||||
fn main() {
|
||||
// let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
|
||||
|
@ -9,18 +9,26 @@ fn main() {
|
|||
let is_android = cfg!(target_os = "android") || (cfg!(target_os = "linux") && target.contains("android")); // hack
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
//
|
||||
// #[cfg(feature = "generate-gl")]
|
||||
// {
|
||||
// let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap();
|
||||
//
|
||||
// Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, [])
|
||||
// .write_bindings(gl_generator::StructGenerator, &mut file)
|
||||
// .unwrap();
|
||||
// }
|
||||
|
||||
if target.contains("macos") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.12")
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("res/sue.ico");
|
||||
res.compile().unwrap();
|
||||
|
||||
if target.contains("i686") {
|
||||
// yet another hack
|
||||
println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
|
||||
println!("cargo:rustc-link-lib=shlwapi");
|
||||
}
|
||||
}
|
||||
|
||||
if target.contains("darwin") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15");
|
||||
println!("cargo:rustc-link-arg=-weak_framework");
|
||||
println!("cargo:rustc-link-arg=GameController");
|
||||
println!("cargo:rustc-link-arg=-weak_framework");
|
||||
println!("cargo:rustc-link-arg=CoreHaptics");
|
||||
}
|
||||
|
||||
if is_android {
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
[package]
|
||||
name = "drsandroid"
|
||||
description = "doukutsu-rs targeted for Android"
|
||||
version = "0.1.0"
|
||||
authors = ["Alula"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "off"
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ndk = "0.3"
|
||||
ndk-glue = "0.3"
|
||||
ndk-sys = "0.2"
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin"] }
|
||||
ndk = "0.7"
|
||||
ndk-glue = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin", "webbrowser"] }
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#[cfg(target_os = "android")]
|
||||
#[cfg_attr(target_os = "android", ndk_glue::main())]
|
||||
pub fn android_main() {
|
||||
let options = doukutsu_rs::LaunchOptions { server_mode: false };
|
||||
let resource_dir = std::path::PathBuf::from(ndk_glue::native_activity().internal_data_path().to_string_lossy().to_string());
|
||||
|
||||
doukutsu_rs::init(options).unwrap();
|
||||
std::env::set_current_dir(&resource_dir).unwrap();
|
||||
|
||||
let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false };
|
||||
|
||||
doukutsu_rs::game::init(options).unwrap();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
[build]
|
||||
target = ["aarch64-nintendo-switch"]
|
||||
|
||||
[target.aarch64-nintendo-switch]
|
||||
cc = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc"}
|
||||
cxx = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++"}
|
||||
ar = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-ar"
|
||||
ranlib = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-ranlib"}
|
||||
linker = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc"
|
|
@ -0,0 +1,31 @@
|
|||
[package]
|
||||
name = "drshorizon"
|
||||
description = "doukutsu-rs targeted for Nintendo Switch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
lto = "off"
|
||||
overflow-checks = false
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
incremental = true
|
||||
|
||||
[profile.dev.package."doukutsu-rs"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[dependencies]
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-horizon"] }
|
|
@ -0,0 +1,3 @@
|
|||
Experimental.
|
||||
|
||||
ld script and .specs taken from devkitPro
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"arch": "aarch64",
|
||||
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
||||
"dynamic-linking": true,
|
||||
"disable-redzone": true,
|
||||
"env": "newlib",
|
||||
"executables": true,
|
||||
"exe-suffix": ".elf",
|
||||
"features": "+a57,+strict-align,+crc,+crypto",
|
||||
"has-rpath": false,
|
||||
"has-thread-local": false,
|
||||
"linker": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc",
|
||||
"linker-flavor": "gcc",
|
||||
"llvm-target": "aarch64-unknown-none",
|
||||
"max-atomic-width": 128,
|
||||
"no-default-libraries": false,
|
||||
"os": "horizon",
|
||||
"panic-strategy": "abort",
|
||||
"position-independent-executables": true,
|
||||
"pre-link-args": {
|
||||
"gcc": [
|
||||
"-fPIC",
|
||||
"-specs",
|
||||
"aarch64_nintendo_switch.specs",
|
||||
"-T",
|
||||
"aarch64_nintendo_switch.ld",
|
||||
"-L",
|
||||
"/opt/devkitpro/portlibs/switch/lib",
|
||||
"-L",
|
||||
"/opt/devkitpro/libnx/lib",
|
||||
"-I",
|
||||
"/opt/devkitpro/libnx/include"
|
||||
]
|
||||
},
|
||||
"relocation-model": "pic",
|
||||
"requires-uwtable": true,
|
||||
"target-c-int-width": "32",
|
||||
"target-endian": "little",
|
||||
"target-family": [
|
||||
"unix"
|
||||
],
|
||||
"target-pointer-width": "64",
|
||||
"trap-unreachable": true,
|
||||
"vendor": "nintendo"
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
OUTPUT_ARCH(aarch64)
|
||||
ENTRY(_start)
|
||||
|
||||
PHDRS
|
||||
{
|
||||
code PT_LOAD FLAGS(5) /* Read | Execute */;
|
||||
rodata PT_LOAD FLAGS(4) /* Read */;
|
||||
data PT_LOAD FLAGS(6) /* Read | Write */;
|
||||
dyn PT_DYNAMIC;
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* =========== CODE section =========== */
|
||||
PROVIDE(__start__ = 0x0);
|
||||
. = __start__;
|
||||
__code_start = . ;
|
||||
|
||||
.text :
|
||||
{
|
||||
HIDDEN(__text_start = .);
|
||||
KEEP (*(.crt0))
|
||||
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
|
||||
*(.text.exit .text.exit.*)
|
||||
*(.text.startup .text.startup.*)
|
||||
*(.text.hot .text.hot.*)
|
||||
*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
.init :
|
||||
{
|
||||
KEEP( *(.init) )
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
.plt :
|
||||
{
|
||||
*(.plt)
|
||||
*(.iplt)
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
.fini :
|
||||
{
|
||||
KEEP( *(.fini) )
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
/* =========== RODATA section =========== */
|
||||
. = ALIGN(0x1000);
|
||||
__rodata_start = . ;
|
||||
|
||||
.nx-module-name : { KEEP (*(.nx-module-name)) } :rodata
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||
. = ALIGN(8);
|
||||
} :rodata
|
||||
|
||||
.eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } :rodata
|
||||
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } :rodata
|
||||
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } :rodata
|
||||
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } : rodata
|
||||
|
||||
HIDDEN(__dynamic_start = .);
|
||||
.dynamic : { *(.dynamic) } :rodata :dyn
|
||||
.dynsym : { *(.dynsym) } :rodata
|
||||
.dynstr : { *(.dynstr) } :rodata
|
||||
.rela.dyn : { *(.rela.*) } :rodata
|
||||
.interp : { *(.interp) } :rodata
|
||||
.hash : { *(.hash) } :rodata
|
||||
.gnu.hash : { *(.gnu.hash) } :rodata
|
||||
.gnu.version : { *(.gnu.version) } :rodata
|
||||
.gnu.version_d : { *(.gnu.version_d) } :rodata
|
||||
.gnu.version_r : { *(.gnu.version_r) } :rodata
|
||||
.note.gnu.build-id : { *(.note.gnu.build-id) } :rodata
|
||||
|
||||
/* =========== DATA section =========== */
|
||||
. = ALIGN(0x1000);
|
||||
__data_start = . ;
|
||||
|
||||
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } :data
|
||||
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } :data
|
||||
.gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } : data
|
||||
.exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } :data
|
||||
|
||||
.tdata ALIGN(8) :
|
||||
{
|
||||
__tdata_lma = .;
|
||||
*(.tdata .tdata.* .gnu.linkonce.td.*)
|
||||
. = ALIGN(8);
|
||||
__tdata_lma_end = .;
|
||||
} :data
|
||||
|
||||
.tbss ALIGN(8) :
|
||||
{
|
||||
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
|
||||
. = ALIGN(8);
|
||||
} :data
|
||||
|
||||
.preinit_array ALIGN(8) :
|
||||
{
|
||||
PROVIDE (__preinit_array_start = .);
|
||||
KEEP (*(.preinit_array))
|
||||
PROVIDE (__preinit_array_end = .);
|
||||
} :data
|
||||
|
||||
.init_array ALIGN(8) :
|
||||
{
|
||||
PROVIDE (__init_array_start = .);
|
||||
KEEP( *(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)) )
|
||||
KEEP( *(.init_array .ctors) )
|
||||
PROVIDE (__init_array_end = .);
|
||||
} :data
|
||||
|
||||
.fini_array ALIGN(8) :
|
||||
{
|
||||
PROVIDE (__fini_array_start = .);
|
||||
KEEP( *(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)) )
|
||||
KEEP( *(.fini_array .dtors) )
|
||||
PROVIDE (__fini_array_end = .);
|
||||
} :data
|
||||
|
||||
__got_start__ = .;
|
||||
|
||||
.got : { *(.got) *(.igot) } :data
|
||||
.got.plt : { *(.got.plt) *(.igot.plt) } :data
|
||||
|
||||
__got_end__ = .;
|
||||
|
||||
.data ALIGN(8) :
|
||||
{
|
||||
*(.data .data.* .gnu.linkonce.d.*)
|
||||
SORT(CONSTRUCTORS)
|
||||
} :data
|
||||
|
||||
__bss_start__ = .;
|
||||
.bss ALIGN(8) :
|
||||
{
|
||||
HIDDEN(__bss_start = .);
|
||||
*(.dynbss)
|
||||
*(.bss .bss.* .gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(8);
|
||||
|
||||
/* Reserve space for the TLS segment of the main thread */
|
||||
__tls_start = .;
|
||||
. += + SIZEOF(.tdata) + SIZEOF(.tbss);
|
||||
__tls_end = .;
|
||||
HIDDEN(__bss_end = .);
|
||||
} :data
|
||||
__bss_end__ = .;
|
||||
|
||||
__end__ = ABSOLUTE(.) ;
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
__argdata__ = ABSOLUTE(.) ;
|
||||
|
||||
/* ==================
|
||||
==== Metadata ====
|
||||
================== */
|
||||
|
||||
/* Discard sections that difficult post-processing */
|
||||
/DISCARD/ : { *(.group .comment .note) }
|
||||
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
|
||||
/* DWARF debug sections.
|
||||
Symbols in the DWARF debugging sections are relative to the beginning
|
||||
of the section so we begin them at 0. */
|
||||
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
%rename link old_link
|
||||
|
||||
*link:
|
||||
%(old_link) -pie --no-dynamic-linker --spare-dynamic-tags=0 -z text -z nodynamic-undefined-weak --build-id=sha1 --nx-module-name
|
||||
|
||||
*startfile:
|
||||
crti%O%s crtbegin%O%s --require-defined=main
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
println!("cargo:rustc-link-lib=dylib=nx");
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
DARK_GRAY=$(tput setaf 8)
|
||||
YELLOW=$(tput bold)$(tput setaf 3)
|
||||
RESET=$(tput sgr0)
|
||||
|
||||
function message() {
|
||||
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
|
||||
}
|
||||
|
||||
message "Compiling shaders..."
|
||||
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
|
||||
|
||||
message "Building crate..."
|
||||
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json
|
||||
|
||||
rm -f target/aarch64-nintendo-switch/debug/drshorizon.nro
|
||||
rm -f target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "Creating NACP..."
|
||||
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.101.0' target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "Running elf2nro..."
|
||||
elf2nro target/aarch64-nintendo-switch/debug/drshorizon.elf target/aarch64-nintendo-switch/debug/drshorizon.nro \
|
||||
--icon=../res/nx_icon.jpg \
|
||||
--nacp=target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "done!"
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
DARK_GRAY=$(tput setaf 8)
|
||||
YELLOW=$(tput bold)$(tput setaf 3)
|
||||
RESET=$(tput sgr0)
|
||||
|
||||
function message() {
|
||||
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
|
||||
}
|
||||
|
||||
message "Compiling shaders..."
|
||||
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
|
||||
|
||||
message "Building crate..."
|
||||
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json --release
|
||||
|
||||
rm -f target/aarch64-nintendo-switch/release/drshorizon.nro
|
||||
rm -f target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "Creating NACP..."
|
||||
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.101.0' target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "Running elf2nro..."
|
||||
elf2nro target/aarch64-nintendo-switch/release/drshorizon.elf target/aarch64-nintendo-switch/release/drshorizon.nro \
|
||||
--icon=../res/nx_icon.jpg \
|
||||
--nacp=target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "done!"
|
|
@ -0,0 +1,47 @@
|
|||
//#![feature(restricted_std)]
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PrintConsole {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ApmCpuBoostMode {
|
||||
Normal = 0,
|
||||
FastLoad = 1,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn consoleInit(unk: *mut PrintConsole) -> *mut PrintConsole;
|
||||
fn consoleUpdate(unk: *mut PrintConsole);
|
||||
|
||||
fn socketInitialize(unk: *const std::ffi::c_void) -> u32;
|
||||
fn nxlinkConnectToHost(redir_stdout: bool, redir_stderr: bool) -> i32;
|
||||
|
||||
fn appletSetCpuBoostMode(mode: ApmCpuBoostMode) -> u32;
|
||||
|
||||
static __text_start: u32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
// if socketInitialize(std::ptr::null()) == 0 {
|
||||
// nxlinkConnectToHost(true, true);
|
||||
// }
|
||||
|
||||
// appletSetCpuBoostMode(ApmCpuBoostMode::FastLoad);
|
||||
|
||||
std::env::set_var("RUST_BACKTRACE", "full");
|
||||
|
||||
println!("__text_start = {:#x}", (&__text_start) as *const _ as usize);
|
||||
|
||||
let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false };
|
||||
let result = doukutsu_rs::game::init(options);
|
||||
|
||||
if let Err(e) = result {
|
||||
println!("Initialization error: {}", e);
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// <reference types="node" />
|
||||
|
||||
const readline = require('readline');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
terminal: false
|
||||
});
|
||||
|
||||
let textStart = 0;
|
||||
const textStartRegex = /__text_start = 0x([0-9a-f]+)/i;
|
||||
let symbolize = false;
|
||||
|
||||
if (process.argv.length <= 2) {
|
||||
console.error('Usage: node symbolize.js <path to ELF file>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const elfPath = process.argv[2];
|
||||
|
||||
rl.on('line', (line) => {
|
||||
if (textStart === 0) {
|
||||
const match = textStartRegex.exec(line);
|
||||
if (match) {
|
||||
textStart = parseInt(match[1], 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.includes("stack backtrace:")) {
|
||||
symbolize = true;
|
||||
}
|
||||
|
||||
if (symbolize) {
|
||||
const match = /0x([0-9a-f]+) - \<unknown\>/.exec(line);
|
||||
if (match) {
|
||||
const addr = parseInt(match[1], 16);
|
||||
const relative = addr - textStart;
|
||||
// run addr2line on the address
|
||||
const addr2line = childProcess.spawnSync('addr2line', ['-e', elfPath, '-j', '.text', '-f', '-C', '0x' + relative.toString(16)]);
|
||||
if (addr2line.status === 0) {
|
||||
const output = addr2line.stdout.toString();
|
||||
const lines = output.split('\n');
|
||||
const [func, file] = lines;
|
||||
line = line.replace(match[0], `0x${addr.toString(16)} - ${func} (${file})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(line);
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"title": "JSON schema for texture sizes",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sizes": {
|
||||
"description": "List of textures and their sizes to prevent accidental scaling being applied",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"description": "Location and Width/Height of texture",
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 65535
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 65535
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
curl -o ./src/data/builtin/gamecontrollerdb.txt https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 764 KiB |
|
@ -1,4 +1,4 @@
|
|||
edition = "2018"
|
||||
edition = "2021"
|
||||
max_width = 120
|
||||
use_small_heuristics = "Max"
|
||||
newline_style = "Unix"
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BmChar {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub xoffset: i16,
|
||||
pub yoffset: i16,
|
||||
pub xadvance: i16,
|
||||
pub page: u8,
|
||||
pub chnl: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BMFont {
|
||||
pub pages: u16,
|
||||
pub font_size: i16,
|
||||
pub line_height: u16,
|
||||
pub base: u16,
|
||||
pub chars: HashMap<char, BmChar>,
|
||||
}
|
||||
|
||||
const MAGIC: [u8; 4] = [b'B', b'M', b'F', 3];
|
||||
|
||||
impl BMFont {
|
||||
pub fn load_from<R: io::Read + io::Seek>(mut data: R) -> GameResult<Self> {
|
||||
let mut magic = [0u8; 4];
|
||||
let mut pages = 0u16;
|
||||
let mut chars = HashMap::with_capacity(128);
|
||||
let mut font_size = 0i16;
|
||||
let mut line_height = 0u16;
|
||||
let mut base = 0u16;
|
||||
|
||||
data.read_exact(&mut magic)?;
|
||||
|
||||
if magic != MAGIC {
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
while let Ok(block_type) = data.read_u8() {
|
||||
let length = data.read_u32::<LE>()?;
|
||||
match block_type {
|
||||
1 => {
|
||||
font_size = data.read_i16::<LE>()?;
|
||||
|
||||
data.seek(io::SeekFrom::Current(length as i64 - 2))?;
|
||||
}
|
||||
2 => {
|
||||
line_height = data.read_u16::<LE>()?;
|
||||
base = data.read_u16::<LE>()?;
|
||||
data.seek(io::SeekFrom::Current(4))?;
|
||||
pages = data.read_u16::<LE>()?;
|
||||
|
||||
data.seek(io::SeekFrom::Current(length as i64 - 10))?;
|
||||
}
|
||||
3 | 5 => {
|
||||
data.seek(io::SeekFrom::Current(length as i64))?;
|
||||
}
|
||||
4 => {
|
||||
let count = length / 20;
|
||||
for _ in 0..count {
|
||||
let id = data.read_u32::<LE>()?;
|
||||
let x = data.read_u16::<LE>()?;
|
||||
let y = data.read_u16::<LE>()?;
|
||||
let width = data.read_u16::<LE>()?;
|
||||
let height = data.read_u16::<LE>()?;
|
||||
let xoffset = data.read_i16::<LE>()?;
|
||||
let yoffset = data.read_i16::<LE>()?;
|
||||
let xadvance = data.read_i16::<LE>()?;
|
||||
let page = data.read_u8()?;
|
||||
let chnl = data.read_u8()?;
|
||||
|
||||
if let Some(chr) = std::char::from_u32(id) {
|
||||
chars.insert(chr, BmChar { x, y, width, height, xoffset, yoffset, xadvance, page, chnl });
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceLoadError("Unknown block type.".to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { pages, font_size, line_height, base, chars })
|
||||
}
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::bmfont::BMFont;
|
||||
use crate::common::{Rect, FILE_TYPES};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::texture_set::TextureSet;
|
||||
|
||||
pub struct BMFontRenderer {
|
||||
font: BMFont,
|
||||
pages: Vec<String>,
|
||||
}
|
||||
|
||||
impl BMFontRenderer {
|
||||
pub fn load(root: &str, desc_path: &str, ctx: &mut Context) -> GameResult<BMFontRenderer> {
|
||||
let root = PathBuf::from(root);
|
||||
let full_path = &root.join(PathBuf::from(desc_path));
|
||||
let desc_stem =
|
||||
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
|
||||
let stem = full_path.parent().unwrap_or(full_path).join(desc_stem);
|
||||
|
||||
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
|
||||
let mut pages = Vec::new();
|
||||
|
||||
let (zeros, _, _) = FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
|
||||
.find(|(_, _, path)| filesystem::exists(ctx, &path))
|
||||
.or_else(|| {
|
||||
FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| (2, ext, format!("{}_00{}", stem.to_string_lossy(), ext)))
|
||||
.find(|(_, _, path)| filesystem::exists(ctx, &path))
|
||||
})
|
||||
.ok_or_else(|| ResourceLoadError(format!("Cannot find glyph atlas 0 for font: {:?}", desc_path)))?;
|
||||
|
||||
for i in 0..font.pages {
|
||||
let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros);
|
||||
|
||||
pages.push(page_path);
|
||||
}
|
||||
|
||||
Ok(Self { font, pages })
|
||||
}
|
||||
|
||||
pub fn line_height(&self, constants: &EngineConstants) -> f32 {
|
||||
self.font.line_height as f32 * constants.font_scale
|
||||
}
|
||||
|
||||
pub fn text_width<I: Iterator<Item = char>>(&self, iter: I, constants: &EngineConstants) -> f32 {
|
||||
let mut offset_x = 0.0;
|
||||
|
||||
for chr in iter {
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
offset_x += ((glyph.width as f32 + glyph.xoffset as f32) * constants.font_scale).floor()
|
||||
+ if chr != ' ' { 1.0 } else { constants.font_space_offset };
|
||||
}
|
||||
}
|
||||
|
||||
offset_x
|
||||
}
|
||||
|
||||
pub fn draw_text<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_text_with_shadow<I: Iterator<Item = char> + Clone>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text(iter.clone(), x + 1.0, y + 1.0, (0, 0, 0, 150), constants, texture_set, ctx)?;
|
||||
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_colored_text_with_shadow_scaled<I: Iterator<Item = char> + Clone>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
scale: f32,
|
||||
color: (u8, u8, u8, u8),
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text_scaled(
|
||||
iter.clone(),
|
||||
x + scale,
|
||||
y + scale,
|
||||
scale,
|
||||
(0, 0, 0, 150),
|
||||
constants,
|
||||
texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_colored_text_scaled<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
scale: f32,
|
||||
color: (u8, u8, u8, u8),
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
if self.pages.len() == 1 {
|
||||
let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?;
|
||||
let mut offset_x = x;
|
||||
|
||||
for chr in iter {
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
batch.add_rect_scaled_tinted(
|
||||
offset_x,
|
||||
y + (glyph.yoffset as f32 * constants.font_scale).floor(),
|
||||
color,
|
||||
constants.font_scale,
|
||||
constants.font_scale,
|
||||
&Rect::new_size(glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16),
|
||||
);
|
||||
|
||||
offset_x += ((glyph.width as f32 + glyph.xoffset as f32) * constants.font_scale).floor()
|
||||
+ if chr != ' ' { 1.0 } else { constants.font_space_offset };
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
} else {
|
||||
let mut pages = HashSet::new();
|
||||
let mut chars = Vec::new();
|
||||
|
||||
for chr in iter {
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
pages.insert(glyph.page);
|
||||
chars.push((chr, glyph));
|
||||
}
|
||||
}
|
||||
|
||||
for page in pages {
|
||||
let page_tex = if let Some(p) = self.pages.get(page as usize) {
|
||||
p
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?;
|
||||
let mut offset_x = x;
|
||||
|
||||
for (chr, glyph) in chars.iter() {
|
||||
if glyph.page == page {
|
||||
batch.add_rect_scaled_tinted(
|
||||
offset_x,
|
||||
y + (glyph.yoffset as f32 * constants.font_scale).floor(),
|
||||
color,
|
||||
constants.font_scale * scale,
|
||||
constants.font_scale * scale,
|
||||
&Rect::new_size(glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16),
|
||||
);
|
||||
}
|
||||
|
||||
offset_x += scale
|
||||
* (((glyph.width as f32 + glyph.xoffset as f32) * constants.font_scale).floor()
|
||||
+ if *chr != ' ' { 1.0 } else { constants.font_space_offset });
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_colored_text<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
color: (u8, u8, u8, u8),
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text_scaled(iter, x, y, 1.0, color, constants, texture_set, ctx)
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.0 KiB |
|
@ -1,8 +0,0 @@
|
|||
#version 330
|
||||
|
||||
varying vec4 color;
|
||||
varying vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
frag_color = color;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
#version 150 core
|
||||
|
||||
uniform sampler2D t_Texture;
|
||||
in vec2 v_Uv;
|
||||
in vec4 v_Color;
|
||||
out vec4 Target0;
|
||||
|
||||
layout (std140) uniform Globals {
|
||||
mat4 u_MVP;
|
||||
};
|
||||
|
||||
layout (std140) uniform WaterShaderParams {
|
||||
vec2 u_Resolution;
|
||||
vec2 u_FramePos;
|
||||
float u_Tick;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec2 wave = v_Uv;
|
||||
wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x;
|
||||
wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y;
|
||||
float off = 0.4 / u_Resolution.y;
|
||||
vec4 color = texture(t_Texture, wave);
|
||||
color.r = texture(t_Texture, wave + off).r;
|
||||
color.b = texture(t_Texture, wave - off).b;
|
||||
|
||||
Target0 = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7);
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
#version 300 es
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D t_Texture;
|
||||
in vec2 v_Uv;
|
||||
in vec4 v_Color;
|
||||
out vec4 Target0;
|
||||
|
||||
layout (std140) uniform Globals {
|
||||
mat4 u_MVP;
|
||||
};
|
||||
|
||||
layout (std140) uniform WaterShaderParams {
|
||||
vec2 u_Resolution;
|
||||
vec2 u_FramePos;
|
||||
float u_Tick;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec2 wave = v_Uv;
|
||||
wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x;
|
||||
wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y;
|
||||
float off = 0.4 / u_Resolution.y;
|
||||
vec4 color = texture(t_Texture, wave);
|
||||
color.r = texture(t_Texture, wave + off).r;
|
||||
color.b = texture(t_Texture, wave - off).b;
|
||||
|
||||
Target0 = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7);
|
||||
}
|
||||
|
||||
/*
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D t_Texture;
|
||||
varying vec2 v_Uv;
|
||||
varying vec4 v_Color;
|
||||
|
||||
uniform mat4 u_MVP;
|
||||
uniform vec2 u_Resolution;
|
||||
uniform vec2 u_FramePos;
|
||||
uniform float u_Tick;
|
||||
|
||||
void main() {
|
||||
vec2 wave = v_Uv;
|
||||
wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x;
|
||||
wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y;
|
||||
float off = 0.4 / u_Resolution.y;
|
||||
vec4 color = texture2D(t_Texture, wave);
|
||||
color.r = texture2D(t_Texture, wave + off).r;
|
||||
color.b = texture2D(t_Texture, wave - off).b;
|
||||
|
||||
gl_FragColor = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7);
|
||||
}*/
|
Before Width: | Height: | Size: 789 B |
|
@ -1,254 +0,0 @@
|
|||
use std::{fmt, io};
|
||||
use std::fmt::Debug;
|
||||
use std::io::Cursor;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use crate::framework::error::GameError::FilesystemError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||
|
||||
impl BuiltinFile {
|
||||
pub fn from(buf: &'static [u8]) -> Box<dyn VFile> {
|
||||
Box::new(BuiltinFile(Cursor::new(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for BuiltinFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for BuiltinFile {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
self.0.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for BuiltinFile {
|
||||
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
}
|
||||
|
||||
struct BuiltinMetadata {
|
||||
is_dir: bool,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl VMetadata for BuiltinMetadata {
|
||||
fn is_dir(&self) -> bool {
|
||||
self.is_dir
|
||||
}
|
||||
|
||||
fn is_file(&self) -> bool {
|
||||
!self.is_dir
|
||||
}
|
||||
|
||||
fn len(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum FSNode {
|
||||
File(&'static str, &'static [u8]),
|
||||
Directory(&'static str, Vec<FSNode>),
|
||||
}
|
||||
|
||||
impl FSNode {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
FSNode::File(name, _) => { name }
|
||||
FSNode::Directory(name, _) => { name }
|
||||
}
|
||||
}
|
||||
|
||||
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => { Ok(BuiltinFile::from(buf)) }
|
||||
FSNode::Directory(name, _) => { Err(FilesystemError(format!("{} is a directory.", name))) }
|
||||
}
|
||||
}
|
||||
|
||||
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => {
|
||||
Box::new(BuiltinMetadata {
|
||||
is_dir: false,
|
||||
size: buf.len() as u64,
|
||||
})
|
||||
}
|
||||
FSNode::Directory(_, _) => {
|
||||
Box::new(BuiltinMetadata {
|
||||
is_dir: true,
|
||||
size: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuiltinFS {
|
||||
root: Vec<FSNode>,
|
||||
}
|
||||
|
||||
impl BuiltinFS {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root: vec![
|
||||
FSNode::Directory("builtin", vec![
|
||||
FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
||||
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
|
||||
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")),
|
||||
FSNode::File("organya-wavetable-doukutsu.bin", include_bytes!("builtin/organya-wavetable-doukutsu.bin")),
|
||||
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
|
||||
FSNode::Directory("shaders", vec![
|
||||
// FSNode::File("basic_150.vert.glsl", include_bytes!("builtin/shaders/basic_150.vert.glsl")),
|
||||
// FSNode::File("water_150.frag.glsl", include_bytes!("builtin/shaders/water_150.frag.glsl")),
|
||||
// FSNode::File("basic_es300.vert.glsl", include_bytes!("builtin/shaders/basic_es300.vert.glsl")),
|
||||
// FSNode::File("water_es300.frag.glsl", include_bytes!("builtin/shaders/water_es300.frag.glsl")),
|
||||
]),
|
||||
FSNode::Directory("lightmap", vec![
|
||||
FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png")),
|
||||
]),
|
||||
])
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node(&self, path: &Path) -> GameResult<FSNode> {
|
||||
let mut iter = path.components().peekable();
|
||||
|
||||
if let Some(Component::RootDir) = iter.next() {
|
||||
let mut curr_dir = &self.root;
|
||||
|
||||
if iter.peek().is_none() {
|
||||
return Ok(FSNode::Directory("", self.root.clone()));
|
||||
}
|
||||
|
||||
while let Some(comp) = iter.next() {
|
||||
let comp_name = comp.as_os_str().to_string_lossy();
|
||||
|
||||
for file in curr_dir {
|
||||
match file {
|
||||
FSNode::File(name, _) if comp_name.eq(name) => {
|
||||
return if iter.peek().is_some() {
|
||||
Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path)))
|
||||
} else {
|
||||
Ok(file.clone())
|
||||
};
|
||||
}
|
||||
FSNode::Directory(name, contents) if comp_name.eq(name) => {
|
||||
if iter.peek().is_some() {
|
||||
curr_dir = contents;
|
||||
break;
|
||||
} else {
|
||||
return Ok(file.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(FilesystemError("Path must be absolute.".to_string()));
|
||||
}
|
||||
|
||||
Err(FilesystemError("File not found.".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BuiltinFS {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "<BuiltinFS>")
|
||||
}
|
||||
}
|
||||
|
||||
impl VFS for BuiltinFS {
|
||||
fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult<Box<dyn VFile>> {
|
||||
if open_options.write || open_options.create || open_options.append || open_options.truncate {
|
||||
let msg = format!(
|
||||
"Cannot alter file {:?} in root {:?}, filesystem read-only",
|
||||
path, self
|
||||
);
|
||||
return Err(FilesystemError(msg));
|
||||
}
|
||||
|
||||
self.get_node(path)?.to_file()
|
||||
}
|
||||
|
||||
fn mkdir(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to make directory {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn rm(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to remove file {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn rmrf(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to remove file/dir {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn exists(&self, path: &Path) -> bool {
|
||||
self.get_node(path).is_ok()
|
||||
}
|
||||
|
||||
fn metadata(&self, path: &Path) -> GameResult<Box<dyn VMetadata>> {
|
||||
self.get_node(path).map(|v| v.to_metadata())
|
||||
}
|
||||
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||
match self.get_node(path) {
|
||||
Ok(FSNode::Directory(_, contents)) => {
|
||||
let mut vec = Vec::new();
|
||||
for node in contents {
|
||||
vec.push(Ok(PathBuf::from(node.get_name())))
|
||||
}
|
||||
|
||||
Ok(Box::new(vec.into_iter()))
|
||||
}
|
||||
Ok(FSNode::File(_, _)) => {
|
||||
Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path)))
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_path_buf(&self) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builtin_fs() {
|
||||
let fs = BuiltinFS {
|
||||
root: vec![
|
||||
FSNode::File("test.txt", &[]),
|
||||
FSNode::Directory("memes", vec![
|
||||
FSNode::File("nothing.txt", &[]),
|
||||
FSNode::Directory("secret stuff", vec![
|
||||
FSNode::File("passwords.txt", b"12345678"),
|
||||
]),
|
||||
]),
|
||||
FSNode::File("test2.txt", &[]),
|
||||
],
|
||||
};
|
||||
|
||||
println!("{:?}", fs.get_node(Path::new("/")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/test.txt")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap());
|
||||
}
|
104
src/common.rs
|
@ -1,13 +1,14 @@
|
|||
use std::fmt;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::{abs, Num};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::de::{SeqAccess, Visitor};
|
||||
use serde::ser::SerializeTupleStruct;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::bitfield;
|
||||
use crate::texture_set::G_MAG;
|
||||
use crate::graphics::texture_set::G_MAG;
|
||||
|
||||
/// Multiply cave story degrees (0-255, which corresponds to 0°-360°) with this constant to get
|
||||
/// respective value in radians.
|
||||
|
@ -38,15 +39,15 @@ bitfield! {
|
|||
pub hit_right_slope, set_hit_right_slope: 4;
|
||||
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
|
||||
pub hit_left_slope, set_hit_left_slope: 5;
|
||||
/// Unknown purpose (corresponds to flag & 0x40)
|
||||
pub flag_x40, set_flag_x40: 6;
|
||||
/// Unknown purpose (corresponds to flag & 0x80)
|
||||
pub flag_x80, set_flag_x80: 7;
|
||||
/// Used only in bullet code, set if a bullet hits upper right slope (corresponds to flag & 0x40)
|
||||
pub hit_upper_right_slope, set_hit_upper_right_slope: 6;
|
||||
/// Used only in bullet code, set if a bullet hits upper left slope (corresponds to flag & 0x80)
|
||||
pub hit_upper_left_slope, set_hit_upper_left_slope: 7;
|
||||
/// Set if entity is in water. (corresponds to flag & 0x100)
|
||||
pub in_water, set_in_water: 8;
|
||||
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
||||
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
||||
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
||||
pub bloody_droplets, set_bloody_droplets: 11; // 0x800
|
||||
pub force_left, set_force_left: 12; // 0x1000
|
||||
pub force_up, set_force_up: 13; // 0x2000
|
||||
pub force_right, set_force_right: 14; // 0x4000
|
||||
|
@ -123,7 +124,10 @@ bitfield! {
|
|||
pub control_enabled, set_control_enabled: 1; // 0x02
|
||||
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
|
||||
pub credits_running, set_credits_running: 3; // 0x08
|
||||
|
||||
// cs+ switch specific, according to peri:
|
||||
// Flag 0x10 prevents the OK button from restarting the item description event (resets when the cursor is moved)
|
||||
// (it does not prevent the cancel button from exiting the inventory, however)
|
||||
pub ok_button_disabled, set_ok_button_disabled: 4; // 0x10
|
||||
// engine specific flags
|
||||
pub friendly_fire, set_friendly_fire: 14;
|
||||
}
|
||||
|
@ -131,16 +135,21 @@ bitfield! {
|
|||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct BulletFlag(u16);
|
||||
pub struct BulletFlag(u8);
|
||||
impl Debug;
|
||||
pub flag_x01, set_flag_x01: 0; // 0x01
|
||||
pub flag_x02, set_flag_x02: 1; // 0x02
|
||||
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
|
||||
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
|
||||
pub flag_x10, set_flag_x10: 4; // 0x10
|
||||
pub flag_x20, set_flag_x20: 5; // 0x20
|
||||
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
|
||||
pub flag_x80, set_flag_x80: 7; // 0x80
|
||||
pub flag_x01, set_flag_x01: 0; // 0x01, nowhere in code?
|
||||
pub flag_x02, set_flag_x02: 1; // 0x02, nowhere in code?
|
||||
/// Corresponds to flag & 0x04. If set, bullet will pass through blocks.
|
||||
pub no_collision_checks, set_no_collision_checks: 2;
|
||||
/// Corresponds to flag & 0x08. IF set, bullet will bounce off walls.
|
||||
pub bounce_from_walls, set_bounce_from_walls: 3;
|
||||
/// Corresponds to flag & 0x10. IF set, bullet will not produce projectile dissipation effect when it hits a NPC or boss.
|
||||
pub no_proj_dissipation, set_no_proj_dissipation: 4;
|
||||
/// Corresponds to flag & 0x20. If set, performs checks in block collision check procedure. Kills the bullet if flag 0x40 isn't set.
|
||||
pub check_block_hit, set_check_block_hit: 5;
|
||||
/// Corresponds to flag & 0x40. If set, bullet will destroy snack blocks on hit.
|
||||
pub can_destroy_snack, set_can_destroy_snack: 6;
|
||||
pub flag_x80, set_flag_x80: 7; // 0x80, nowhere in code?
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -154,7 +163,7 @@ pub enum FadeDirection {
|
|||
}
|
||||
|
||||
impl FadeDirection {
|
||||
pub fn from_int(val: usize) -> Option<FadeDirection> {
|
||||
pub const fn from_int(val: usize) -> Option<FadeDirection> {
|
||||
match val {
|
||||
0 => Some(FadeDirection::Left),
|
||||
1 => Some(FadeDirection::Up),
|
||||
|
@ -165,7 +174,7 @@ impl FadeDirection {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn opposite(&self) -> FadeDirection {
|
||||
pub const fn opposite(&self) -> FadeDirection {
|
||||
match self {
|
||||
FadeDirection::Left => FadeDirection::Right,
|
||||
FadeDirection::Up => FadeDirection::Down,
|
||||
|
@ -198,7 +207,7 @@ pub enum Direction {
|
|||
pub const FILE_TYPES: [&str; 3] = [".png", ".bmp", ".pbm"];
|
||||
|
||||
impl Direction {
|
||||
pub fn from_int(val: usize) -> Option<Direction> {
|
||||
pub const fn from_int(val: usize) -> Option<Direction> {
|
||||
match val {
|
||||
0 => Some(Direction::Left),
|
||||
1 => Some(Direction::Up),
|
||||
|
@ -208,7 +217,7 @@ impl Direction {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_int_facing(val: usize) -> Option<Direction> {
|
||||
pub const fn from_int_facing(val: usize) -> Option<Direction> {
|
||||
match val {
|
||||
0 => Some(Direction::Left),
|
||||
1 => Some(Direction::Up),
|
||||
|
@ -219,33 +228,33 @@ impl Direction {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn opposite(&self) -> Direction {
|
||||
pub const fn opposite(&self) -> Direction {
|
||||
match self {
|
||||
Direction::Left => Direction::Right,
|
||||
Direction::Up => Direction::Bottom,
|
||||
Direction::Right => Direction::Left,
|
||||
Direction::Bottom => Direction::Up,
|
||||
Direction::FacingPlayer => unreachable!(),
|
||||
Direction::FacingPlayer => Direction::FacingPlayer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector_x(&self) -> i32 {
|
||||
pub const fn vector_x(&self) -> i32 {
|
||||
match self {
|
||||
Direction::Left => -1,
|
||||
Direction::Up => 0,
|
||||
Direction::Right => 1,
|
||||
Direction::Bottom => 0,
|
||||
Direction::FacingPlayer => unreachable!(),
|
||||
Direction::FacingPlayer => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector_y(&self) -> i32 {
|
||||
pub const fn vector_y(&self) -> i32 {
|
||||
match self {
|
||||
Direction::Left => 0,
|
||||
Direction::Up => -1,
|
||||
Direction::Right => 0,
|
||||
Direction::Bottom => 1,
|
||||
Direction::FacingPlayer => unreachable!(),
|
||||
Direction::FacingPlayer => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -293,8 +302,8 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
|||
|
||||
impl<T: Num + PartialOrd + Copy + Serialize> Serialize for Rect<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_tuple_struct("Rect", 4)?;
|
||||
state.serialize_field(&self.left)?;
|
||||
|
@ -316,7 +325,7 @@ impl<T: Num + PartialOrd + Copy + Serialize> Default for Rect<T> {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! rect_deserialze {
|
||||
macro_rules! rect_deserialize {
|
||||
($num_type: ident) => {
|
||||
impl<'de> Deserialize<'de> for Rect<$num_type> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Rect<$num_type>, D::Error>
|
||||
|
@ -353,11 +362,11 @@ macro_rules! rect_deserialze {
|
|||
};
|
||||
}
|
||||
|
||||
rect_deserialze!(u8);
|
||||
rect_deserialze!(u16);
|
||||
rect_deserialze!(i32);
|
||||
rect_deserialze!(isize);
|
||||
rect_deserialze!(usize);
|
||||
rect_deserialize!(u8);
|
||||
rect_deserialize!(u16);
|
||||
rect_deserialize!(i32);
|
||||
rect_deserialize!(isize);
|
||||
rect_deserialize!(usize);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn fix9_scale(val: i32) -> f32 {
|
||||
|
@ -384,6 +393,11 @@ pub fn interpolate_fix9_scale(old_val: i32, val: i32, frame_delta: f64) -> f32 {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_timestamp() -> u64 {
|
||||
let now = SystemTime::now();
|
||||
now.duration_since(UNIX_EPOCH).unwrap().as_secs() as u64
|
||||
}
|
||||
|
||||
/// A RGBA color in the `sRGB` color space represented as `f32`'s in the range `[0.0-1.0]`
|
||||
///
|
||||
/// For convenience, [`WHITE`](constant.WHITE.html) and [`BLACK`](constant.BLACK.html) are provided.
|
||||
|
@ -541,18 +555,14 @@ impl<T> SliceExt for [T] {
|
|||
type Item = T;
|
||||
|
||||
fn get_two_mut(&mut self, a: usize, b: usize) -> Option<(&mut Self::Item, &mut Self::Item)> {
|
||||
if a == b {
|
||||
None
|
||||
} else {
|
||||
if a >= self.len() || b >= self.len() {
|
||||
None
|
||||
} else {
|
||||
unsafe {
|
||||
let ar = &mut *(self.get_unchecked_mut(a) as *mut _);
|
||||
let br = &mut *(self.get_unchecked_mut(b) as *mut _);
|
||||
Some((ar, br))
|
||||
}
|
||||
}
|
||||
if a == b || a >= self.len() || b >= self.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ar = &mut *(self.get_unchecked_mut(a) as *mut _);
|
||||
let br = &mut *(self.get_unchecked_mut(b) as *mut _);
|
||||
Some((ar, br))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
|
||||
pub struct Background {
|
||||
pub tick: usize,
|
||||
pub prev_tick: usize,
|
||||
}
|
||||
|
||||
impl Background {
|
||||
pub fn new() -> Self {
|
||||
Background { tick: 0, prev_tick: 0 }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> GameResult<()> {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_tick(&mut self) -> GameResult<()> {
|
||||
self.prev_tick = self.tick;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
frame: &Frame,
|
||||
textures: &StageTexturePaths,
|
||||
stage: &Stage,
|
||||
) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &textures.background)?;
|
||||
let scale = state.scale;
|
||||
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
match stage.data.background_type {
|
||||
BackgroundType::TiledStatic => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
|
||||
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
|
||||
let count_x = state.canvas_size.0 as i32 / bg_width + 1;
|
||||
let count_y = state.canvas_size.1 as i32 / bg_height + 1;
|
||||
|
||||
for y in -1..count_y {
|
||||
for x in -1..count_x {
|
||||
batch.add((x * bg_width) as f32, (y * bg_height) as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundType::TiledParallax | BackgroundType::Tiled | BackgroundType::Waterway => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
|
||||
let (off_x, off_y) = if stage.data.background_type == BackgroundType::Tiled {
|
||||
(frame_x % (batch.width() as f32), frame_y % (batch.height() as f32))
|
||||
} else {
|
||||
(
|
||||
((frame_x / 2.0 * scale).floor() / scale) % (batch.width() as f32),
|
||||
((frame_y / 2.0 * scale).floor() / scale) % (batch.height() as f32),
|
||||
)
|
||||
};
|
||||
|
||||
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
|
||||
let count_x = state.canvas_size.0 as i32 / bg_width + 2;
|
||||
let count_y = state.canvas_size.1 as i32 / bg_height + 2;
|
||||
|
||||
for y in -1..count_y {
|
||||
for x in -1..count_x {
|
||||
batch.add((x * bg_width) as f32 - off_x, (y * bg_height) as f32 - off_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundType::Water => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Black => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Scrolling => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
|
||||
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
|
||||
let offset_x = self.tick as f32 % (bg_width as f32 / 3.0);
|
||||
let interp_x = (offset_x * (1.0 - state.frame_time as f32)
|
||||
+ (offset_x + 1.0) * state.frame_time as f32)
|
||||
* 3.0
|
||||
* scale;
|
||||
|
||||
let count_x = state.canvas_size.0 as i32 / bg_width + 6;
|
||||
let count_y = state.canvas_size.1 as i32 / bg_height + 1;
|
||||
|
||||
for y in -1..count_y {
|
||||
for x in -1..count_x {
|
||||
batch.add((x * bg_width) as f32 - interp_x, (y * bg_height) as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundType::OutsideWind | BackgroundType::Outside | BackgroundType::OutsideUnknown => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 0));
|
||||
|
||||
let offset_x = (self.tick % 640) as i32;
|
||||
let offset_y = ((state.canvas_size.1 - 240.0) / 2.0).floor();
|
||||
|
||||
// Sun/Moon with 100px buffers on either side
|
||||
let (start, width, center) = if state.constants.is_switch {
|
||||
(0, 427, ((state.canvas_size.0 - 427.0) / 2.0).floor())
|
||||
} else {
|
||||
(144, 320, ((state.canvas_size.0 - 320.0) / 2.0).floor())
|
||||
};
|
||||
|
||||
for x in (0..(center as i32)).step_by(100) {
|
||||
batch.add_rect(x as f32, offset_y, &Rect::new_size(start, 0, 100, 88));
|
||||
}
|
||||
|
||||
batch.add_rect(center, offset_y, &Rect::new_size(0, 0, width, 88));
|
||||
|
||||
for x in (center as i32 + width as i32..(state.canvas_size.0 as i32)).step_by(100) {
|
||||
batch.add_rect(x as f32, offset_y, &Rect::new_size(start, 0, 100, 88));
|
||||
}
|
||||
|
||||
// top / bottom edges
|
||||
if offset_y > 0.0 {
|
||||
let scale = offset_y;
|
||||
|
||||
for x in (0..(state.canvas_size.0 as i32)).step_by(100) {
|
||||
batch.add_rect_scaled(x as f32, 0.0, 1.0, scale, &Rect::new_size(128, 0, 100, 1));
|
||||
}
|
||||
|
||||
batch.add_rect_scaled(
|
||||
(state.canvas_size.0 - 320.0) / 2.0,
|
||||
0.0,
|
||||
1.0,
|
||||
scale,
|
||||
&Rect::new_size(0, 0, 320, 1),
|
||||
);
|
||||
|
||||
for x in ((-offset_x * 4)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect_scaled(
|
||||
x as f32,
|
||||
offset_y + 240.0,
|
||||
1.0,
|
||||
scale + 4.0,
|
||||
&Rect::new_size(0, 239, 320, 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for x in ((-offset_x / 2)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 88.0, &Rect::new_size(0, 88, 320, 35));
|
||||
}
|
||||
|
||||
for x in ((-offset_x % 320)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 123.0, &Rect::new_size(0, 123, 320, 23));
|
||||
}
|
||||
|
||||
for x in ((-offset_x * 2)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 146.0, &Rect::new_size(0, 146, 320, 30));
|
||||
}
|
||||
|
||||
for x in ((-offset_x * 4)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 176.0, &Rect::new_size(0, 176, 320, 64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::npc::boss::BossNPC;
|
||||
use crate::game::npc::list::NPCList;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
@ -25,13 +25,7 @@ pub struct BossLifeBar {
|
|||
|
||||
impl BossLifeBar {
|
||||
pub fn new() -> BossLifeBar {
|
||||
BossLifeBar {
|
||||
target: BossLifeTarget::None,
|
||||
life: 0,
|
||||
max_life: 0,
|
||||
prev_life: 0,
|
||||
counter: 0,
|
||||
}
|
||||
BossLifeBar { target: BossLifeTarget::None, life: 0, max_life: 0, prev_life: 0, counter: 0 }
|
||||
}
|
||||
|
||||
pub fn set_npc_target(&mut self, npc_id: u16, npc_list: &NPCList) {
|
||||
|
@ -49,6 +43,91 @@ impl BossLifeBar {
|
|||
self.max_life = self.life;
|
||||
self.prev_life = self.life;
|
||||
}
|
||||
|
||||
fn draw_regular(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let box_length = 256;
|
||||
let bar_length = box_length - 58;
|
||||
|
||||
let text_rect = Rect::new_size(0, 48, 32, 8);
|
||||
let box_rect1 = Rect::new_size(0, 0, 244, 8);
|
||||
let box_rect2 = Rect::new_size(0, 16, 244, 8);
|
||||
let mut rect_prev_bar = Rect::new_size(0, 32, 232, 8);
|
||||
let mut rect_life_bar = Rect::new_size(0, 24, 232, 8);
|
||||
|
||||
rect_prev_bar.right = ((self.prev_life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0,
|
||||
&box_rect1,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 12.0,
|
||||
&box_rect2,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0,
|
||||
&box_rect1,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0,
|
||||
&rect_prev_bar,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0,
|
||||
&rect_life_bar,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0 + 8.0).floor(),
|
||||
state.canvas_size.1 - 16.0,
|
||||
&text_rect,
|
||||
);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_nx(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let box_length = 148;
|
||||
let bar_length = box_length - 52;
|
||||
|
||||
let text_rect = Rect::new_size(0, 48, 32, 8);
|
||||
let box_rect1 = Rect::new_size(0, 0, 136, 8);
|
||||
let box_rect2 = Rect::new_size(0, 16, 136, 8);
|
||||
let box_rect3 = Rect::new_size(238, 0, 6, 8);
|
||||
let box_rect4 = Rect::new_size(238, 16, 6, 8);
|
||||
let mut rect_prev_bar = Rect::new_size(0, 32, 124, 8);
|
||||
let mut rect_life_bar = Rect::new_size(0, 24, 124, 8);
|
||||
|
||||
rect_prev_bar.right = ((self.prev_life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
let base_x = state.canvas_size.0 - box_length as f32;
|
||||
|
||||
batch.add_rect((base_x - 6.0).floor(), state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect((base_x - 6.0).floor(), state.canvas_size.1 - 12.0, &box_rect2);
|
||||
batch.add_rect((base_x - 6.0).floor(), state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect((state.canvas_size.0 - 18.0).floor(), state.canvas_size.1 - 20.0, &box_rect3);
|
||||
batch.add_rect((state.canvas_size.0 - 18.0).floor(), state.canvas_size.1 - 12.0, &box_rect4);
|
||||
batch.add_rect((state.canvas_size.0 - 18.0).floor(), state.canvas_size.1 - 20.0, &box_rect3);
|
||||
batch.add_rect((base_x + 34.0).floor(), state.canvas_size.1 - 16.0, &rect_prev_bar);
|
||||
batch.add_rect((base_x + 34.0).floor(), state.canvas_size.1 - 16.0, &rect_life_bar);
|
||||
batch.add_rect((base_x + 2.0).floor(), state.canvas_size.1 - 16.0, &text_rect);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
|
||||
|
@ -86,35 +165,9 @@ impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let box_length = 256;
|
||||
let bar_length = box_length - 58;
|
||||
|
||||
let text_rect = Rect::new_size(0, 48, 32, 8);
|
||||
let box_rect1 = Rect::new_size(0, 0, 244, 8);
|
||||
let box_rect2 = Rect::new_size(0, 16, 244, 8);
|
||||
let mut rect_prev_bar = Rect::new_size(0, 32, 232, 8);
|
||||
let mut rect_life_bar = Rect::new_size(0, 24, 232, 8);
|
||||
|
||||
rect_prev_bar.right = ((self.prev_life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 12.0, &box_rect2);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0, &rect_prev_bar);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0, &rect_life_bar);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 8.0).floor(),
|
||||
state.canvas_size.1 - 16.0, &text_rect);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
match state.constants.is_switch {
|
||||
true => self.draw_nx(state, ctx, _frame),
|
||||
false => self.draw_regular(state, ctx, _frame),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::Font;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CompactJukebox {
|
||||
song_id: usize,
|
||||
shown: bool,
|
||||
}
|
||||
|
||||
impl CompactJukebox {
|
||||
pub fn new() -> CompactJukebox {
|
||||
CompactJukebox { song_id: 0, shown: false }
|
||||
}
|
||||
|
||||
pub fn change_song(&mut self, song_id: usize, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.song_id = song_id;
|
||||
|
||||
if self.song_id == state.sound_manager.current_song() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false);
|
||||
}
|
||||
|
||||
pub fn next_song(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let mut new_song_id = if self.song_id == state.constants.music_table.len() - 1 { 1 } else { self.song_id + 1 };
|
||||
|
||||
// skip ika if soundtrack is not set to new
|
||||
if self.is_ika_unavailable(new_song_id, state) {
|
||||
new_song_id = 1;
|
||||
}
|
||||
|
||||
self.change_song(new_song_id, state, ctx)
|
||||
}
|
||||
|
||||
pub fn prev_song(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let mut new_song_id = if self.song_id == 1 { state.constants.music_table.len() - 1 } else { self.song_id - 1 };
|
||||
|
||||
// skip ika if soundtrack is not set to new
|
||||
if self.is_ika_unavailable(new_song_id, state) {
|
||||
new_song_id = 42;
|
||||
}
|
||||
|
||||
self.change_song(new_song_id, state, ctx)
|
||||
}
|
||||
|
||||
pub fn show(&mut self) {
|
||||
self.shown = true;
|
||||
}
|
||||
|
||||
pub fn is_shown(&self) -> bool {
|
||||
self.shown
|
||||
}
|
||||
|
||||
fn is_ika_unavailable(&self, song_id: usize, state: &SharedGameState) -> bool {
|
||||
song_id == 43 && state.settings.soundtrack != "New"
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<&mut Context> for CompactJukebox {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !self.shown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let text = format!("< {:02} >", self.song_id);
|
||||
|
||||
let font_builder = state.font.builder();
|
||||
let text_width = font_builder.compute_width(&text);
|
||||
|
||||
let x = state.canvas_size.0 as f32 - text_width - 15.0;
|
||||
let y = state.canvas_size.1 as f32 - 15.0;
|
||||
|
||||
font_builder.x(x).y(y).shadow(true).draw(&text, ctx, &state.constants, &mut state.texture_set)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::scripting::tsc::text_script::IllustrationState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::scripting::tsc::text_script::IllustrationState;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::Font;
|
||||
|
||||
pub struct Credits {}
|
||||
|
||||
|
@ -60,23 +61,64 @@ impl GameEntity<()> for Credits {
|
|||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Casts")?;
|
||||
for line in state.creditscript_vm.lines.iter() {
|
||||
for line in &state.creditscript_vm.lines {
|
||||
let x = (line.cast_id % 13) * 24;
|
||||
let y = ((line.cast_id / 13) & 0xff) * 24;
|
||||
let rect = Rect::new_size(x, y, 24, 24);
|
||||
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
if state.more_rust && line.cast_id == 1 {
|
||||
// sue with more rust
|
||||
batch.add_rect_tinted(line.pos_x - 24.0, line.pos_y - 8.0, (200, 200, 255, 255), &rect);
|
||||
} else {
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
}
|
||||
}
|
||||
batch.draw(ctx)?;
|
||||
|
||||
for line in state.creditscript_vm.lines.iter() {
|
||||
state.font.draw_text_with_shadow(
|
||||
line.text.chars(),
|
||||
line.pos_x,
|
||||
line.pos_y,
|
||||
if state.more_rust {
|
||||
// draw sue's headband separately because rust doesn't let me mutate the texture set multiple times at once
|
||||
|
||||
let headband_spritesheet = {
|
||||
let base = if state.settings.original_textures { "ogph" } else { "plus" };
|
||||
format!("headband/{}/Casts", base)
|
||||
};
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, headband_spritesheet.as_str())?;
|
||||
|
||||
for line in &state.creditscript_vm.lines {
|
||||
if line.cast_id != 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let x = (line.cast_id % 13) * 24;
|
||||
let y = ((line.cast_id / 13) & 0xff) * 24;
|
||||
let rect = Rect::new_size(x, y, 24, 24);
|
||||
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
for line in &state.creditscript_vm.lines {
|
||||
let mut text_ovr = None;
|
||||
|
||||
if state.more_rust {
|
||||
text_ovr = Some(line.text.replace("Sue Sakamoto", "Crabby Sue"));
|
||||
}
|
||||
|
||||
let mut text = line.text.as_str();
|
||||
if let Some(ovr) = text_ovr.as_ref() {
|
||||
text = ovr.as_str();
|
||||
}
|
||||
|
||||
state.font.builder().position(line.pos_x, line.pos_y).shadow(true).draw(
|
||||
text,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::Rect;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum Alignment {
|
||||
|
@ -23,3 +23,18 @@ pub fn draw_number(x: f32, y: f32, val: usize, align: Alignment, state: &mut Sha
|
|||
batch.draw(ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_number_zeros(x: f32, y: f32, val: usize, align: Alignment, zeros: usize, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let n = format!("{:01$}", val, zeros);
|
||||
let align_offset = if align == Alignment::Right { n.len() as f32 * 8.0 } else { 0.0 };
|
||||
|
||||
for (offset, chr) in n.chars().enumerate() {
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(x - align_offset + offset as f32 * 8.0, y, &Rect::new_size(idx * 8, 56, 8, 8));
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
use crate::common::{FadeDirection, FadeState, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
pub struct Fade;
|
||||
|
||||
impl Fade {
|
||||
pub fn new() -> Self {
|
||||
Fade
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Fade {
|
||||
fn tick(&mut self, state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
let fade_ticks = state.constants.textscript.fade_ticks;
|
||||
match state.fade_state {
|
||||
FadeState::FadeOut(tick, direction) if tick < fade_ticks => {
|
||||
state.fade_state = FadeState::FadeOut(tick + 1, direction);
|
||||
}
|
||||
FadeState::FadeOut(tick, _) if tick == fade_ticks => {
|
||||
state.fade_state = FadeState::Hidden;
|
||||
}
|
||||
FadeState::FadeIn(tick, direction) if tick > -fade_ticks => {
|
||||
state.fade_state = FadeState::FadeIn(tick - 1, direction);
|
||||
}
|
||||
FadeState::FadeIn(tick, _) if tick == -fade_ticks => {
|
||||
state.fade_state = FadeState::Visible;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
match state.fade_state {
|
||||
FadeState::Visible => {
|
||||
return Ok(());
|
||||
}
|
||||
FadeState::Hidden => {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Fade")?;
|
||||
let mut rect = Rect::new(0, 0, 16, 16);
|
||||
let frame = 15;
|
||||
rect.left = frame * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
|
||||
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
|
||||
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
FadeState::FadeIn(tick, direction) | FadeState::FadeOut(tick, direction) => {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Fade")?;
|
||||
let mut rect = Rect::new(0, 0, 16, 16);
|
||||
|
||||
match direction {
|
||||
FadeDirection::Left | FadeDirection::Right => {
|
||||
let mut frame = tick;
|
||||
|
||||
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
|
||||
if frame >= 15 {
|
||||
frame = 15;
|
||||
} else {
|
||||
frame += 1;
|
||||
}
|
||||
|
||||
if frame >= 0 {
|
||||
rect.left = frame.abs() as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
|
||||
if direction == FadeDirection::Left {
|
||||
batch.add_rect(
|
||||
state.canvas_size.0 - x as f32 * 16.0 - 16.0,
|
||||
y as f32 * 16.0,
|
||||
&rect,
|
||||
);
|
||||
} else {
|
||||
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FadeDirection::Up | FadeDirection::Down => {
|
||||
let mut frame = tick;
|
||||
|
||||
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
|
||||
if frame >= 15 {
|
||||
frame = 15;
|
||||
} else {
|
||||
frame += 1;
|
||||
}
|
||||
|
||||
if frame >= 0 {
|
||||
rect.left = frame.abs() as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
|
||||
if direction == FadeDirection::Down {
|
||||
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
|
||||
} else {
|
||||
batch.add_rect(x as f32 * 16.0, state.canvas_size.1 - y as f32 * 16.0, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FadeDirection::Center => {
|
||||
let center_x = (state.canvas_size.0 / 2.0 - 8.0) as i32;
|
||||
let center_y = (state.canvas_size.1 / 2.0 - 8.0) as i32;
|
||||
let mut start_frame = tick;
|
||||
|
||||
for x in 0..(center_x / 16 + 2) {
|
||||
let mut frame = start_frame;
|
||||
|
||||
for y in 0..(center_y / 16 + 2) {
|
||||
if frame >= 15 {
|
||||
frame = 15;
|
||||
} else {
|
||||
frame += 1;
|
||||
}
|
||||
|
||||
if frame >= 0 {
|
||||
rect.left = frame.abs() as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
batch.add_rect((center_x - x * 16) as f32, (center_y + y * 16) as f32, &rect);
|
||||
batch.add_rect((center_x - x * 16) as f32, (center_y - y * 16) as f32, &rect);
|
||||
batch.add_rect((center_x + x * 16) as f32, (center_y + y * 16) as f32, &rect);
|
||||
batch.add_rect((center_x + x * 16) as f32, (center_y - y * 16) as f32, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
start_frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::graphics;
|
||||
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
|
||||
pub struct FallingIsland {}
|
||||
|
||||
|
@ -16,7 +18,7 @@ impl FallingIsland {
|
|||
}
|
||||
|
||||
impl GameEntity<()> for FallingIsland {
|
||||
fn tick(&mut self, state: &mut SharedGameState, custom: ()) -> GameResult {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -43,7 +45,11 @@ impl GameEntity<()> for FallingIsland {
|
|||
static RECT_ISLAND: Rect<u16> = Rect { left: 160, top: 0, right: 200, bottom: 24 };
|
||||
static RECT_TERRAIN: Rect<u16> = Rect { left: 160, top: 48, right: 320, bottom: 80 };
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &state.npc_table.tex_npc1_name)?;
|
||||
let batch = state.texture_set.get_or_load_batch(
|
||||
ctx,
|
||||
&state.constants,
|
||||
&state.npc_table.stage_textures.deref().borrow().npc1,
|
||||
)?;
|
||||
batch.add_rect(off_x + 80.0, 80.0, &RECT_BG);
|
||||
batch.add_rect(off_x + (pos_x as f32 / 512.0) - 20.0, (pos_y as f32 / 512.0) - 12.0, &RECT_ISLAND);
|
||||
batch.add_rect(off_x + 80.0, 128.0, &RECT_TERRAIN);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
pub enum FlashState {
|
||||
None,
|
||||
|
@ -83,7 +83,7 @@ impl GameEntity<()> for Flash {
|
|||
left: 0,
|
||||
top: ((cen_y - width) * state.scale) as isize,
|
||||
right: (state.canvas_size.0 * state.scale) as isize,
|
||||
bottom: ((cen_y + width) * state.scale) as isize
|
||||
bottom: ((cen_y + width) * state.scale) as isize,
|
||||
};
|
||||
|
||||
graphics::draw_rect(ctx, rect, WHITE)?;
|
||||
|
@ -93,7 +93,7 @@ impl GameEntity<()> for Flash {
|
|||
left: ((cen_x - width) * state.scale) as isize,
|
||||
top: 0,
|
||||
right: ((cen_x + width) * state.scale) as isize,
|
||||
bottom: (state.canvas_size.1 * state.scale) as isize
|
||||
bottom: (state.canvas_size.1 * state.scale) as isize,
|
||||
};
|
||||
|
||||
graphics::draw_rect(ctx, rect, WHITE)?;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::screen_insets_scaled;
|
||||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::inventory::Inventory;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::player::Player;
|
||||
use crate::game::weapon::WeaponType;
|
||||
|
||||
pub struct HUD {
|
||||
pub alignment: Alignment,
|
||||
|
@ -30,6 +31,7 @@ pub struct HUD {
|
|||
weapon_count: usize,
|
||||
current_weapon: isize,
|
||||
weapon_types: [u8; 16],
|
||||
shock: bool,
|
||||
}
|
||||
|
||||
impl HUD {
|
||||
|
@ -55,6 +57,7 @@ impl HUD {
|
|||
weapon_count: 0,
|
||||
current_weapon: 0,
|
||||
weapon_types: [0; 16],
|
||||
shock: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +78,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
self.max_life = player.max_life;
|
||||
self.air = player.air;
|
||||
self.air_counter = player.air_counter;
|
||||
self.shock = player.shock_counter / 2 % 2 != 0;
|
||||
self.weapon_count = inventory.get_weapon_count();
|
||||
self.current_weapon = inventory.get_current_weapon_idx() as isize;
|
||||
|
||||
|
@ -108,12 +112,26 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
if player.controller.trigger_next_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
|
||||
if let Some(weapon) = inventory.get_current_weapon_mut() {
|
||||
if weapon.wtype == WeaponType::Spur {
|
||||
weapon.reset_xp();
|
||||
}
|
||||
}
|
||||
|
||||
self.weapon_x_pos = 32;
|
||||
}
|
||||
|
||||
if player.controller.trigger_prev_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
|
||||
if let Some(weapon) = inventory.get_current_weapon_mut() {
|
||||
if weapon.wtype == WeaponType::Spur {
|
||||
weapon.reset_xp();
|
||||
}
|
||||
}
|
||||
|
||||
self.weapon_x_pos = 0;
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +161,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
|
||||
let wtype = self.weapon_types[a];
|
||||
if wtype != 0 {
|
||||
rect = Rect::new_size(pos_x + weapon_offset - 4, 16 - 4, 24, 24);
|
||||
rect = Rect::new_size(pos_x + weapon_offset - 4, 16 + (4 * state.scale as isize), 24, 24);
|
||||
|
||||
if state.touch_controls.consume_click_in(rect) {
|
||||
state.sound_manager.play_sfx(4);
|
||||
|
@ -190,38 +208,40 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
batch.add_rect(bar_offset + weap_x + 48.0, 24.0 + top, &Rect::new_size(80, 48, 16, 8));
|
||||
}
|
||||
|
||||
// per
|
||||
batch.add_rect(bar_offset + weap_x + 32.0, 24.0 + top, &Rect::new_size(72, 48, 8, 8));
|
||||
// lv
|
||||
batch.add_rect(num_offset + weap_x, 32.0 + top, &Rect::new_size(80, 80, 16, 8));
|
||||
// xp box
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 72, 40, 8));
|
||||
if !self.shock {
|
||||
// per
|
||||
batch.add_rect(bar_offset + weap_x + 32.0, 24.0 + top, &Rect::new_size(72, 48, 8, 8));
|
||||
// lv
|
||||
batch.add_rect(num_offset + weap_x, 32.0 + top, &Rect::new_size(80, 80, 16, 8));
|
||||
// xp box
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 72, 40, 8));
|
||||
|
||||
if self.max_level {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 72, 40, 8));
|
||||
} else if self.max_xp > 0 {
|
||||
// xp bar
|
||||
let bar_width = (self.xp as f32 / self.max_xp as f32 * 40.0) as u16;
|
||||
if self.max_level {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 72, 40, 8));
|
||||
} else if self.max_xp > 0 {
|
||||
// xp bar
|
||||
let bar_width = (self.xp as f32 / self.max_xp as f32 * 40.0) as u16;
|
||||
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 80, bar_width, 8));
|
||||
}
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 80, bar_width, 8));
|
||||
}
|
||||
|
||||
if (self.xp_bar_counter & 0x02) != 0 {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 80, 40, 8));
|
||||
}
|
||||
if (self.xp_bar_counter & 0x02) != 0 {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 80, 40, 8));
|
||||
}
|
||||
|
||||
if self.max_life != 0 {
|
||||
let yellow_bar_width = (self.life_bar as f32 / self.max_life as f32 * 39.0) as u16;
|
||||
let bar_width = (self.life as f32 / self.max_life as f32 * 39.0) as u16;
|
||||
if self.max_life != 0 {
|
||||
let yellow_bar_width = (self.life_bar as f32 / self.max_life as f32 * 39.0) as u16;
|
||||
let bar_width = (self.life as f32 / self.max_life as f32 * 39.0) as u16;
|
||||
|
||||
// heart/hp number box
|
||||
batch.add_rect(num_offset + 16.0, 40.0 + top, &Rect::new_size(0, 40, 24, 8));
|
||||
// life box
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(24, 40, 40, 8));
|
||||
// yellow bar
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(0, 32, yellow_bar_width, 8));
|
||||
// life
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(0, 24, bar_width, 8));
|
||||
// heart/hp number box
|
||||
batch.add_rect(num_offset + 16.0, 40.0 + top, &Rect::new_size(0, 40, 24, 8));
|
||||
// life box
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(24, 40, 40, 8));
|
||||
// yellow bar
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(0, 32, yellow_bar_width, 8));
|
||||
// life
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(0, 24, bar_width, 8));
|
||||
}
|
||||
}
|
||||
|
||||
if self.air_counter > 0 {
|
||||
|
@ -240,8 +260,18 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
if self.weapon_count != 0 {
|
||||
let mut rect = Rect::new(0, 0, 0, 16);
|
||||
|
||||
// First frame of animation is off by one weapon
|
||||
// There's probably a more elegant solution than this
|
||||
let first_frame_offset = if self.weapon_x_pos == 32 {
|
||||
-1
|
||||
} else if self.weapon_x_pos == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
for a in 0..self.weapon_count {
|
||||
let mut pos_x = ((a as isize - self.current_weapon) as f32 * 16.0) + weap_x;
|
||||
let mut pos_x = ((a as isize - self.current_weapon + first_frame_offset) as f32 * 16.0) + weap_x;
|
||||
|
||||
if pos_x < 8.0 {
|
||||
pos_x += 48.0 + self.weapon_count as f32 * 16.0;
|
||||
|
@ -282,11 +312,13 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
}
|
||||
|
||||
if self.max_ammo != 0 {
|
||||
draw_number(num_offset + weap_x + 64.0, 16.0 + top, self.ammo as usize, Alignment::Right, state, ctx)?;
|
||||
draw_number(num_offset + weap_x + 64.0, 24.0 + top, self.max_ammo as usize, Alignment::Right, state, ctx)?;
|
||||
draw_number(bar_offset + weap_x + 64.0, 16.0 + top, self.ammo as usize, Alignment::Right, state, ctx)?;
|
||||
draw_number(bar_offset + weap_x + 64.0, 24.0 + top, self.max_ammo as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
if !self.shock {
|
||||
draw_number(num_offset + weap_x + 24.0, 32.0 + top, self.current_level, Alignment::Right, state, ctx)?;
|
||||
draw_number(num_offset + 40.0, 40.0 + top, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
draw_number(num_offset + weap_x + 24.0, 32.0 + top, self.current_level, Alignment::Right, state, ctx)?;
|
||||
draw_number(num_offset + 40.0, 40.0 + top, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::components::hud::HUD;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::inventory::Inventory;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::scripting::tsc::text_script::ScriptMode;
|
||||
use crate::weapon::{WeaponLevel, WeaponType};
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState};
|
||||
use crate::game::weapon::{WeaponLevel, WeaponType};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
@ -62,21 +63,25 @@ impl InventoryUI {
|
|||
inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 6000).unwrap_or(6000)
|
||||
}
|
||||
|
||||
fn exit(&mut self, state: &mut SharedGameState, player: &mut Player, inventory: &mut Inventory) {
|
||||
fn get_weapon_event_number(&self, inventory: &Inventory) -> u16 {
|
||||
inventory.get_current_weapon().map(|w| w.wtype as u16 + 1000).unwrap_or(1000)
|
||||
}
|
||||
|
||||
fn exit(&mut self, state: &mut SharedGameState, _player: &mut Player, inventory: &mut Inventory, hud: &mut HUD) {
|
||||
self.focus = InventoryFocus::None;
|
||||
inventory.current_item = 0;
|
||||
self.text_y_pos = 16;
|
||||
hud.weapon_x_pos = 32;
|
||||
state.textscript_vm.reset();
|
||||
state.textscript_vm.set_mode(ScriptMode::Map);
|
||||
player.controller.update_trigger();
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
||||
impl GameEntity<(&mut Context, &mut Player, &mut Inventory, &mut HUD)> for InventoryUI {
|
||||
fn tick(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
(ctx, player, inventory): (&mut Context, &mut Player, &mut Inventory),
|
||||
(ctx, player, inventory, hud): (&mut Context, &mut Player, &mut Inventory, &mut HUD),
|
||||
) -> GameResult<()> {
|
||||
let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
let mut slot_rect =
|
||||
|
@ -86,10 +91,12 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
|
||||
if state.control_flags.control_enabled()
|
||||
&& (player.controller.trigger_inventory()
|
||||
|| player.controller.trigger_menu_back()
|
||||
|| (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect)))
|
||||
|| player.controller.trigger_menu_back()
|
||||
|| (player.controller.trigger_menu_ok() && self.focus == InventoryFocus::Weapons)
|
||||
|| (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect)))
|
||||
{
|
||||
self.exit(state, player, inventory);
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
self.exit(state, player, inventory, hud);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -124,10 +131,6 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_weapon_event_number(inventory: &Inventory) -> u16 {
|
||||
inventory.get_current_weapon().map(|w| w.wtype as u16 + 1000).unwrap_or(1000)
|
||||
}
|
||||
|
||||
self.selected_item = inventory.current_item;
|
||||
self.selected_weapon = inventory.current_weapon;
|
||||
|
||||
|
@ -136,27 +139,45 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
match self.focus {
|
||||
InventoryFocus::None => {
|
||||
self.focus = InventoryFocus::Weapons;
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
// check weapon count (0 count means we run item script)
|
||||
let event = if self.weapon_count > 0 {
|
||||
self.get_weapon_event_number(inventory)
|
||||
} else {
|
||||
self.get_item_event_number(inventory)
|
||||
};
|
||||
state.textscript_vm.start_script(event);
|
||||
}
|
||||
InventoryFocus::Weapons if state.control_flags.control_enabled() => {
|
||||
if player.controller.trigger_left() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
|
||||
// if we have no weapons, the TSC should not be refreshed with L/R keystrokes
|
||||
if self.weapon_count > 0
|
||||
{
|
||||
if player.controller.trigger_left() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_right() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
}
|
||||
}
|
||||
|
||||
if player.controller.trigger_right() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_up() || player.controller.trigger_down() {
|
||||
// we should not move from the weapon row if there are no items
|
||||
if (player.controller.trigger_up() || player.controller.trigger_down()) && self.item_count > 0 {
|
||||
self.focus = InventoryFocus::Items;
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
}
|
||||
InventoryFocus::Items if self.item_count != 0 && state.control_flags.control_enabled() => {
|
||||
let mut moved_cursor = false;
|
||||
|
||||
if player.controller.trigger_left() {
|
||||
state.sound_manager.play_sfx(1);
|
||||
|
||||
|
@ -166,22 +187,24 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
self.selected_item += count_x - 1;
|
||||
}
|
||||
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
moved_cursor = true;
|
||||
}
|
||||
|
||||
if player.controller.trigger_right() {
|
||||
match () {
|
||||
_ if self.selected_item == self.item_count + 1 => {
|
||||
_ if self.selected_item + 1 == self.item_count => {
|
||||
self.selected_item = count_x * (self.selected_item / count_x);
|
||||
}
|
||||
_ if (self.selected_item % count_x) + 1 == count_x => {
|
||||
self.selected_item = self.selected_item.saturating_sub(count_x) + 1;
|
||||
self.selected_item = self.selected_item.saturating_sub(count_x - 1);
|
||||
}
|
||||
_ => self.selected_item += 1,
|
||||
}
|
||||
|
||||
state.sound_manager.play_sfx(1);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
moved_cursor = true;
|
||||
}
|
||||
|
||||
if player.controller.trigger_up() {
|
||||
|
@ -189,12 +212,14 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
self.focus = InventoryFocus::Weapons;
|
||||
|
||||
state.sound_manager.play_sfx(4);
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
} else {
|
||||
self.selected_item -= count_x;
|
||||
|
||||
state.sound_manager.play_sfx(1);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
moved_cursor = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,20 +228,27 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
self.focus = InventoryFocus::Weapons;
|
||||
|
||||
state.sound_manager.play_sfx(4);
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
moved_cursor = true;
|
||||
} else {
|
||||
self.selected_item += count_x;
|
||||
|
||||
state.sound_manager.play_sfx(1);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
moved_cursor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if player.controller.trigger_menu_ok() {
|
||||
self.selected_item = self.selected_item.min(self.item_count - 1);
|
||||
|
||||
if moved_cursor {
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
|
||||
if !state.control_flags.ok_button_disabled() && player.controller.trigger_menu_ok() {
|
||||
state.textscript_vm.start_script(self.get_item_event_number_action(inventory));
|
||||
}
|
||||
|
||||
self.selected_item = self.selected_item.min(self.item_count - 1);
|
||||
inventory.current_item = self.selected_item;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -234,8 +266,8 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
state.sound_manager.play_sfx(4);
|
||||
self.selected_weapon = i;
|
||||
inventory.current_weapon = i;
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
self.exit(state, player, inventory);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
self.exit(state, player, inventory, hud);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,6 +296,10 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult<()> {
|
||||
if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut tmp_rect = Rect { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
let x = (((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left;
|
||||
|
@ -286,8 +322,8 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
|
||||
let (item_cursor_frame, weapon_cursor_frame) = match self.focus {
|
||||
InventoryFocus::None => (1, 1),
|
||||
InventoryFocus::Weapons => (1, self.tick & 1),
|
||||
InventoryFocus::Items => (self.tick & 1, 1),
|
||||
InventoryFocus::Weapons => (1, (self.tick / 2) % 2), //every-other frame (& 1): this is not what we want, we want every 2 frames.
|
||||
InventoryFocus::Items => ((self.tick / 2) % 2, 1),
|
||||
};
|
||||
|
||||
batch.add_rect(
|
||||
|
@ -358,6 +394,23 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
|||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
for (idx, (item_id, amount)) in self.item_data.iter().enumerate() {
|
||||
if *item_id == 0 || *amount == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if *amount > 1 {
|
||||
draw_number(
|
||||
x + 12.0 + (idx % count_x) as f32 * 32.0 + 32.0,
|
||||
y + 68.0 + (idx / count_x) as f32 * 16.0,
|
||||
*amount as usize,
|
||||
Alignment::Right,
|
||||
state,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, weapon) in self.weapon_data.iter().enumerate() {
|
||||
if weapon.wtype == WeaponType::None {
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::backend::{BackendTexture, SpriteBatchCommand};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::stage::Stage;
|
||||
use crate::graphics::font::Font;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum MapSystemState {
|
||||
Hidden,
|
||||
FadeInBox(u16),
|
||||
FadeInLine(u16),
|
||||
Visible,
|
||||
FadeOutBox(u16),
|
||||
}
|
||||
|
||||
pub struct MapSystem {
|
||||
texture: RefCell<Option<Box<dyn BackendTexture>>>,
|
||||
has_map_data: RefCell<bool>,
|
||||
last_size: (u16, u16),
|
||||
tick: u16,
|
||||
state: MapSystemState,
|
||||
}
|
||||
|
||||
impl MapSystem {
|
||||
pub fn new() -> MapSystem {
|
||||
MapSystem {
|
||||
texture: RefCell::new(None),
|
||||
has_map_data: RefCell::new(false),
|
||||
last_size: (0, 0),
|
||||
tick: 0,
|
||||
state: MapSystemState::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_map(&self, state: &mut SharedGameState, ctx: &mut Context, stage: &Stage) -> GameResult {
|
||||
if self.texture.borrow().is_none() {
|
||||
*self.has_map_data.borrow_mut() = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
*self.has_map_data.borrow_mut() = true;
|
||||
|
||||
graphics::set_render_target(ctx, self.texture.borrow().as_ref())?;
|
||||
graphics::clear(ctx, Color::new(0.0, 0.0, 0.0, 1.0));
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
for y in 0..stage.map.height {
|
||||
for x in 0..stage.map.width {
|
||||
const RECTS: [Rect<u16>; 4] = [
|
||||
Rect { left: 240, top: 24, right: 241, bottom: 25 },
|
||||
Rect { left: 241, top: 24, right: 242, bottom: 25 },
|
||||
Rect { left: 242, top: 24, right: 243, bottom: 25 },
|
||||
Rect { left: 243, top: 24, right: 244, bottom: 25 },
|
||||
];
|
||||
|
||||
let attr = stage.map.get_attribute(x as _, y as _);
|
||||
|
||||
let layer = match attr {
|
||||
0 => 0,
|
||||
0x01 | 0x02 | 0x40 | 0x44 | 0x51 | 0x52 | 0x55 | 0x56 | 0x60 | 0x71 | 0x72 | 0x75 | 0x76 | 0x80
|
||||
| 0x81 | 0x82 | 0x83 | 0xA0 | 0xA1 | 0xA2 | 0xA3 => 1,
|
||||
0x43 | 0x50 | 0x53 | 0x54 | 0x57 | 0x63 | 0x70 | 0x73 | 0x74 | 0x77 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
|
||||
batch.add_rect(x as _, y as _, &RECTS[layer]);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
graphics::set_render_target(ctx, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
stage: &Stage,
|
||||
players: [&Player; 2],
|
||||
) -> GameResult {
|
||||
let touch_rect = Rect::new_size(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize);
|
||||
|
||||
if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
|
||||
if self.state == MapSystemState::Hidden {
|
||||
state.touch_controls.control_type = TouchControlType::None;
|
||||
state.control_flags.set_control_enabled(false);
|
||||
self.state = MapSystemState::FadeInBox(0);
|
||||
}
|
||||
} else {
|
||||
self.state = MapSystemState::Hidden;
|
||||
}
|
||||
|
||||
if self.state == MapSystemState::Hidden {
|
||||
self.tick = 0;
|
||||
*self.has_map_data.borrow_mut() = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
|
||||
let width = (stage.map.width as f32 * state.scale) as u16;
|
||||
let height = (stage.map.height as f32 * state.scale) as u16;
|
||||
|
||||
if self.last_size != (width, height) {
|
||||
self.last_size = (width, height);
|
||||
*self.texture.borrow_mut() = graphics::create_texture_mutable(ctx, width, height).ok();
|
||||
*self.has_map_data.borrow_mut() = false;
|
||||
}
|
||||
|
||||
match self.state {
|
||||
MapSystemState::FadeInBox(tick) => {
|
||||
if tick >= 8 {
|
||||
self.state = MapSystemState::FadeInLine(0);
|
||||
} else {
|
||||
self.state = MapSystemState::FadeInBox(tick + 1);
|
||||
}
|
||||
}
|
||||
MapSystemState::FadeOutBox(tick) => {
|
||||
if tick == 0 {
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_control_enabled(true);
|
||||
state.textscript_vm.state = TextScriptExecutionState::Ended;
|
||||
self.state = MapSystemState::Hidden;
|
||||
} else {
|
||||
self.state = MapSystemState::FadeOutBox(tick - 1);
|
||||
}
|
||||
}
|
||||
MapSystemState::FadeInLine(tick) => {
|
||||
if (tick + 2) < stage.map.height {
|
||||
self.state = MapSystemState::FadeInLine(tick + 2);
|
||||
} else {
|
||||
self.state = MapSystemState::Visible;
|
||||
}
|
||||
|
||||
for player in &players {
|
||||
if player.controller.trigger_jump()
|
||||
|| player.controller.trigger_shoot()
|
||||
|| state.touch_controls.consume_click_in(touch_rect)
|
||||
{
|
||||
self.state = MapSystemState::FadeOutBox(8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
MapSystemState::Visible => {
|
||||
for player in &players {
|
||||
if player.controller.trigger_jump()
|
||||
|| player.controller.trigger_shoot()
|
||||
|| state.touch_controls.consume_click_in(touch_rect)
|
||||
{
|
||||
self.state = MapSystemState::FadeOutBox(8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
stage: &Stage,
|
||||
players: [&Player; 2],
|
||||
) -> GameResult {
|
||||
if self.state == MapSystemState::Hidden {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !*self.has_map_data.borrow() {
|
||||
self.render_map(state, ctx, stage)?;
|
||||
}
|
||||
|
||||
let (scr_w, scr_h) = (state.canvas_size.0 * state.scale, state.canvas_size.1 * state.scale);
|
||||
let text_height = state.font.line_height();
|
||||
let rect_black_bar = Rect::new_size(
|
||||
0,
|
||||
(7.0 * state.scale) as _,
|
||||
state.screen_size.0 as _,
|
||||
((text_height + 4.0) * state.scale) as _,
|
||||
);
|
||||
|
||||
if !state.constants.is_switch {
|
||||
graphics::draw_rect(ctx, rect_black_bar, Color::new(0.0, 0.0, 0.0, 1.0))?;
|
||||
}
|
||||
|
||||
let map_name = if state.constants.is_cs_plus && state.settings.locale == "jp" {
|
||||
stage.data.name_jp.as_str()
|
||||
} else {
|
||||
stage.data.name.as_str()
|
||||
};
|
||||
|
||||
state.font.builder().center(state.canvas_size.0).y(9.0).draw(
|
||||
map_name,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
|
||||
let mut map_rect = Rect::new(0.0, 0.0, self.last_size.0 as f32, self.last_size.1 as f32);
|
||||
|
||||
match self.state {
|
||||
MapSystemState::FadeInBox(tick) | MapSystemState::FadeOutBox(tick) => {
|
||||
let width = (state.scale * tick as f32 * stage.map.width as f32 / 16.0) as isize;
|
||||
let height = (state.scale * tick as f32 * stage.map.height as f32 / 16.0) as isize;
|
||||
|
||||
let rect = Rect::new_size(
|
||||
(scr_w / 2.0) as isize - width,
|
||||
(scr_h / 2.0) as isize - height,
|
||||
width * 2,
|
||||
height * 2,
|
||||
);
|
||||
|
||||
graphics::draw_rect(ctx, rect, Color::new(0.0, 0.0, 0.0, 1.0))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
MapSystemState::FadeInLine(line) => {
|
||||
map_rect.bottom = state.scale * (line as f32 + 1.0);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let width_border = state.scale * (stage.map.width as f32 + 2.0);
|
||||
let height_border = state.scale * (stage.map.height as f32 + 2.0);
|
||||
|
||||
let rect = Rect::new_size(
|
||||
((scr_w - width_border) / 2.0) as isize,
|
||||
((scr_h - height_border) / 2.0) as isize,
|
||||
width_border as isize,
|
||||
height_border as isize,
|
||||
);
|
||||
|
||||
graphics::draw_rect(ctx, rect, Color::new(0.0, 0.0, 0.0, 1.0))?;
|
||||
|
||||
if let Some(tex) = self.texture.borrow_mut().as_mut() {
|
||||
let width = state.scale * stage.map.width as f32;
|
||||
let height = state.scale * stage.map.height as f32;
|
||||
|
||||
tex.clear();
|
||||
tex.add(SpriteBatchCommand::DrawRect(
|
||||
map_rect,
|
||||
Rect::new_size((scr_w - width) / 2.0, (scr_h - height) / 2.0, map_rect.width(), map_rect.height()),
|
||||
));
|
||||
tex.draw()?;
|
||||
}
|
||||
|
||||
if (self.tick & 8) != 0 {
|
||||
const PLAYER_RECT: Rect<u16> = Rect { left: 0, top: 57, right: 1, bottom: 58 };
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
let x_offset = (state.canvas_size.0 - stage.map.width as f32) / 2.0;
|
||||
let y_offset = (state.canvas_size.1 - stage.map.height as f32) / 2.0;
|
||||
let tile_div = stage.map.tile_size.as_int() * 0x200;
|
||||
|
||||
for player in &players {
|
||||
if !player.cond.alive() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let plr_x = x_offset + (player.x / tile_div) as f32;
|
||||
let plr_y = y_offset + (player.y / tile_div) as f32;
|
||||
|
||||
batch.add_rect(plr_x, plr_y, &PLAYER_RECT);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,10 +1,19 @@
|
|||
pub mod background;
|
||||
pub mod boss_life_bar;
|
||||
pub mod compact_jukebox;
|
||||
pub mod credits;
|
||||
pub mod draw_common;
|
||||
pub mod fade;
|
||||
pub mod falling_island;
|
||||
pub mod flash;
|
||||
pub mod hud;
|
||||
pub mod inventory;
|
||||
pub mod map_system;
|
||||
pub mod nikumaru;
|
||||
pub mod number_popup;
|
||||
pub mod replay;
|
||||
pub mod stage_select;
|
||||
pub mod text_boxes;
|
||||
pub mod tilemap;
|
||||
pub mod water_renderer;
|
||||
pub mod whimsical_star;
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
use byteorder::{LE, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{Alignment, draw_number, draw_number_zeros};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::vfs::OpenOptions;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::{SharedGameState, TimingMode};
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::util::rng::RNG;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NikumaruCounter {
|
||||
pub tick: usize,
|
||||
pub shown: bool,
|
||||
}
|
||||
|
||||
impl NikumaruCounter {
|
||||
pub fn new() -> NikumaruCounter {
|
||||
NikumaruCounter { tick: 0, shown: false }
|
||||
}
|
||||
|
||||
fn load_time(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<u32> {
|
||||
if let Ok(mut data) = filesystem::user_open(ctx, [state.get_rec_filename(), ".rec".to_string()].join("")) {
|
||||
let mut ticks: [u32; 4] = [0; 4];
|
||||
|
||||
for iter in 0..=3 {
|
||||
ticks[iter] = data.read_u32::<LE>()?;
|
||||
}
|
||||
|
||||
let random = data.read_u32::<LE>()?;
|
||||
let random_list: [u8; 4] = random.to_le_bytes();
|
||||
|
||||
for iter in 0..=3 {
|
||||
ticks[iter] = u32::from_le_bytes([
|
||||
ticks[iter].to_le_bytes()[0].wrapping_sub(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[1].wrapping_sub(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[2].wrapping_sub(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[3].wrapping_sub(random_list[iter] / 2),
|
||||
]);
|
||||
}
|
||||
|
||||
if ticks[0] == ticks[1] && ticks[0] == ticks[2] {
|
||||
return Ok(ticks[0]);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Failed to open 290 record.");
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn save_time(&mut self, new_time: u32, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut data) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), ".rec".to_string()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
let mut ticks: [u32; 4] = [new_time; 4];
|
||||
let mut random_list: [u8; 4] = [0; 4];
|
||||
|
||||
for iter in 0..=3 {
|
||||
random_list[iter] = state.effect_rng.range(0..250) as u8 + iter as u8;
|
||||
|
||||
ticks[iter] = u32::from_le_bytes([
|
||||
ticks[iter].to_le_bytes()[0].wrapping_add(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[1].wrapping_add(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[2].wrapping_add(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[3].wrapping_add(random_list[iter] / 2),
|
||||
]);
|
||||
|
||||
data.write_u32::<LE>(ticks[iter])?;
|
||||
}
|
||||
|
||||
data.write_u32::<LE>(u32::from_le_bytes(random_list))?;
|
||||
} else {
|
||||
log::warn!("Failed to write 290 record.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_counter(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.tick = self.load_time(state, ctx)? as usize;
|
||||
if self.tick > 0 {
|
||||
self.shown = true;
|
||||
} else {
|
||||
self.shown = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_counter(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<bool> {
|
||||
let old_record = self.load_time(state, ctx)? as usize;
|
||||
if self.tick < old_record || old_record == 0 {
|
||||
self.save_time(self.tick as u32, state, ctx)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<&Player> for NikumaruCounter {
|
||||
fn tick(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
if !player.equip.has_nikumaru() {
|
||||
self.tick = 0;
|
||||
self.shown = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.shown = true;
|
||||
|
||||
if state.control_flags.control_enabled() {
|
||||
self.tick += 1;
|
||||
}
|
||||
|
||||
if self.tick >= 300000 {
|
||||
self.tick = 300000;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !self.shown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let x = 16.0;
|
||||
let y = 8.0;
|
||||
|
||||
const CLOCK_RECTS: [Rect<u16>; 2] = [
|
||||
Rect { left: 112, top: 104, right: 120, bottom: 112 },
|
||||
Rect { left: 120, top: 104, right: 128, bottom: 112 },
|
||||
];
|
||||
const PRIME: Rect<u16> = Rect { left: 128, top: 104, right: 160, bottom: 112 };
|
||||
|
||||
let (one_tenth, second, minute) = match state.settings.timing_mode {
|
||||
TimingMode::_60Hz => (6, 60, 3600),
|
||||
_ => (5, 50, 3000),
|
||||
};
|
||||
|
||||
if self.tick % 30 <= 10 {
|
||||
batch.add_rect(x, y, &CLOCK_RECTS[1]);
|
||||
} else {
|
||||
batch.add_rect(x, y, &CLOCK_RECTS[0]);
|
||||
}
|
||||
batch.add_rect(x + 30.0, y, &PRIME);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
draw_number(x + 32.0, y, self.tick / minute, Alignment::Right, state, ctx)?;
|
||||
draw_number_zeros(x + 52.0, y, (self.tick / second) % 60, Alignment::Right, 2, state, ctx)?;
|
||||
draw_number(x + 64.0, y, (self.tick / one_tenth) % 10, Alignment::Right, state, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
use crate::common::{interpolate_fix9_scale, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct NumberPopup {
|
||||
|
@ -13,43 +13,48 @@ pub struct NumberPopup {
|
|||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
counter: u16,
|
||||
value_display: i16,
|
||||
}
|
||||
|
||||
impl NumberPopup {
|
||||
pub fn new() -> NumberPopup {
|
||||
NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 0 }
|
||||
NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 0, value_display: 0 }
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, value: i16) {
|
||||
if self.counter > 32 {
|
||||
self.counter = 32;
|
||||
}
|
||||
|
||||
self.value = value;
|
||||
}
|
||||
|
||||
pub fn add_value(&mut self, value: i16) {
|
||||
self.set_value(self.value + value);
|
||||
}
|
||||
|
||||
pub fn update_displayed_value(&mut self) {
|
||||
if self.counter > 32 {
|
||||
self.counter = 32;
|
||||
}
|
||||
self.value_display += self.value;
|
||||
self.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for NumberPopup {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult<()> {
|
||||
if self.value == 0 {
|
||||
if self.value_display == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.counter += 1;
|
||||
if self.counter == 80 {
|
||||
self.counter = 0;
|
||||
self.value = 0;
|
||||
self.value_display = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
|
||||
if self.value == 0 {
|
||||
if self.value_display == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -63,12 +68,10 @@ impl GameEntity<()> for NumberPopup {
|
|||
|
||||
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
let x = interpolate_fix9_scale(self.prev_x, self.x, state.frame_time) - frame_x;
|
||||
let y = interpolate_fix9_scale(self.prev_y, self.y, state.frame_time) - frame_y - y_offset;
|
||||
let y = interpolate_fix9_scale(self.prev_y, self.y, state.frame_time) - frame_y - y_offset
|
||||
- 3.0f32; // This is supposed to be -4, but for some reason -3 looks more accurate
|
||||
|
||||
let mut n = self.value.to_string();
|
||||
if self.value > 0 {
|
||||
n = "+".to_owned() + n.as_str();
|
||||
};
|
||||
let n = format!("{:+}", self.value_display);
|
||||
|
||||
let x = x - n.len() as f32 * 4.0;
|
||||
|
||||
|
@ -81,7 +84,7 @@ impl GameEntity<()> for NumberPopup {
|
|||
batch.add_rect(x + offset as f32 * 8.0, y, &Rect::new_size(40, 48 + clip, 8, 8 - clip));
|
||||
}
|
||||
'0'..='9' => {
|
||||
let number_set = if self.value < 0 { 64 } else { 56 };
|
||||
let number_set = if self.value_display < 0 { 64 } else { 56 };
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(
|
||||
x + offset as f32 * 8.0,
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
use std::io::{Cursor, Read};
|
||||
|
||||
use byteorder::{LE, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::framework::vfs::OpenOptions;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::{ReplayKind, ReplayState, SharedGameState};
|
||||
use crate::input::replay_player_controller::{KeyState, ReplayController};
|
||||
use crate::game::player::Player;
|
||||
use crate::graphics::font::Font;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Replay {
|
||||
replay_version: u16,
|
||||
keylist: Vec<u16>,
|
||||
last_input: KeyState,
|
||||
rng_seed: u64,
|
||||
pub controller: ReplayController,
|
||||
tick: usize,
|
||||
resume_tick: usize,
|
||||
is_active: bool,
|
||||
}
|
||||
|
||||
impl Replay {
|
||||
pub fn new() -> Replay {
|
||||
Replay {
|
||||
replay_version: 0,
|
||||
keylist: Vec::new(),
|
||||
last_input: KeyState(0),
|
||||
rng_seed: 0,
|
||||
controller: ReplayController::new(),
|
||||
tick: 0,
|
||||
resume_tick: 0,
|
||||
is_active: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_recording(&mut self, state: &mut SharedGameState) {
|
||||
if !self.is_active {
|
||||
self.rng_seed = state.game_rng.dump_state();
|
||||
self.is_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop_recording(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
is_new_record: bool,
|
||||
) -> GameResult {
|
||||
state.replay_state = ReplayState::None;
|
||||
|
||||
self.write_replay(state, ctx, ReplayKind::Last)?;
|
||||
|
||||
if is_new_record {
|
||||
self.write_replay(state, ctx, ReplayKind::Best)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initialize_playback(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
replay_kind: ReplayKind,
|
||||
) -> GameResult {
|
||||
if !self.is_active {
|
||||
state.replay_state = ReplayState::Playback(replay_kind);
|
||||
self.read_replay(state, ctx, replay_kind)?;
|
||||
state.game_rng.load_state(self.rng_seed);
|
||||
self.is_active = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context, replay_kind: ReplayKind) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), replay_kind.get_suffix()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
file.write_u16::<LE>(0)?; // Space for versioning replay files
|
||||
file.write_u64::<LE>(self.rng_seed)?;
|
||||
for input in &self.keylist {
|
||||
file.write_u16::<LE>(*input)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context, replay_kind: ReplayKind) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::user_open(ctx, [state.get_rec_filename(), replay_kind.get_suffix()].join(""))
|
||||
{
|
||||
self.replay_version = file.read_u16::<LE>()?;
|
||||
self.rng_seed = file.read_u64::<LE>()?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
|
||||
let count = data.len() / 2;
|
||||
let mut inputs = Vec::new();
|
||||
let mut f = Cursor::new(data);
|
||||
|
||||
for _ in 0..count {
|
||||
inputs.push(f.read_u16::<LE>()?);
|
||||
}
|
||||
|
||||
self.keylist = inputs;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Context, &mut Player)> for Replay {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (ctx, player): (&mut Context, &mut Player)) -> GameResult {
|
||||
match state.replay_state {
|
||||
ReplayState::Recording => {
|
||||
// This mimics the KeyState bitfield
|
||||
let inputs = player.controller.move_left() as u16
|
||||
+ ((player.controller.move_right() as u16) << 1)
|
||||
+ ((player.controller.move_up() as u16) << 2)
|
||||
+ ((player.controller.move_down() as u16) << 3)
|
||||
+ ((player.controller.trigger_map() as u16) << 4)
|
||||
+ ((player.controller.trigger_inventory() as u16) << 5)
|
||||
+ (((player.controller.jump() || player.controller.trigger_menu_ok()) as u16) << 6)
|
||||
+ (((player.controller.shoot() || player.controller.trigger_menu_back()) as u16) << 7)
|
||||
+ ((player.controller.next_weapon() as u16) << 8)
|
||||
+ ((player.controller.prev_weapon() as u16) << 9)
|
||||
+ ((player.controller.trigger_menu_ok() as u16) << 11)
|
||||
+ ((player.controller.skip() as u16) << 12)
|
||||
+ ((player.controller.strafe() as u16) << 13);
|
||||
|
||||
self.keylist.push(inputs);
|
||||
}
|
||||
ReplayState::Playback(_) => {
|
||||
let pause = ctx.keyboard_context.is_key_pressed(ScanCode::Escape) && (self.tick - self.resume_tick > 3);
|
||||
|
||||
let next_input = if pause { 1 << 10 } else { *self.keylist.get(self.tick).unwrap_or(&0) };
|
||||
|
||||
self.controller.state = KeyState(next_input);
|
||||
self.controller.old_state = self.last_input;
|
||||
player.controller = Box::new(self.controller);
|
||||
|
||||
if !pause {
|
||||
self.last_input = KeyState(next_input);
|
||||
self.tick += 1;
|
||||
} else {
|
||||
self.resume_tick = self.tick;
|
||||
};
|
||||
|
||||
if self.tick >= self.keylist.len() {
|
||||
state.replay_state = ReplayState::None;
|
||||
player.controller = state.settings.create_player1_controller();
|
||||
}
|
||||
}
|
||||
ReplayState::None => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let x = state.canvas_size.0 - 32.0;
|
||||
let y = 8.0 + if state.settings.fps_counter { 12.0 } else { 0.0 };
|
||||
|
||||
match state.replay_state {
|
||||
ReplayState::None => {}
|
||||
ReplayState::Playback(_) => {
|
||||
state.font.builder()
|
||||
.position(x, y)
|
||||
.draw("PLAY", ctx, &state.constants, &mut state.texture_set)?;
|
||||
}
|
||||
ReplayState::Recording => {
|
||||
state.font.builder()
|
||||
.position(x, y)
|
||||
.draw("REC", ctx, &state.constants, &mut state.texture_set)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::scripting::tsc::text_script::ScriptMode;
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::ScriptMode;
|
||||
|
||||
pub struct StageSelect {
|
||||
pub current_teleport_slot: u8,
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::engine_constants::AnimatedFace;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::scripting::tsc::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptLine};
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::{Font, Symbols};
|
||||
|
||||
pub struct TextBoxes {
|
||||
pub slide_in: u8,
|
||||
pub anim_counter: usize,
|
||||
animated_face: AnimatedFace,
|
||||
}
|
||||
|
||||
const FACE_TEX: &str = "Face";
|
||||
const SWITCH_FACE_TEX: [&str; 5] = ["Face1", "Face2", "Face3", "Face4", "Face5"];
|
||||
|
||||
impl TextBoxes {
|
||||
pub fn new() -> TextBoxes {
|
||||
TextBoxes {
|
||||
slide_in: 7,
|
||||
anim_counter: 0,
|
||||
animated_face: AnimatedFace { face_id: 0, anim_id: 0, anim_frames: vec![(0, 0)] },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for TextBoxes {
|
||||
fn tick(&mut self, state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
if state.textscript_vm.face != 0 {
|
||||
self.slide_in = self.slide_in.saturating_sub(1);
|
||||
self.anim_counter = self.anim_counter.wrapping_add(1);
|
||||
|
||||
let face_num = state.textscript_vm.face % 100;
|
||||
let animation = state.textscript_vm.face % 1000 / 100;
|
||||
|
||||
if state.constants.textscript.animated_face_pics
|
||||
&& !state.settings.original_textures
|
||||
&& (self.animated_face.anim_id != animation || self.animated_face.face_id != face_num)
|
||||
{
|
||||
self.animated_face = state
|
||||
.constants
|
||||
.animated_face_table
|
||||
.clone()
|
||||
.into_iter()
|
||||
.find(|face| face.face_id == face_num && face.anim_id == animation)
|
||||
.unwrap_or_else(|| AnimatedFace { face_id: face_num, anim_id: 0, anim_frames: vec![(0, 0)] });
|
||||
}
|
||||
|
||||
if self.anim_counter > self.animated_face.anim_frames.first().unwrap().1 as usize {
|
||||
self.animated_face.anim_frames.rotate_left(1);
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !state.textscript_vm.flags.render() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (off_left, off_top, off_right, off_bottom) =
|
||||
crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
|
||||
let center = ((state.canvas_size.0 - off_left - off_right) / 2.0).floor();
|
||||
let top_pos = if state.textscript_vm.flags.position_top() {
|
||||
32.0 + off_top
|
||||
} else {
|
||||
state.canvas_size.1 as f32 - off_bottom - 66.0
|
||||
};
|
||||
let left_pos = off_left + center - 122.0;
|
||||
|
||||
{
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
if state.textscript_vm.flags.background_visible() {
|
||||
batch.add_rect(left_pos, top_pos, &state.constants.textscript.textbox_rect_top);
|
||||
for i in 1..7 {
|
||||
batch.add_rect(left_pos, top_pos + i as f32 * 8.0, &state.constants.textscript.textbox_rect_middle);
|
||||
}
|
||||
batch.add_rect(left_pos, top_pos + 56.0, &state.constants.textscript.textbox_rect_bottom);
|
||||
}
|
||||
|
||||
if state.textscript_vm.item != 0 {
|
||||
batch.add_rect(
|
||||
center - 40.0,
|
||||
state.canvas_size.1 - off_bottom - 112.0,
|
||||
&state.constants.textscript.get_item_top_left,
|
||||
);
|
||||
batch.add_rect(
|
||||
center - 40.0,
|
||||
state.canvas_size.1 - off_bottom - 96.0,
|
||||
&state.constants.textscript.get_item_bottom_left,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 112.0,
|
||||
&state.constants.textscript.get_item_top_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 104.0,
|
||||
&state.constants.textscript.get_item_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 96.0,
|
||||
&state.constants.textscript.get_item_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 88.0,
|
||||
&state.constants.textscript.get_item_bottom_right,
|
||||
);
|
||||
}
|
||||
|
||||
if let TextScriptExecutionState::WaitConfirmation(_, _, _, wait, selection) = state.textscript_vm.state {
|
||||
let pos_y = if wait > 14 {
|
||||
state.canvas_size.1 - off_bottom - 96.0 + 4.0 * (17 - wait) as f32
|
||||
} else {
|
||||
state.canvas_size.1 - off_bottom - 96.0
|
||||
};
|
||||
|
||||
batch.add_rect(center + 56.0, pos_y, &state.constants.textscript.textbox_rect_yes_no);
|
||||
|
||||
if wait == 0 {
|
||||
let pos_x = if selection == ConfirmSelection::No { 41.0 } else { 0.0 };
|
||||
|
||||
batch.add_rect(
|
||||
center + 51.0 + pos_x,
|
||||
pos_y + 10.0,
|
||||
&state.constants.textscript.textbox_rect_cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
if state.textscript_vm.face != 0 {
|
||||
let clip_rect = Rect::new_size(
|
||||
((left_pos + 14.0) * state.scale) as isize,
|
||||
((top_pos + 8.0) * state.scale) as isize,
|
||||
(48.0 * state.scale) as isize,
|
||||
(48.0 * state.scale) as isize,
|
||||
);
|
||||
|
||||
graphics::set_clip_rect(ctx, Some(clip_rect))?;
|
||||
|
||||
// switch version uses 1xxx flag to show a flipped version of face
|
||||
let flip = state.textscript_vm.face > 1000;
|
||||
let face_num = state.textscript_vm.face % 100;
|
||||
let animation_frame = self.animated_face.anim_frames.first().unwrap().0 as usize;
|
||||
|
||||
let tex_name = if state.constants.textscript.animated_face_pics && !state.settings.original_textures {
|
||||
SWITCH_FACE_TEX[animation_frame]
|
||||
} else {
|
||||
FACE_TEX
|
||||
};
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex_name)?;
|
||||
|
||||
let face_x = (4.0 + (6 - self.slide_in) as f32 * 8.0) - 52.0;
|
||||
|
||||
let final_x = left_pos + 14.0 + face_x;
|
||||
let final_y = top_pos + 8.0;
|
||||
let rect = Rect::new_size((face_num as u16 % 6) * 48, (face_num as u16 / 6) * 48, 48, 48);
|
||||
|
||||
if face_num >= 1 && face_num <= 4 && state.more_rust {
|
||||
// sue
|
||||
batch.add_rect_flip_tinted(final_x, final_y, flip, false, (200, 200, 255, 255), &rect);
|
||||
} else {
|
||||
batch.add_rect_flip(final_x, final_y, flip, false, &rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
}
|
||||
|
||||
if state.textscript_vm.item != 0 {
|
||||
let mut rect = Rect::new(0, 0, 0, 0);
|
||||
|
||||
if state.textscript_vm.item < 1000 {
|
||||
let item_id = state.textscript_vm.item as u16;
|
||||
|
||||
rect.left = (item_id % 16) * 16;
|
||||
rect.right = rect.left + 16;
|
||||
rect.top = (item_id / 16) * 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
batch.add_rect((center - 12.0).floor(), state.canvas_size.1 - off_bottom - 104.0, &rect);
|
||||
batch.draw(ctx)?;
|
||||
} else {
|
||||
let item_id = state.textscript_vm.item as u16 - 1000;
|
||||
|
||||
rect.left = (item_id % 8) * 32;
|
||||
rect.right = rect.left + 32;
|
||||
rect.top = (item_id / 8) * 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ItemImage")?;
|
||||
batch.add_rect((center - 20.0).floor(), state.canvas_size.1 - off_bottom - 104.0, &rect);
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
let text_offset = if state.textscript_vm.face == 0 { 0.0 } else { 56.0 };
|
||||
|
||||
let y_offset = if let TextScriptExecutionState::MsgNewLine(_, _, _, _, counter) = state.textscript_vm.state {
|
||||
16.0 - counter as f32 * 4.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let lines = [&state.textscript_vm.line_1, &state.textscript_vm.line_2, &state.textscript_vm.line_3];
|
||||
|
||||
let clip_rect = Rect::new_size(
|
||||
0,
|
||||
((top_pos + 6.0) * state.scale) as isize,
|
||||
state.screen_size.0 as isize,
|
||||
(48.0 * state.scale) as isize,
|
||||
);
|
||||
|
||||
graphics::set_clip_rect(ctx, Some(clip_rect))?;
|
||||
for (idx, line) in lines.iter().enumerate() {
|
||||
if !line.is_empty() {
|
||||
let symbols = Symbols { symbols: &state.textscript_vm.substitution_rect_map, texture: "TextBox" };
|
||||
|
||||
state
|
||||
.font
|
||||
.builder()
|
||||
.position(left_pos + text_offset + 14.0, top_pos + 10.0 + idx as f32 * 16.0 - y_offset)
|
||||
.shadow(state.constants.textscript.text_shadow)
|
||||
.with_symbols(Some(symbols))
|
||||
.draw_iter(line.iter().copied(), ctx, &state.constants, &mut state.texture_set)?;
|
||||
}
|
||||
}
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
|
||||
if let TextScriptExecutionState::WaitInput(_, _, tick) = state.textscript_vm.state {
|
||||
if tick > 10 {
|
||||
let builder = state
|
||||
.font
|
||||
.builder()
|
||||
.with_symbols(Some(Symbols { symbols: &state.textscript_vm.substitution_rect_map, texture: "" }));
|
||||
|
||||
let (mut x, y) = match state.textscript_vm.current_line {
|
||||
TextScriptLine::Line1 => {
|
||||
(builder.compute_width_iter(state.textscript_vm.line_1.iter().copied()), top_pos + 10.0)
|
||||
}
|
||||
TextScriptLine::Line2 => {
|
||||
(builder.compute_width_iter(state.textscript_vm.line_2.iter().copied()), top_pos + 10.0 + 16.0)
|
||||
}
|
||||
TextScriptLine::Line3 => {
|
||||
(builder.compute_width_iter(state.textscript_vm.line_3.iter().copied()), top_pos + 10.0 + 32.0)
|
||||
}
|
||||
};
|
||||
x += left_pos + text_offset + 14.0;
|
||||
|
||||
graphics::draw_rect(
|
||||
ctx,
|
||||
Rect::new_size(
|
||||
(x * state.scale) as isize,
|
||||
(y * state.scale) as isize,
|
||||
(5.0 * state.scale) as isize,
|
||||
(state.font.line_height() * state.scale) as isize,
|
||||
),
|
||||
Color::from_rgb(255, 255, 255),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
use crate::common::Rect;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::game::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
|
||||
pub struct Tilemap {
|
||||
tick: u32,
|
||||
prev_tick: u32,
|
||||
pub no_water: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TileLayer {
|
||||
Background,
|
||||
Middleground,
|
||||
Foreground,
|
||||
Snack,
|
||||
}
|
||||
|
||||
impl Tilemap {
|
||||
pub fn new() -> Self {
|
||||
Tilemap { tick: 0, prev_tick: 0, no_water: false }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> GameResult {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_prev(&mut self) -> GameResult {
|
||||
self.prev_tick = self.tick;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
frame: &Frame,
|
||||
layer: TileLayer,
|
||||
textures: &StageTexturePaths,
|
||||
stage: &Stage,
|
||||
) -> GameResult {
|
||||
if stage.map.tile_size == TileSize::Tile8x8 && layer == TileLayer::Snack {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tex = match layer {
|
||||
TileLayer::Snack => "Npc/NpcSym",
|
||||
TileLayer::Background => &textures.tileset_bg,
|
||||
TileLayer::Middleground => &textures.tileset_mg,
|
||||
TileLayer::Foreground => &textures.tileset_fg,
|
||||
};
|
||||
|
||||
let (layer_offset, layer_width, layer_height, uses_layers) = if let Some(pxpack_data) = &stage.data.pxpack_data
|
||||
{
|
||||
match layer {
|
||||
TileLayer::Background => {
|
||||
(pxpack_data.offset_bg as usize, pxpack_data.size_bg.0, pxpack_data.size_bg.1, true)
|
||||
}
|
||||
TileLayer::Middleground => {
|
||||
(pxpack_data.offset_mg as usize, pxpack_data.size_mg.0, pxpack_data.size_mg.1, true)
|
||||
}
|
||||
_ => (0, pxpack_data.size_fg.0, pxpack_data.size_fg.1, true),
|
||||
}
|
||||
} else {
|
||||
(0, stage.map.width, stage.map.height, false)
|
||||
};
|
||||
|
||||
if !uses_layers && layer == TileLayer::Middleground {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tile_size = state.tile_size.as_int();
|
||||
let tile_sizef = state.tile_size.as_float();
|
||||
let halft = tile_size / 2;
|
||||
let halftf = tile_sizef / 2.0;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex)?;
|
||||
let mut rect = Rect::new(0, 0, tile_size as u16, tile_size as u16);
|
||||
let (mut frame_x, mut frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
if let Some(pxpack_data) = &stage.data.pxpack_data {
|
||||
let (fx, fy) = match layer {
|
||||
TileLayer::Background => pxpack_data.scroll_bg.transform_camera_pos(frame_x, frame_y),
|
||||
TileLayer::Middleground => pxpack_data.scroll_mg.transform_camera_pos(frame_x, frame_y),
|
||||
_ => pxpack_data.scroll_fg.transform_camera_pos(frame_x, frame_y),
|
||||
};
|
||||
|
||||
frame_x = fx;
|
||||
frame_y = fy;
|
||||
}
|
||||
|
||||
let tile_start_x = (frame_x as i32 / tile_size).clamp(0, layer_width as i32) as usize;
|
||||
let tile_start_y = (frame_y as i32 / tile_size).clamp(0, layer_height as i32) as usize;
|
||||
let tile_end_x =
|
||||
((frame_x as i32 + 8 + state.canvas_size.0 as i32) / tile_size + 1).clamp(0, layer_width as i32) as usize;
|
||||
let tile_end_y = ((frame_y as i32 + halft + state.canvas_size.1 as i32) / tile_size + 1)
|
||||
.clamp(0, layer_height as i32) as usize;
|
||||
|
||||
if layer == TileLayer::Snack {
|
||||
rect = state.constants.world.snack_rect;
|
||||
}
|
||||
|
||||
for y in tile_start_y..tile_end_y {
|
||||
for x in tile_start_x..tile_end_x {
|
||||
let tile = *stage.map.tiles.get((y * layer_width as usize) + x + layer_offset).unwrap();
|
||||
match layer {
|
||||
_ if uses_layers => {
|
||||
if tile == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Background => {
|
||||
if stage.map.attrib[tile as usize] >= 0x20 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Foreground => {
|
||||
let attr = stage.map.attrib[tile as usize];
|
||||
|
||||
if attr < 0x40 || attr >= 0x80 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Snack => {
|
||||
if stage.map.attrib[tile as usize] != 0x43 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
batch.add_rect(
|
||||
(x as f32 * tile_sizef - halftf) - frame_x,
|
||||
(y as f32 * tile_sizef - halftf) - frame_y,
|
||||
&rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if !self.no_water && layer == TileLayer::Foreground && stage.data.background_type == BackgroundType::Water {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &textures.background)?;
|
||||
let rect_top = Rect { left: 0, top: 0, right: 32, bottom: 16 };
|
||||
let rect_middle = Rect { left: 0, top: 16, right: 32, bottom: 48 };
|
||||
|
||||
let tile_start_x = frame_x as i32 / 32;
|
||||
let tile_end_x = (frame_x + 16.0 + state.canvas_size.0) as i32 / 32 + 1;
|
||||
let water_y = state.water_level as f32 / 512.0;
|
||||
let tile_count_y = (frame_y + 16.0 + state.canvas_size.1 - water_y) as i32 / 32 + 1;
|
||||
|
||||
for x in tile_start_x..tile_end_x {
|
||||
batch.add_rect((x as f32 * 32.0) - frame_x, water_y - frame_y, &rect_top);
|
||||
|
||||
for y in 0..tile_count_y {
|
||||
batch.add_rect((x as f32 * 32.0) - frame_x, (y as f32 * 32.0) + water_y - frame_y, &rect_middle);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
if layer == TileLayer::Foreground {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
|
||||
|
||||
for y in tile_start_y..tile_end_y {
|
||||
for x in tile_start_x..tile_end_x {
|
||||
let tile = *stage.map.tiles.get((y * layer_width as usize) + x + layer_offset).unwrap();
|
||||
let attr = stage.map.attrib[tile as usize];
|
||||
|
||||
if ![0x80, 0x81, 0x82, 0x83, 0xA0, 0xA1, 0xA2, 0xA3].contains(&attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let shift =
|
||||
((self.tick as f64 + (self.tick - self.prev_tick) as f64 * state.frame_time) * 2.0) as u16 % 16;
|
||||
let mut push_rect = state.constants.world.water_push_rect;
|
||||
|
||||
match attr {
|
||||
0x80 | 0xA0 => {
|
||||
push_rect.left = push_rect.left + shift;
|
||||
push_rect.right = push_rect.right + shift;
|
||||
}
|
||||
0x81 | 0xA1 => {
|
||||
push_rect.top = push_rect.top + shift;
|
||||
push_rect.bottom = push_rect.bottom + shift;
|
||||
}
|
||||
0x82 | 0xA2 => {
|
||||
push_rect.left = push_rect.left - shift + state.tile_size.as_int() as u16;
|
||||
push_rect.right = push_rect.right - shift + state.tile_size.as_int() as u16;
|
||||
}
|
||||
0x83 | 0xA3 => {
|
||||
push_rect.top = push_rect.top - shift + state.tile_size.as_int() as u16;
|
||||
push_rect.bottom = push_rect.bottom - shift + state.tile_size.as_int() as u16;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
batch.add_rect(
|
||||
(x as f32 * tile_sizef - halftf) - frame_x,
|
||||
(y as f32 * tile_sizef - halftf) - frame_y,
|
||||
&push_rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,20 +1,29 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::backend::{BackendShader, VertexData};
|
||||
use crate::framework::backend::{BackendShader, SpriteBatchCommand, VertexData};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::map::WaterRegionType;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::map::{WaterParamEntry, WaterParams, WaterRegionType};
|
||||
use crate::game::physics::PhysicalEntity;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::stage::{BackgroundType, Stage};
|
||||
use crate::game::npc::list::NPCList;
|
||||
use crate::game::player::Player;
|
||||
|
||||
const TENSION: f32 = 0.03;
|
||||
const DAMPENING: f32 = 0.01;
|
||||
const SPREAD: f32 = 0.02;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum WaterLayer {
|
||||
Front,
|
||||
Back,
|
||||
}
|
||||
|
||||
struct DynamicWaterColumn {
|
||||
target_height: f32,
|
||||
height: f32,
|
||||
|
@ -33,14 +42,15 @@ impl DynamicWaterColumn {
|
|||
}
|
||||
|
||||
pub struct DynamicWater {
|
||||
x: u16,
|
||||
y: u16,
|
||||
end_x: u16,
|
||||
x: f32,
|
||||
y: f32,
|
||||
end_x: f32,
|
||||
columns: Vec<DynamicWaterColumn>,
|
||||
color: WaterParamEntry,
|
||||
}
|
||||
|
||||
impl DynamicWater {
|
||||
pub fn new(x: u16, y: u16, length: u16) -> DynamicWater {
|
||||
pub fn new(x: u16, y: u16, length: u16, color: WaterParamEntry) -> DynamicWater {
|
||||
let mut columns = Vec::new();
|
||||
let count = length as usize * 8 + 1;
|
||||
|
||||
|
@ -48,11 +58,11 @@ impl DynamicWater {
|
|||
columns.push(DynamicWaterColumn::new());
|
||||
}
|
||||
|
||||
DynamicWater { x, y, end_x: x + length, columns }
|
||||
DynamicWater { x: x as f32 * 16.0, y: y as f32 * 16.0, end_x: (x + length) as f32 * 16.0, columns, color }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
for col in self.columns.iter_mut() {
|
||||
for col in &mut self.columns {
|
||||
col.tick(DAMPENING, TENSION);
|
||||
}
|
||||
|
||||
|
@ -89,156 +99,259 @@ impl DynamicWater {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interact(&mut self, players: &[&Player], npc_list: &NPCList) {
|
||||
let cols_i32 = self.columns.len() as i32;
|
||||
|
||||
let mut tick_object = |obj: &dyn PhysicalEntity| {
|
||||
let obj_x = obj.x() as f32 / 512.0 + 8.0;
|
||||
let obj_y = obj.y() as f32 / 512.0 + 8.0;
|
||||
|
||||
if (obj.vel_y() > 0x80 || obj.vel_y() < -0x80)
|
||||
&& obj_x > self.x
|
||||
&& obj_x < self.end_x as f32
|
||||
&& obj_y > self.y - 5.0
|
||||
&& obj_y < self.y + 4.0
|
||||
{
|
||||
let col_idx_center = (((obj_x - self.x) / 2.0) as i32).clamp(0, cols_i32);
|
||||
let col_idx_left =
|
||||
(col_idx_center - (obj.hit_bounds().left as i32 / (8 * 0x200))).clamp(0, cols_i32) as usize;
|
||||
let col_idx_right =
|
||||
(col_idx_center + (obj.hit_bounds().left as i32 / (8 * 0x200))).clamp(0, cols_i32) as usize;
|
||||
|
||||
for col in &mut self.columns[col_idx_left..=col_idx_right] {
|
||||
col.speed = (obj.vel_y() as f32 / 512.0) * (obj.hit_rect_size() as f32 * 0.25).clamp(0.1, 1.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for player in players {
|
||||
tick_object(*player);
|
||||
}
|
||||
|
||||
for npc in npc_list.iter_alive() {
|
||||
static NO_COLL_NPCS: [u16; 6] = [0, 3, 4, 18, 191, 195];
|
||||
if NO_COLL_NPCS.contains(&npc.npc_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tick_object(npc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DepthRegion {
|
||||
rect: Rect<f32>,
|
||||
color: WaterParamEntry,
|
||||
}
|
||||
|
||||
impl DepthRegion {
|
||||
pub fn new_tile(rect: Rect<u16>, color: WaterParamEntry) -> DepthRegion {
|
||||
DepthRegion {
|
||||
rect: Rect {
|
||||
left: rect.left as f32 * 16.0,
|
||||
top: rect.top as f32 * 16.0,
|
||||
right: rect.right as f32 * 16.0,
|
||||
bottom: rect.bottom as f32 * 16.0,
|
||||
},
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(rect: Rect<f32>, color: WaterParamEntry) -> DepthRegion {
|
||||
DepthRegion { rect, color }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaterRenderer {
|
||||
depth_regions: Vec<Rect<u16>>,
|
||||
surf_regions: Vec<Rect<u16>>,
|
||||
depth_regions: Vec<DepthRegion>,
|
||||
water_surfaces: Vec<DynamicWater>,
|
||||
core_water: Option<(DynamicWater, DepthRegion)>,
|
||||
t: RefCell<u32>,
|
||||
}
|
||||
|
||||
impl WaterRenderer {
|
||||
pub fn new() -> WaterRenderer {
|
||||
WaterRenderer { depth_regions: Vec::new(), surf_regions: Vec::new(), water_surfaces: Vec::new() }
|
||||
WaterRenderer { depth_regions: Vec::new(), water_surfaces: Vec::new(), core_water: None, t: RefCell::new(0) }
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, regions: Vec<(WaterRegionType, Rect<u16>)>) {
|
||||
for (reg_type, bounds) in regions {
|
||||
pub fn initialize(
|
||||
&mut self,
|
||||
regions: Vec<(WaterRegionType, Rect<u16>, u8)>,
|
||||
water_params: &WaterParams,
|
||||
stage: &Stage,
|
||||
) {
|
||||
for (reg_type, bounds, color_idx) in regions {
|
||||
let color = water_params.get_entry(color_idx);
|
||||
|
||||
match reg_type {
|
||||
WaterRegionType::WaterLine => {
|
||||
self.surf_regions.push(bounds);
|
||||
self.water_surfaces.push(DynamicWater::new(bounds.left, bounds.top, bounds.width() + 1));
|
||||
self.water_surfaces.push(DynamicWater::new(bounds.left, bounds.top, bounds.width() + 1, *color));
|
||||
}
|
||||
WaterRegionType::WaterDepth => {
|
||||
self.depth_regions.push(bounds);
|
||||
self.depth_regions.push(DepthRegion::new_tile(bounds, *color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if stage.data.background_type == BackgroundType::Water {
|
||||
let core_water_color = water_params.get_entry(0);
|
||||
self.core_water = Some((
|
||||
DynamicWater::new(0, 32768, stage.map.width, *core_water_color),
|
||||
DepthRegion::new(
|
||||
Rect {
|
||||
left: 0.0,
|
||||
top: stage.map.height as f32 * 16.0,
|
||||
right: stage.map.width as f32 * 16.0,
|
||||
bottom: stage.map.height as f32 * 16.0 + 1.0,
|
||||
},
|
||||
*core_water_color,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&[&Player], &NPCList)> for WaterRenderer {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (players, npc_list): (&[&Player], &NPCList)) -> GameResult<()> {
|
||||
if !state.settings.shader_effects {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for surf in self.water_surfaces.iter_mut() {
|
||||
let line_x = surf.x as f32 * 16.0;
|
||||
let line_y = surf.y as f32 * 16.0;
|
||||
|
||||
let mut tick_object = |obj: &dyn PhysicalEntity| {
|
||||
let obj_x = obj.x() as f32 / 512.0 + 8.0;
|
||||
let obj_y = obj.y() as f32 / 512.0 + 8.0;
|
||||
|
||||
if (obj.vel_y() > 0x80 || obj.vel_y() < -0x80)
|
||||
&& obj_x > line_x
|
||||
&& obj_x < surf.end_x as f32 * 16.0
|
||||
&& obj_y > line_y - 5.0
|
||||
&& obj_y < line_y + 4.0
|
||||
{
|
||||
let col_idx_center = (((obj_x - line_x) / 2.0) as i32).clamp(0, surf.columns.len() as i32);
|
||||
let col_idx_left = (col_idx_center - (obj.hit_bounds().left as i32 / (8 * 0x200)))
|
||||
.clamp(0, surf.columns.len() as i32) as usize;
|
||||
let col_idx_right = (col_idx_center + (obj.hit_bounds().left as i32 / (8 * 0x200)))
|
||||
.clamp(0, surf.columns.len() as i32) as usize;
|
||||
|
||||
for col in surf.columns[col_idx_left..=col_idx_right].iter_mut() {
|
||||
col.speed = (obj.vel_y() as f32 / 512.0) * (obj.hit_rect_size() as f32 * 0.25).clamp(0.1, 1.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for player in players {
|
||||
tick_object(*player);
|
||||
}
|
||||
|
||||
for npc in npc_list.iter_alive() {
|
||||
static NO_COLL_NPCS: [u16; 3] = [0, 3, 4];
|
||||
if NO_COLL_NPCS.contains(&npc.npc_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tick_object(npc);
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, state: &mut SharedGameState, (players, npc_list): (&[&Player], &NPCList)) -> GameResult<()> {
|
||||
for surf in &mut self.water_surfaces {
|
||||
surf.interact(players, npc_list);
|
||||
surf.tick();
|
||||
}
|
||||
|
||||
if let Some((ref mut core_water, ref mut core_depth)) = &mut self.core_water {
|
||||
let level = state.water_level as f32 / 512.0 + 8.0;
|
||||
core_water.y = level;
|
||||
core_depth.rect.top = (level + 16.0).min(core_depth.rect.bottom);
|
||||
|
||||
core_water.interact(players, npc_list);
|
||||
core_water.tick();
|
||||
}
|
||||
|
||||
let mut t_ref = self.t.borrow_mut();
|
||||
*t_ref = t_ref.wrapping_add(1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
|
||||
let mut out_rect = Rect::new(0, 0, 0, 0);
|
||||
let (o_x, o_y) = frame.xy_interpolated(state.frame_time);
|
||||
let water_color_top = Color::from_rgba(102, 153, 204, 150);
|
||||
let water_color = Color::from_rgba(102, 153, 204, 75);
|
||||
|
||||
for region in self.depth_regions.iter() {
|
||||
out_rect.left = ((region.left as f32 * 16.0 - o_x - 8.0) * state.scale) as isize;
|
||||
out_rect.top = ((region.top as f32 * 16.0 - o_y - 8.0) * state.scale) as isize;
|
||||
out_rect.right = ((region.right as f32 * 16.0 - o_x + 8.0) * state.scale) as isize;
|
||||
out_rect.bottom = ((region.bottom as f32 * 16.0 - o_y + 8.0) * state.scale) as isize;
|
||||
graphics::draw_rect(ctx, out_rect, water_color)?;
|
||||
}
|
||||
|
||||
if !state.settings.shader_effects || !graphics::supports_vertex_draw(ctx)? {
|
||||
for region in self.surf_regions.iter() {
|
||||
out_rect.left = ((region.left as f32 * 16.0 - o_x - 8.0) * state.scale) as isize;
|
||||
out_rect.top = ((region.top as f32 * 16.0 - o_y - 5.0) * state.scale) as isize;
|
||||
out_rect.right = ((region.right as f32 * 16.0 - o_x + 8.0) * state.scale) as isize;
|
||||
out_rect.bottom = ((region.bottom as f32 * 16.0 - o_y + 8.0) * state.scale) as isize;
|
||||
graphics::draw_rect(ctx, out_rect, water_color)?;
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
frame: &Frame,
|
||||
layer: WaterLayer,
|
||||
) -> GameResult<()> {
|
||||
if !graphics::supports_vertex_draw(ctx)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
graphics::set_render_target(ctx, state.lightmap_canvas.as_ref())?;
|
||||
graphics::clear(ctx, Color::from_rgba(0, 0, 0, 0));
|
||||
graphics::set_blend_mode(ctx, BlendMode::None)?;
|
||||
|
||||
let (o_x, o_y) = frame.xy_interpolated(state.frame_time);
|
||||
let uv = (0.0, 0.0);
|
||||
let color_top_rgba = water_color_top.to_rgba();
|
||||
let color_mid_rgba = water_color.to_rgba();
|
||||
let color_btm_rgba = water_color.to_rgba();
|
||||
let t = *self.t.borrow_mut() as f32 + state.frame_time as f32;
|
||||
let shader = BackendShader::WaterFill(state.scale, t, (o_x, o_y));
|
||||
let mut vertices = Vec::new();
|
||||
|
||||
for surf in self.water_surfaces.iter() {
|
||||
let pos_x = surf.x as f32 * 16.0;
|
||||
let pos_y = surf.y as f32 * 16.0;
|
||||
{
|
||||
let mut draw_region = |region: &DepthRegion| -> GameResult {
|
||||
let color_mid_rgba = region.color.color_middle.to_rgba();
|
||||
let color_btm_rgba = region.color.color_bottom.to_rgba();
|
||||
vertices.clear();
|
||||
vertices.reserve(6);
|
||||
|
||||
if (pos_x - o_x - 16.0) > state.canvas_size.0
|
||||
|| (pos_x - o_x + 16.0 + surf.end_x as f32 * 16.0) < 0.0
|
||||
|| (pos_y - o_y - 16.0) > state.canvas_size.1
|
||||
|| (pos_y - o_y + 16.0) < 0.0
|
||||
{
|
||||
continue;
|
||||
let left = (region.rect.left - o_x - 8.0) * state.scale;
|
||||
let top = (region.rect.top - o_y - 8.0) * state.scale;
|
||||
let right = (region.rect.right - o_x + 8.0) * state.scale;
|
||||
let bottom = (region.rect.bottom - o_y + 8.0) * state.scale;
|
||||
|
||||
vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (left, top), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (right, bottom), uv, color: color_btm_rgba });
|
||||
|
||||
graphics::draw_triangle_list(ctx, &vertices, None, shader)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if layer == WaterLayer::Back {
|
||||
for region in &self.depth_regions {
|
||||
draw_region(region)?;
|
||||
}
|
||||
} else if let Some((_, ref core_depth)) = &self.core_water {
|
||||
draw_region(core_depth)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut vertices = vec![];
|
||||
vertices.reserve(12 * surf.columns.len());
|
||||
{
|
||||
let mut draw_region = |surf: &DynamicWater| -> GameResult {
|
||||
let pos_x = surf.x;
|
||||
let pos_y = surf.y;
|
||||
let color_top_rgba = surf.color.color_top.to_rgba();
|
||||
let color_mid_rgba = surf.color.color_middle.to_rgba();
|
||||
let color_btm_rgba = surf.color.color_bottom.to_rgba();
|
||||
|
||||
let bottom = (pos_y - o_y + 8.0) * state.scale;
|
||||
for i in 1..surf.columns.len() {
|
||||
let x_right = (pos_x - 8.0 - o_x + i as f32 * 2.0) * state.scale;
|
||||
let x_left = x_right - 2.0 * state.scale;
|
||||
let top_left = (pos_y - o_y - 13.0 + surf.columns[i - 1].height) * state.scale;
|
||||
let top_right = (pos_y - o_y - 13.0 + surf.columns[i].height) * state.scale;
|
||||
let middle_left = top_left + 6.0 * state.scale;
|
||||
let middle_right = top_left + 6.0 * state.scale;
|
||||
if (pos_x - o_x - 16.0) > state.canvas_size.0
|
||||
|| (pos_x - o_x + 16.0 + surf.end_x) < 0.0
|
||||
|| (pos_y - o_y - 16.0) > state.canvas_size.1
|
||||
|| (pos_y - o_y + 16.0) < 0.0
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, top_left), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.clear();
|
||||
vertices.reserve(12 * surf.columns.len());
|
||||
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, bottom), uv, color: color_btm_rgba });
|
||||
let bottom = (pos_y - o_y + 8.0) * state.scale;
|
||||
for i in 1..surf.columns.len() {
|
||||
let x_right = (pos_x - 8.0 - o_x + i as f32 * 2.0) * state.scale;
|
||||
let x_left = x_right - 2.0 * state.scale;
|
||||
let top_left = (pos_y - o_y - 13.0 + surf.columns[i - 1].height) * state.scale;
|
||||
let top_right = (pos_y - o_y - 13.0 + surf.columns[i].height) * state.scale;
|
||||
let middle_left = top_left + 6.0 * state.scale;
|
||||
let middle_right = top_left + 6.0 * state.scale;
|
||||
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, top_left), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, bottom), uv, color: color_btm_rgba });
|
||||
}
|
||||
|
||||
graphics::draw_triangle_list(ctx, &vertices, None, shader)?;
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if layer == WaterLayer::Back {
|
||||
for surf in &self.water_surfaces {
|
||||
draw_region(surf)?;
|
||||
}
|
||||
} else if let Some((ref surf, _)) = &self.core_water {
|
||||
draw_region(surf)?;
|
||||
}
|
||||
}
|
||||
|
||||
graphics::draw_triangle_list(ctx, vertices, None, BackendShader::Fill)?;
|
||||
graphics::set_blend_mode(ctx, BlendMode::Alpha)?;
|
||||
graphics::set_render_target(ctx, None)?;
|
||||
|
||||
{
|
||||
let canvas = state.lightmap_canvas.as_mut().unwrap();
|
||||
let rect = Rect { left: 0.0, top: 0.0, right: state.screen_size.0, bottom: state.screen_size.1 };
|
||||
|
||||
canvas.clear();
|
||||
canvas.add(SpriteBatchCommand::DrawRect(rect, rect));
|
||||
canvas.draw()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
use crate::common::{Direction, interpolate_fix9_scale, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::player::{Player, TargetPlayer};
|
||||
use crate::game::weapon::bullet::{Bullet, BulletManager};
|
||||
|
||||
pub struct WhimsicalStar {
|
||||
pub star: [Star; 3],
|
||||
pub tex: String,
|
||||
pub star_count: u8,
|
||||
pub equipped: bool,
|
||||
pub active_star: u8,
|
||||
}
|
||||
|
||||
pub struct Star {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
pub vel_x: i32,
|
||||
pub vel_y: i32,
|
||||
pub rect: Rect<u16>,
|
||||
}
|
||||
|
||||
impl Star {
|
||||
fn new(vel_x: i32, vel_y: i32) -> Star {
|
||||
Star { x: 0, y: 0, vel_x, vel_y, prev_x: 0, prev_y: 0, rect: Rect::new(0, 0, 0, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl WhimsicalStar {
|
||||
pub fn new() -> WhimsicalStar {
|
||||
WhimsicalStar {
|
||||
star: [Star::new(0x400, -0x200), Star::new(-0x200, 0x400), Star::new(0x200, 0x200)],
|
||||
tex: "MyChar".to_string(),
|
||||
star_count: 0,
|
||||
equipped: false,
|
||||
active_star: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, player: &Player) {
|
||||
self.tex = player.skin.get_skin_texture_name().to_string();
|
||||
for (iter, star) in &mut self.star.iter_mut().enumerate() {
|
||||
star.rect = player.skin.get_whimsical_star_rect(iter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prev(&mut self) {
|
||||
for star in &mut self.star {
|
||||
star.prev_x = star.x;
|
||||
star.prev_y = star.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&Player, &mut BulletManager)> for WhimsicalStar {
|
||||
fn tick(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
(player, bullet_manager): (&Player, &mut BulletManager),
|
||||
) -> GameResult {
|
||||
if !self.equipped && player.equip.has_whimsical_star() {
|
||||
for star in &mut self.star {
|
||||
star.x = player.x;
|
||||
star.y = player.y;
|
||||
}
|
||||
self.equipped = true;
|
||||
}
|
||||
|
||||
if !player.equip.has_whimsical_star() {
|
||||
self.equipped = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.star_count = player.stars;
|
||||
|
||||
let mut prev_x = player.x;
|
||||
let mut prev_y = player.y;
|
||||
|
||||
for star in &mut self.star {
|
||||
star.vel_x += if prev_x >= star.x { 0x80 } else { -0x80 };
|
||||
star.vel_y += if prev_y >= star.y { 0xAA } else { -0xAA };
|
||||
|
||||
star.vel_x = star.vel_x.clamp(-0xA00, 0xA00);
|
||||
star.vel_y = star.vel_y.clamp(-0xA00, 0xA00);
|
||||
|
||||
star.x += star.vel_x;
|
||||
star.y += star.vel_y;
|
||||
|
||||
prev_x = star.x;
|
||||
prev_y = star.y;
|
||||
}
|
||||
|
||||
// Only one star can deal damage per tick
|
||||
self.active_star += 1;
|
||||
self.active_star %= 3;
|
||||
|
||||
if self.active_star < self.star_count && state.control_flags.control_enabled() {
|
||||
let bullet = Bullet::new(
|
||||
self.star[self.active_star as usize].x,
|
||||
self.star[self.active_star as usize].y,
|
||||
45,
|
||||
TargetPlayer::Player1,
|
||||
Direction::Left,
|
||||
&state.constants,
|
||||
);
|
||||
bullet_manager.push_bullet(bullet);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
|
||||
if !self.equipped {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex)?;
|
||||
|
||||
let (active_stars, _) = self.star.split_at(self.star_count as usize);
|
||||
|
||||
for star in active_stars {
|
||||
let x = interpolate_fix9_scale(star.prev_x as i32, star.x as i32, state.frame_time) - frame_x;
|
||||
let y = interpolate_fix9_scale(star.prev_y as i32, star.y as i32, state.frame_time) - frame_y;
|
||||
batch.add_rect(x, y, &star.rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 276 KiB |
After Width: | Height: | Size: 447 B |
After Width: | Height: | Size: 456 B |
After Width: | Height: | Size: 562 B |
After Width: | Height: | Size: 621 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.5 KiB |