mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 18:31:26 -10:00
Compare commits
1070
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bb0c77bb5 | ||
|
|
e81001f6a6 | ||
|
|
945427a849 | ||
|
|
9d1c93041e | ||
|
|
5fb9a6f0c6 | ||
|
|
e2a359b546 | ||
|
|
e48011d175 | ||
|
|
8766c642fe | ||
|
|
565f4d469c | ||
|
|
8e7476c1b8 | ||
|
|
d41020222e | ||
|
|
9c1cf58f8d | ||
|
|
183db641d7 | ||
|
|
10a5a0d29a | ||
|
|
830ecf2ea2 | ||
|
|
472fd6f20f | ||
|
|
262e4d4489 | ||
|
|
c9d71ce1ab | ||
|
|
e742bd108a | ||
|
|
990d0b7784 | ||
|
|
7c5371af1a | ||
|
|
99972a17c6 | ||
|
|
2e743d9cd6 | ||
|
|
b7681cfee9 | ||
|
|
5eb924cedc | ||
|
|
d561cd87c4 | ||
|
|
562522e098 | ||
|
|
f7bfd637d8 | ||
|
|
235857ca10 | ||
|
|
f529fbc633 | ||
|
|
22b16c32d4 | ||
|
|
b1298c2b68 | ||
|
|
2ec18afe02 | ||
|
|
7f742a2145 | ||
|
|
b96d9f6e62 | ||
|
|
eaa3fe0979 | ||
|
|
1cd16b7281 | ||
|
|
44ce5e9b00 | ||
|
|
13b922207b | ||
|
|
efc686db2d | ||
|
|
2738919cc9 | ||
|
|
7e9121ed8e | ||
|
|
f0712cfb41 | ||
|
|
af8422cf7b | ||
|
|
39a16019e3 | ||
|
|
bbca518524 | ||
|
|
bb28c37829 | ||
|
|
ad2335cfbf | ||
|
|
7bf7bf5c1d | ||
|
|
99b7c89883 | ||
|
|
370419b1a2 | ||
|
|
17d1924897 | ||
|
|
a351e72895 | ||
|
|
cd4c6e483a | ||
|
|
82c36b1c59 | ||
|
|
47f84846e3 | ||
|
|
2fe789f87b | ||
|
|
f850725c14 | ||
|
|
344eb5a7ed | ||
|
|
10b357172a | ||
|
|
4892ac7b01 | ||
|
|
4474b0087d | ||
|
|
ee1b6263ba | ||
|
|
73ced78633 | ||
|
|
50266c5065 | ||
|
|
00f4797b45 | ||
|
|
2377b5dff2 | ||
|
|
f8c41ac377 | ||
|
|
aee9dc6910 | ||
|
|
2e5a6871c5 | ||
|
|
535b0857b6 | ||
|
|
2fa7ddc552 | ||
|
|
a6f821daee | ||
|
|
360198b99f | ||
|
|
7fe62aabcd | ||
|
|
d2859f4979 | ||
|
|
643a87a7f1 | ||
|
|
d1d78c980e | ||
|
|
148df94e50 | ||
|
|
d443e9e7d0 | ||
|
|
e1bbbf9f9a | ||
|
|
fdafe21684 | ||
|
|
7ccd13bbce | ||
|
|
9f45f49ec1 | ||
|
|
6b5124a8ad | ||
|
|
1187ebbc2c | ||
|
|
d92fab80ef | ||
|
|
1b9dd78233 | ||
|
|
ebb5ed9ba6 | ||
|
|
dabdc871a6 | ||
|
|
77a255af63 | ||
|
|
45c904c25e | ||
|
|
3d0bd54980 | ||
|
|
7228244f0c | ||
|
|
f0f5679731 | ||
|
|
1306a23fd4 | ||
|
|
f067a19583 | ||
|
|
8ad52cd8e0 | ||
|
|
a27976bf9a | ||
|
|
deefbf0860 | ||
|
|
c0f9ad7b3c | ||
|
|
482cc7d7a6 | ||
|
|
18754fafc1 | ||
|
|
d05b3fa637 | ||
|
|
a26367db5f | ||
|
|
e3617169ca | ||
|
|
3c487f81f3 | ||
|
|
878dd4b313 | ||
|
|
74d95f01bc | ||
|
|
226d51c214 | ||
|
|
9855062732 | ||
|
|
5709e24e9a | ||
|
|
13c410e864 | ||
|
|
5f4729bddb | ||
|
|
65fa916cba | ||
|
|
473acced54 | ||
|
|
b58b60b555 | ||
|
|
9c3b90f403 | ||
|
|
3b0e02545e | ||
|
|
9e6e1ef123 | ||
|
|
2e4e9855bc | ||
|
|
9adc89a5a2 | ||
|
|
8faa521df9 | ||
|
|
a3513c38b6 | ||
|
|
15901e430f | ||
|
|
37584f576c | ||
|
|
3863d9b5a9 | ||
|
|
5318fba077 | ||
|
|
d187a6ae99 | ||
|
|
30b35b483b | ||
|
|
f7f0c2ba21 | ||
|
|
f1362a694d | ||
|
|
db55349f70 | ||
|
|
2d84fdf5c6 | ||
|
|
5e7a6ece52 | ||
|
|
ff62ea010e | ||
|
|
a14a746a4f | ||
|
|
d4c0c45595 | ||
|
|
06eaeca9d7 | ||
|
|
dd5c92a910 | ||
|
|
2de1776308 | ||
|
|
833b51e423 | ||
|
|
726d66d0ad | ||
|
|
c7e2b3c78f | ||
|
|
97a93162f8 | ||
|
|
1fa270ad66 | ||
|
|
5b316a562d | ||
|
|
9c14717b81 | ||
|
|
0b8bbb3f47 | ||
|
|
314833ae61 | ||
|
|
4a4d07872a | ||
|
|
fdc33d8ab5 | ||
|
|
c0430f0b55 | ||
|
|
0d2d94a49b | ||
|
|
4f46fff48c | ||
|
|
1b1d864c59 | ||
|
|
f1ddfafdf3 | ||
|
|
3481032ae5 | ||
|
|
3a4e5d75a3 | ||
|
|
84b25b52d7 | ||
|
|
f2d3aaaf20 | ||
|
|
ffbcdab9da | ||
|
|
8db2f374f2 | ||
|
|
46959276f5 | ||
|
|
d5afd8d79b | ||
|
|
2a8064a3e9 | ||
|
|
9741b36442 | ||
|
|
0fe59e10ad | ||
|
|
e287c575ce | ||
|
|
b14a27cdff | ||
|
|
63b0f0a373 | ||
|
|
c464a1cd4c | ||
|
|
6889ee856f | ||
|
|
292f1547c4 | ||
|
|
a687fe0aef | ||
|
|
31fa21bbf1 | ||
|
|
be6a7f552a | ||
|
|
93da6616f7 | ||
|
|
4aeaabf337 | ||
|
|
589071ac46 | ||
|
|
ccf0e7eb04 | ||
|
|
adb7d9d195 | ||
|
|
7a2a4b6640 | ||
|
|
e4f9ca05c5 | ||
|
|
0160fac2ff | ||
|
|
cc2d06d37c | ||
|
|
c5b29c62c6 | ||
|
|
8e2d93211c | ||
|
|
eca6647dbf | ||
|
|
b6e214a4d4 | ||
|
|
841a4b8cea | ||
|
|
19bc18cb32 | ||
|
|
823c76ec22 | ||
|
|
a75ad91518 | ||
|
|
bdefb55ec9 | ||
|
|
00f7f13371 | ||
|
|
1441cc8e85 | ||
|
|
a8a9127d81 | ||
|
|
028168d395 | ||
|
|
7dae8940d7 | ||
|
|
814d44c986 | ||
|
|
99fb2c00ab | ||
|
|
f7c3ab85d3 | ||
|
|
d03a1007df | ||
|
|
b6fe676072 | ||
|
|
07f4f4716b | ||
|
|
30e48c1b03 | ||
|
|
352fd3cdfc | ||
|
|
1becca7cbe | ||
|
|
07312ff4c0 | ||
|
|
f9c73e81f2 | ||
|
|
e2585a9a0e | ||
|
|
d6c65d5139 | ||
|
|
c3551f4361 | ||
|
|
d787bd7c6f | ||
|
|
7dd454f563 | ||
|
|
f1deee8566 | ||
|
|
b24289e918 | ||
|
|
94b1d11f47 | ||
|
|
c541ac91db | ||
|
|
eb6f7fb013 | ||
|
|
d2c24efeff | ||
|
|
318fed546f | ||
|
|
474f225672 | ||
|
|
14337c22b0 | ||
|
|
647334f4ff | ||
|
|
03d51ba400 | ||
|
|
3767758ae5 | ||
|
|
def814fa4e | ||
|
|
11c5af39d8 | ||
|
|
c6b75d3561 | ||
|
|
866a7c40df | ||
|
|
5076066627 | ||
|
|
70546b68a5 | ||
|
|
a6084ba707 | ||
|
|
27650ef45d | ||
|
|
4f5fb48f79 | ||
|
|
c1d2920526 | ||
|
|
90558a5858 | ||
|
|
390f979b7c | ||
|
|
7258861791 | ||
|
|
76b944e01d | ||
|
|
8006baec65 | ||
|
|
443e148869 | ||
|
|
f9265301af | ||
|
|
bd6dae97a7 | ||
|
|
74c6b0c3cb | ||
|
|
3685a78ea9 | ||
|
|
d0948e67e3 | ||
|
|
3660761d7c | ||
|
|
e6579fca09 | ||
|
|
da0459feeb | ||
|
|
b428899d5b | ||
|
|
00ec3431b7 | ||
|
|
353d66c17d | ||
|
|
84ceae724c | ||
|
|
c47518ec6e | ||
|
|
76c9583e45 | ||
|
|
131fadfab3 | ||
|
|
74233c029c | ||
|
|
c1d8834d81 | ||
|
|
7b69c10e56 | ||
|
|
c0bb5bc8eb | ||
|
|
2e56b3ca61 | ||
|
|
3c3f79bc8f | ||
|
|
462a234e81 | ||
|
|
eaf14760c4 | ||
|
|
a9e5505a7c | ||
|
|
1525b520d7 | ||
|
|
1843f76d04 | ||
|
|
41cf0b3d52 | ||
|
|
990b5a3908 | ||
|
|
e4661b00b1 | ||
|
|
4457471ec5 | ||
|
|
128107ea43 | ||
|
|
c840d3e032 | ||
|
|
35a09527e5 | ||
|
|
20e5f35fb4 | ||
|
|
d217873281 | ||
|
|
37c5041c03 | ||
|
|
fb657c081c | ||
|
|
7945baa751 | ||
|
|
e9a2ff0ebf | ||
|
|
4328b66a28 | ||
|
|
11c5bcda74 | ||
|
|
43f9a48cb9 | ||
|
|
c6e79a61ea | ||
|
|
5d58d9ca7a | ||
|
|
384b2e20c7 | ||
|
|
20e742ed61 | ||
|
|
29846e2101 | ||
|
|
a1635cdd76 | ||
|
|
c80144ce44 | ||
|
|
6e11f356ea | ||
|
|
7e4190b97e | ||
|
|
65e65b01b6 | ||
|
|
f3773388b9 | ||
|
|
b05132a64f | ||
|
|
6c399ccace | ||
|
|
373a8b940a | ||
|
|
b4d0db8846 | ||
|
|
9c0575f173 | ||
|
|
64f5ad925b | ||
|
|
81b06fec41 | ||
|
|
3c553665aa | ||
|
|
f252473b84 | ||
|
|
1caf54e909 | ||
|
|
6aecc4660d | ||
|
|
c915bfd474 | ||
|
|
bd2356600f | ||
|
|
9c6cc422ac | ||
|
|
7bd74b2a7b | ||
|
|
9d5eeb6e08 | ||
|
|
5ffc05fa85 | ||
|
|
8ae77e0356 | ||
|
|
a113897c88 | ||
|
|
4ee04f2492 | ||
|
|
093a471602 | ||
|
|
22be402de5 | ||
|
|
84133c42a8 | ||
|
|
df1c518c3b | ||
|
|
8ac4c54d43 | ||
|
|
1445e778f0 | ||
|
|
2b6041f766 | ||
|
|
59d30157d8 | ||
|
|
552201884b | ||
|
|
e053d06a5f | ||
|
|
f677767846 | ||
|
|
9b35a4e653 | ||
|
|
6c0bfd4df5 | ||
|
|
102bc28424 | ||
|
|
081e34815c | ||
|
|
6cf5553147 | ||
|
|
80f4f1abde | ||
|
|
2e30674823 | ||
|
|
448d0457c8 | ||
|
|
9d7acbcc9d | ||
|
|
c8cee4cdb1 | ||
|
|
21aa608eb6 | ||
|
|
d6a0d9fb02 | ||
|
|
edcdbd02b6 | ||
|
|
d49e24fa0c | ||
|
|
e74ebe6b84 | ||
|
|
c197755c0e | ||
|
|
ec4de81a79 | ||
|
|
c10d6ececf | ||
|
|
6c2ebab01c | ||
|
|
9a682ea32f | ||
|
|
a5f501b58a | ||
|
|
5f8cdb37bd | ||
|
|
f998215b07 | ||
|
|
106b16d5eb | ||
|
|
61faa3fa9a | ||
|
|
3a960db633 | ||
|
|
b89d395578 | ||
|
|
f33b42085e | ||
|
|
87f449415f | ||
|
|
a59bb99fc6 | ||
|
|
ca6cc21be8 | ||
|
|
b52ab8bd4e | ||
|
|
a5a2f9e3df | ||
|
|
40840ee3f9 | ||
|
|
76af0129f3 | ||
|
|
f91d3dbe17 | ||
|
|
5ad373502c | ||
|
|
37c9ecd0e3 | ||
|
|
b92cbaff64 | ||
|
|
ed69a1584b | ||
|
|
789243ea06 | ||
|
|
2701b557b8 | ||
|
|
b4fe5c9cbe | ||
|
|
f1ee621624 | ||
|
|
bcf87e8a02 | ||
|
|
3d6fe919da | ||
|
|
2e272cce2d | ||
|
|
309bfd7825 | ||
|
|
308794801c | ||
|
|
0b26662755 | ||
|
|
8fe55fa659 | ||
|
|
9f1e556031 | ||
|
|
d77554767d | ||
|
|
52e2cda4a0 | ||
|
|
3a888906c5 | ||
|
|
638a25ea0d | ||
|
|
6d60647a2a | ||
|
|
cd2e69eb3b | ||
|
|
e92ba1849b | ||
|
|
ce9a0d5e08 | ||
|
|
d971f34ee2 | ||
|
|
47f6d2b368 | ||
|
|
144d484c85 | ||
|
|
3c809a9bb5 | ||
|
|
1c1ee910fb | ||
|
|
6f2e707f5a | ||
|
|
3a427d923b | ||
|
|
a663a7d916 | ||
|
|
42d96d967e | ||
|
|
a3c5186635 | ||
|
|
cf7fa02e6d | ||
|
|
72aa1eebc9 | ||
|
|
28f60df7fa | ||
|
|
bed18012d6 | ||
|
|
d43f30053d | ||
|
|
76dccbe07d | ||
|
|
3eafcde138 | ||
|
|
ca7065df46 | ||
|
|
cedf66cb31 | ||
|
|
03a3fbdc1c | ||
|
|
9c6661e0e8 | ||
|
|
6bea921fbe | ||
|
|
79f5eda0d6 | ||
|
|
4e4c8449fd | ||
|
|
7980891b48 | ||
|
|
3aa61dfccb | ||
|
|
a6a7482467 | ||
|
|
0febfb3545 | ||
|
|
e1c8f5d2ed | ||
|
|
6ce2a445fe | ||
|
|
96ae629ec6 | ||
|
|
1b9577eb85 | ||
|
|
beeb18b46c | ||
|
|
44343f8d1e | ||
|
|
424c69d1e5 | ||
|
|
6d1354f1f6 | ||
|
|
d5c4f3e5c7 | ||
|
|
89bd64f4ab | ||
|
|
417739665c | ||
|
|
11f2920b4f | ||
|
|
9e4ab292ac | ||
|
|
51addab005 | ||
|
|
e236a79773 | ||
|
|
d523f0aef3 | ||
|
|
697c009fb3 | ||
|
|
1f918fd4c6 | ||
|
|
3ea7a6d1ba | ||
|
|
743989525b | ||
|
|
5bca1417c2 | ||
|
|
633cd88e83 | ||
|
|
7431aba995 | ||
|
|
328bebe83a | ||
|
|
94e4f3d4cd | ||
|
|
b57573c439 | ||
|
|
0b1d1eed56 | ||
|
|
123feba557 | ||
|
|
8c64d73a18 | ||
|
|
e156f15561 | ||
|
|
3e1d89ba53 | ||
|
|
4f7bad2a5c | ||
|
|
dd621e8904 | ||
|
|
396d2c55a9 | ||
|
|
8f5064b353 | ||
|
|
782f6f100d | ||
|
|
6ec1f45997 | ||
|
|
4f76c441d6 | ||
|
|
a2bd895b7b | ||
|
|
a4cf53694d | ||
|
|
6bd7a43e3d | ||
|
|
fffc0ebf8b | ||
|
|
f2426de80b | ||
|
|
27f6088117 | ||
|
|
30c76f36ce | ||
|
|
35094851f2 | ||
|
|
b562cf822e | ||
|
|
62942557d5 | ||
|
|
4d68bed436 | ||
|
|
19d5b8e68e | ||
|
|
9dc50706a7 | ||
|
|
f2a0e35959 | ||
|
|
e637eea2fb | ||
|
|
f3e3769056 | ||
|
|
e218f687a9 | ||
|
|
5e10fccdbf | ||
|
|
12672d0c2b | ||
|
|
de7081fbb3 | ||
|
|
d73e4d4dec | ||
|
|
aeefcb4657 | ||
|
|
023dcfca76 | ||
|
|
c6e9f954f0 | ||
|
|
961d5eb171 | ||
|
|
1a66c97c2e | ||
|
|
00e2c2ad52 | ||
|
|
8991fc67cd | ||
|
|
7641abf41c | ||
|
|
871f7a822e | ||
|
|
ca66664eac | ||
|
|
4c1b3551b4 | ||
|
|
07d47cb984 | ||
|
|
ce9ee8c264 | ||
|
|
8039650248 | ||
|
|
cd2b578a1b | ||
|
|
052879b3e4 | ||
|
|
85ec8251e3 | ||
|
|
7e51a13c3a | ||
|
|
538b2c6c95 | ||
|
|
d3a70e8ed6 | ||
|
|
9dc68849d6 | ||
|
|
b6bab87601 | ||
|
|
b8d868f828 | ||
|
|
01e6a57aaa | ||
|
|
e0fe9227aa | ||
|
|
803751959a | ||
|
|
9597b5962e | ||
|
|
792f74b33e | ||
|
|
5181e71dab | ||
|
|
3d0e0f80ae | ||
|
|
251125913a | ||
|
|
89d2d6ed46 | ||
|
|
06f198afc1 | ||
|
|
19a289539a | ||
|
|
c14653b391 | ||
|
|
2527bf31c7 | ||
|
|
184107283a | ||
|
|
42c19cdfee | ||
|
|
3830534978 | ||
|
|
48eef6566d | ||
|
|
b253442545 | ||
|
|
26314c9b97 | ||
|
|
5ebb12e47c | ||
|
|
1d5e9794d6 | ||
|
|
b7a9f545fd | ||
|
|
9541e244d1 | ||
|
|
f12d202afe | ||
|
|
507a5f1de8 | ||
|
|
3810b039f5 | ||
|
|
827754d6de | ||
|
|
d455e17e34 | ||
|
|
7a7999ca56 | ||
|
|
db360820a9 | ||
|
|
804a222041 | ||
|
|
6dab1e464f | ||
|
|
86c45309e8 | ||
|
|
4f4690a482 | ||
|
|
1ca60e1d2f | ||
|
|
420c7ec171 | ||
|
|
011f7e4d6c | ||
|
|
8b94e58596 | ||
|
|
b304ccbe96 | ||
|
|
eb538ffba8 | ||
|
|
5bb934119c | ||
|
|
8c7a8e42b2 | ||
|
|
9a06e0727c | ||
|
|
08603009fb | ||
|
|
609ed4ed94 | ||
|
|
3ab9f85a7a | ||
|
|
45db675c35 | ||
|
|
5b01a68352 | ||
|
|
67cc6a66eb | ||
|
|
842c84da7d | ||
|
|
6598c25fda | ||
|
|
2a73fb340f | ||
|
|
edee63491d | ||
|
|
d716433848 | ||
|
|
5bd351d42a | ||
|
|
ad60240eb6 | ||
|
|
29b03cd49f | ||
|
|
f8df97b489 | ||
|
|
95235b353b | ||
|
|
d5feddfb8a | ||
|
|
ce1907c52a | ||
|
|
895dbdddb8 | ||
|
|
f5e32fa1a2 | ||
|
|
9fa3baa458 | ||
|
|
4b3760dfb4 | ||
|
|
142d586a1c | ||
|
|
95103dfcb8 | ||
|
|
f50ea26051 | ||
|
|
983dd0b6ca | ||
|
|
3bdc5e0c27 | ||
|
|
643cb6a2be | ||
|
|
2348d4db64 | ||
|
|
77534926c8 | ||
|
|
ba6a471054 | ||
|
|
47c409a3d5 | ||
|
|
ce44e3522b | ||
|
|
593fbdbf21 | ||
|
|
c1b87bc2b6 | ||
|
|
1f164dbb64 | ||
|
|
1275cbfd83 | ||
|
|
69be3c8f35 | ||
|
|
85fc1f7cea | ||
|
|
59a5a343c8 | ||
|
|
a3ce01f250 | ||
|
|
20cccdbf20 | ||
|
|
4b64c43ef8 | ||
|
|
4307d12762 | ||
|
|
0d3d336d65 | ||
|
|
077338086a | ||
|
|
0b2ee5f76b | ||
|
|
256a047fab | ||
|
|
222799b1a1 | ||
|
|
649fe7e3ce | ||
|
|
2e5e25dc09 | ||
|
|
3f9218e671 | ||
|
|
3003d0589d | ||
|
|
4bd15d43da | ||
|
|
8df62e99f5 | ||
|
|
d93c9c4220 | ||
|
|
1b5fd3c5fd | ||
|
|
0612a72c2f | ||
|
|
22f7ff3443 | ||
|
|
5105f35a31 | ||
|
|
f025572110 | ||
|
|
ed2cc47093 | ||
|
|
4486b9ee10 | ||
|
|
e8603e1617 | ||
|
|
f67860a502 | ||
|
|
497a0e0805 | ||
|
|
470a996b11 | ||
|
|
5bb9f427b4 | ||
|
|
52df877ed9 | ||
|
|
8292d8c44a | ||
|
|
0eefd352d9 | ||
|
|
cf2e3e8641 | ||
|
|
78c79ac88d | ||
|
|
6e54ed3dfc | ||
|
|
f60d2d8e30 | ||
|
|
a12a33b189 | ||
|
|
cf3ce54038 | ||
|
|
483a632c7f | ||
|
|
9a3fc507d6 | ||
|
|
19c0f64736 | ||
|
|
78ac82d6eb | ||
|
|
2dd40bcbd8 | ||
|
|
5da44a9d72 | ||
|
|
d75b0f4b4b | ||
|
|
5c9582aefc | ||
|
|
ab478c4a0e | ||
|
|
01daf25fe9 | ||
|
|
db73c27a53 | ||
|
|
3c53b74e5e | ||
|
|
829e0b8b7c | ||
|
|
4b60ca467c | ||
|
|
d972f353f1 | ||
|
|
9c1d1e64cc | ||
|
|
abc153e21e | ||
|
|
9385206e0e | ||
|
|
3674f06400 | ||
|
|
be780af04d | ||
|
|
ec1d2e091e | ||
|
|
106da3c95f | ||
|
|
34649c7543 | ||
|
|
0fcfe68164 | ||
|
|
8b0e79703a | ||
|
|
c3c4c21fc2 | ||
|
|
8471b8e444 | ||
|
|
8a5483db0b | ||
|
|
708de001f3 | ||
|
|
6cdbb8159b | ||
|
|
03dbdf7f09 | ||
|
|
1561a75b13 | ||
|
|
aff1ad40f3 | ||
|
|
19f48e3a10 | ||
|
|
2c27707e00 | ||
|
|
9a854a7066 | ||
|
|
897492e228 | ||
|
|
72d7b1bc9c | ||
|
|
14a8db0dda | ||
|
|
258ffdf56d | ||
|
|
bd5f709e14 | ||
|
|
b6c29e3ffd | ||
|
|
b45cb19f3c | ||
|
|
e30c919286 | ||
|
|
c4374d11a9 | ||
|
|
3b1aac26e8 | ||
|
|
bbc0f08541 | ||
|
|
d4c6c3e145 | ||
|
|
66b7b011c9 | ||
|
|
2233d2eb9b | ||
|
|
e9b2047769 | ||
|
|
781aa5c638 | ||
|
|
b5212d69e4 | ||
|
|
03eb74e616 | ||
|
|
f0d5c5a499 | ||
|
|
335f2476f9 | ||
|
|
530916e0fa | ||
|
|
b4791b3b10 | ||
|
|
4bdb9165ea | ||
|
|
8d4976dd95 | ||
|
|
e393503eb2 | ||
|
|
9c4d8f175b | ||
|
|
002b8a69b1 | ||
|
|
529ba78c04 | ||
|
|
772eaa634a | ||
|
|
7c909c6d69 | ||
|
|
a22f44ce01 | ||
|
|
09671d9907 | ||
|
|
da4b6b2f67 | ||
|
|
c0f351d92e | ||
|
|
8700645d21 | ||
|
|
3fe59717c5 | ||
|
|
0fb712ed39 | ||
|
|
003fa68582 | ||
|
|
e3acb2c56b | ||
|
|
789f78aefd | ||
|
|
ec323a3960 | ||
|
|
86d01a3b0f | ||
|
|
a685b6f051 | ||
|
|
2ac85f3172 | ||
|
|
18a15eed29 | ||
|
|
413633855f | ||
|
|
ac5eb11c39 | ||
|
|
ca5ab260c9 | ||
|
|
80ba3b9e9c | ||
|
|
81c771b8ea | ||
|
|
d86723ef07 | ||
|
|
d589234648 | ||
|
|
26aab09b8e | ||
|
|
55b8f3f642 | ||
|
|
063854384f | ||
|
|
9ffcd780e9 | ||
|
|
4125d0bdba | ||
|
|
af29243157 | ||
|
|
659d68e9b9 | ||
|
|
3d856b77d9 | ||
|
|
f447fd4e23 | ||
|
|
8088cf0ab4 | ||
|
|
a69046000e | ||
|
|
881da211a2 | ||
|
|
622a28236e | ||
|
|
d0edab2c74 | ||
|
|
c715cf5c65 | ||
|
|
1fca306fc9 | ||
|
|
02b1e38cd7 | ||
|
|
851a7663a8 | ||
|
|
bcaa7934c6 | ||
|
|
1d6052e81e | ||
|
|
a5a91b5acf | ||
|
|
aa7ea238b9 | ||
|
|
8b23ccdf2e | ||
|
|
5004f6ab32 | ||
|
|
adea41e690 | ||
|
|
d463db3742 | ||
|
|
4305aed38d | ||
|
|
f51502e4ae | ||
|
|
34eed12988 | ||
|
|
60775e73b5 | ||
|
|
e55b246756 | ||
|
|
438f48c835 | ||
|
|
8f88801646 | ||
|
|
afc9e8132a | ||
|
|
371aaa1107 | ||
|
|
ab7e6cbbe1 | ||
|
|
340edd5ec0 | ||
|
|
88e6469fb4 | ||
|
|
fd60dd342f | ||
|
|
dcd38539c2 | ||
|
|
70265c6f63 | ||
|
|
19b3f85eea | ||
|
|
873ec133b4 | ||
|
|
88f6312fd8 | ||
|
|
69c1398368 | ||
|
|
043b1d34c8 | ||
|
|
1226c894ed | ||
|
|
0fb7143f12 | ||
|
|
57c593f74e | ||
|
|
cf3faa35cd | ||
|
|
2429b99184 | ||
|
|
2ee128cd88 | ||
|
|
a2b349855e | ||
|
|
0f451e6190 | ||
|
|
366a96bdd4 | ||
|
|
8770ad74f8 | ||
|
|
68d5467e2a | ||
|
|
ac2fa61f6e | ||
|
|
2060a1dabe | ||
|
|
b561156f09 | ||
|
|
f3bca4384f | ||
|
|
c00ff88a5a | ||
|
|
866b64ff8d | ||
|
|
3acd74e894 | ||
|
|
ffa6e594e7 | ||
|
|
39f9df11f7 | ||
|
|
a606f2cafa | ||
|
|
592855c35d | ||
|
|
3797171fb0 | ||
|
|
028e40a43b | ||
|
|
36ea4c225b | ||
|
|
24df0efb6e | ||
|
|
db2eba92d5 | ||
|
|
dd0bb3ceca | ||
|
|
f9da8c7db2 | ||
|
|
82a93e9e47 | ||
|
|
754f0ea876 | ||
|
|
9145922cb1 | ||
|
|
47e19207f9 | ||
|
|
41f9dbb003 | ||
|
|
c067651498 | ||
|
|
2361152399 | ||
|
|
0318e3f851 | ||
|
|
d21f7b8915 | ||
|
|
640640b4b4 | ||
|
|
f87cf7df23 | ||
|
|
616e6a9bc0 | ||
|
|
303787d97c | ||
|
|
db2bd97669 | ||
|
|
5e38f3c0d4 | ||
|
|
1e756d1a67 | ||
|
|
ec274d58db | ||
|
|
682c15d6b1 | ||
|
|
29fbebde16 | ||
|
|
bd7450c4e6 | ||
|
|
b843a5be3a | ||
|
|
be44e287ee | ||
|
|
af16a6bc42 | ||
|
|
6b7429ab5d | ||
|
|
d4be72c49b | ||
|
|
86308bbf34 | ||
|
|
3d6124c7ad | ||
|
|
6357c2a856 | ||
|
|
0f1c5e0a42 | ||
|
|
cb692b22ba | ||
|
|
0b03c3f672 | ||
|
|
15d3dde5fa | ||
|
|
24947b64d7 | ||
|
|
61bb44d07b | ||
|
|
aa3eb4d42c | ||
|
|
2cdc74264c | ||
|
|
dd92a0f0bb | ||
|
|
e699d991d1 | ||
|
|
ede8c6a172 | ||
|
|
ba9205cd35 | ||
|
|
b7bd8d6fbb | ||
|
|
7015818ea5 | ||
|
|
c831235cd3 | ||
|
|
a6b19706eb | ||
|
|
de17df207a | ||
|
|
5116471d8d | ||
|
|
e01bd28e79 | ||
|
|
994e42a27d | ||
|
|
74c9139c1c | ||
|
|
1d7070cf12 | ||
|
|
19c19f1262 | ||
|
|
754f4a309c | ||
|
|
83bd14586f | ||
|
|
828a993eb6 | ||
|
|
69b319a7d6 | ||
|
|
6414172898 | ||
|
|
e78fccc294 | ||
|
|
54f1a86a5a | ||
|
|
8c387d7507 | ||
|
|
476e916945 | ||
|
|
1d131403d5 | ||
|
|
e7cab7cb42 | ||
|
|
7b2c8304ff | ||
|
|
b6314feb29 | ||
|
|
a124722966 | ||
|
|
47543ac7d4 | ||
|
|
262b45188a | ||
|
|
c0446cd03c | ||
|
|
bc37fda89e | ||
|
|
275a439091 | ||
|
|
f475ea4d25 | ||
|
|
72d2f9f1a1 | ||
|
|
8ab3cf240b | ||
|
|
450900d2b5 | ||
|
|
68b6795d5a | ||
|
|
868715867d | ||
|
|
9b7cc77b55 | ||
|
|
d3355b0783 | ||
|
|
6f382ce0d9 | ||
|
|
dbfb5b9d34 | ||
|
|
b3edc683fc | ||
|
|
f1ff6ebf55 | ||
|
|
f95e51b047 | ||
|
|
213d20f7ef | ||
|
|
a10e456de3 | ||
|
|
5187ea5041 | ||
|
|
9b42819dce | ||
|
|
c1d1a965c6 | ||
|
|
89a8cbcbae | ||
|
|
b97e804123 | ||
|
|
e0dd807bb4 | ||
|
|
888c14aeaf | ||
|
|
ab64183981 | ||
|
|
055784dcff | ||
|
|
4b3a1a8d84 | ||
|
|
91be428596 | ||
|
|
0c0a9e4f26 | ||
|
|
d6672d01cf | ||
|
|
43ae0e3575 | ||
|
|
2be4818d24 | ||
|
|
9bebdbb575 | ||
|
|
04cf220f2d | ||
|
|
61c7cc2856 | ||
|
|
824c537bd2 | ||
|
|
d1f0f5002f | ||
|
|
d3d016602d | ||
|
|
f9e44f545e | ||
|
|
52f57df064 | ||
|
|
61931520d1 | ||
|
|
229dd4c63c | ||
|
|
7515422979 | ||
|
|
eb1d748907 | ||
|
|
b87d3c65a0 | ||
|
|
8d841fa1cc | ||
|
|
537c4b6f24 | ||
|
|
f9a6c86cad | ||
|
|
db466048ea | ||
|
|
6952b58846 | ||
|
|
284ad2941c | ||
|
|
76d295d2bc | ||
|
|
66c38c43c6 | ||
|
|
2161ab563d | ||
|
|
6cd5510bd1 | ||
|
|
25cca192db | ||
|
|
e6067f756a | ||
|
|
6d9e9a2e3d | ||
|
|
373d119f1a | ||
|
|
7cb2338325 | ||
|
|
ddcc94b0da | ||
|
|
9a866434b8 | ||
|
|
37e92e01b6 | ||
|
|
60047116fd | ||
|
|
4aa33c7b3e | ||
|
|
373dbeb63f | ||
|
|
361cc76c30 | ||
|
|
3f27183d6d | ||
|
|
4e5c74d729 | ||
|
|
3d976f53b4 | ||
|
|
e5af389803 | ||
|
|
ba0f2e7f83 | ||
|
|
f0987a4ad1 | ||
|
|
61dd4356f0 | ||
|
|
d9895455cd | ||
|
|
d9a7e786b5 | ||
|
|
40e7ce012e | ||
|
|
31227c659a | ||
|
|
679f7f20d7 | ||
|
|
ff9b2fd6cd | ||
|
|
a939a73c14 | ||
|
|
487aad8bbe | ||
|
|
554bbb884d | ||
|
|
020f14fb51 | ||
|
|
98e501acc7 | ||
|
|
3f5ad1239d | ||
|
|
4e715352c9 | ||
|
|
5f538fa514 | ||
|
|
506ba911d0 | ||
|
|
f44cfa06c8 | ||
|
|
eeb6f5b091 | ||
|
|
18cb07dc72 | ||
|
|
3a4df0d89a | ||
|
|
d33f9db1f0 | ||
|
|
75b26ef94b | ||
|
|
398f4d9053 | ||
|
|
069947e397 | ||
|
|
580d67eca7 | ||
|
|
4bf93e08df | ||
|
|
6c475130d1 | ||
|
|
6f0dec4169 | ||
|
|
3b99402a0a | ||
|
|
6c6ade116f | ||
|
|
4f6c834587 | ||
|
|
c486843e5f | ||
|
|
ec93ac4c0e | ||
|
|
2307d05086 | ||
|
|
d3d48ee6fa | ||
|
|
ac7bdbf269 | ||
|
|
ddf00588d6 | ||
|
|
365ba2029e | ||
|
|
02cdb106ba | ||
|
|
85a2a8f11e | ||
|
|
e76fdea8b9 | ||
|
|
726ce953c7 | ||
|
|
a545a4ec27 | ||
|
|
fc0662a9c4 | ||
|
|
e5685ded86 | ||
|
|
226cbf1204 | ||
|
|
043466309a | ||
|
|
766624600d | ||
|
|
dab9a41f45 | ||
|
|
51d8ab13d8 | ||
|
|
b93d30e12b | ||
|
|
945db25578 | ||
|
|
67de007ecb | ||
|
|
e9a9b3b978 | ||
|
|
e10522a2c7 | ||
|
|
9f33b432fc | ||
|
|
7a0578a797 | ||
|
|
b81825326c | ||
|
|
b8c2c975c0 | ||
|
|
dc0272e664 | ||
|
|
3e329d64dd | ||
|
|
882ac2b1a1 | ||
|
|
b3153df6be | ||
|
|
bc8e16b37b | ||
|
|
79e4c9b4e3 | ||
|
|
70969c4475 | ||
|
|
ecbb6a12f8 | ||
|
|
218059ffbb | ||
|
|
c65ae50b81 | ||
|
|
95de0c6833 | ||
|
|
ab2c08392e | ||
|
|
89a2c4dbcb | ||
|
|
2cd557e483 | ||
|
|
e5df64ab75 | ||
|
|
52b0b19d11 | ||
|
|
36a2355e86 | ||
|
|
351e8e0c44 | ||
|
|
91e0d8f578 | ||
|
|
f8f26e3b23 | ||
|
|
873a3c3442 | ||
|
|
1318b814d3 | ||
|
|
4c8f5f789c | ||
|
|
b9b922b235 | ||
|
|
716b4a3205 | ||
|
|
3c592b799f | ||
|
|
c8c8a8d896 | ||
|
|
12b19a32bd | ||
|
|
40e114351c | ||
|
|
704c600ef0 | ||
|
|
5dde26fc33 | ||
|
|
cb5f80b0d5 | ||
|
|
32636968bd | ||
|
|
e546a75c6b | ||
|
|
9b0aab3f82 | ||
|
|
7f2baec387 | ||
|
|
392148f84b | ||
|
|
577ae95026 | ||
|
|
968d76431f | ||
|
|
c2408839d1 | ||
|
|
a03af2e4a9 | ||
|
|
aec7589b94 | ||
|
|
e1974d1b57 | ||
|
|
bc7809de43 | ||
|
|
f23a72e228 | ||
|
|
45a45a4eff | ||
|
|
7e9ce1c9f7 | ||
|
|
dce37d529c | ||
|
|
f528b3caa5 | ||
|
|
492320f8d3 | ||
|
|
636b4b12e4 | ||
|
|
93c96b8943 | ||
|
|
d1f94c7b92 | ||
|
|
74ffcb2db8 | ||
|
|
c7112fd897 | ||
|
|
1bb9ed8832 | ||
|
|
23f85da586 | ||
|
|
f163f56c10 | ||
|
|
9c03e8c069 | ||
|
|
92a76c05ee | ||
|
|
730ab835a8 | ||
|
|
ef89a4aa38 | ||
|
|
f9474cf773 | ||
|
|
96382b47e2 | ||
|
|
99dfdf07a2 | ||
|
|
62fa76d65e | ||
|
|
1a482cab4a | ||
|
|
ec61af7e1c | ||
|
|
d8d4e9ed2c | ||
|
|
38df849e6e | ||
|
|
d06eedf13c | ||
|
|
a3d3be11fb | ||
|
|
be76cd727a | ||
|
|
641cf48073 | ||
|
|
2dbaf53e49 | ||
|
|
849b4c57b6 | ||
|
|
de0933e1b9 | ||
|
|
8c9a6d049e | ||
|
|
799fa27d36 | ||
|
|
01cd5a34b9 | ||
|
|
9b3905a4bb | ||
|
|
1239ec47e3 | ||
|
|
eb1814e303 | ||
|
|
b4ec1b32ba | ||
|
|
27544e781a | ||
|
|
eebfb7eadb | ||
|
|
e6938bd14d | ||
|
|
6ce29b5c6a | ||
|
|
bc99331d47 |
No files matched your search
@@ -6,3 +6,5 @@ code/executor/ @asarium
|
||||
code/scpui/ @asarium
|
||||
|
||||
code/scripting/ @asarium
|
||||
|
||||
code/controlconfig/ @z64555
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
configuration: [FastDebug, Release]
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-a8ca321
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
@@ -29,6 +29,8 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
dir: ${{ github.workspace }}/..
|
||||
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
|
||||
setup-python: 'false'
|
||||
aqtversion: ==1.1.3
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
name: Linux
|
||||
needs: create_release
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-a8ca321
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
@@ -53,6 +53,8 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
dir: ${{ github.workspace }}/..
|
||||
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
|
||||
setup-python: 'false'
|
||||
aqtversion: ==1.1.3
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
configuration: [FastDebug, Release]
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-a8ca321
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
@@ -29,6 +29,8 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
dir: ${{ github.workspace }}/..
|
||||
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
|
||||
setup-python: 'false'
|
||||
aqtversion: ==1.1.3
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Build caches for master
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
QT_VERSION: 5.12.9
|
||||
|
||||
jobs:
|
||||
build_linux:
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Debug, Release]
|
||||
compiler: [gcc-5, gcc-10]
|
||||
cmake_options: [""]
|
||||
include:
|
||||
# Also include configurations that check if the code compiles without the graphics backends
|
||||
- configuration: Debug
|
||||
compiler: gcc-10
|
||||
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
|
||||
- configuration: Release
|
||||
compiler: gcc-10
|
||||
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ github.workspace }}/../Qt
|
||||
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
dir: ${{ github.workspace }}/..
|
||||
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
|
||||
setup-python: 'false'
|
||||
aqtversion: ==1.1.3
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
CONFIGURATION: ${{ matrix.configuration }}
|
||||
COMPILER: ${{ matrix.compiler }}
|
||||
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
|
||||
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
|
||||
- name: Compile
|
||||
working-directory: ./build
|
||||
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-e977d36
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
@@ -37,12 +37,18 @@ jobs:
|
||||
dir: ${{ github.workspace }}/..
|
||||
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
|
||||
setup-python: 'false'
|
||||
aqtversion: ==1.1.3
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
CONFIGURATION: ${{ matrix.configuration }}
|
||||
|
||||
@@ -3,7 +3,8 @@ name: Publish Standalone docker image
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- 'nightly_*'
|
||||
- 'release_*'
|
||||
|
||||
jobs:
|
||||
build_images:
|
||||
@@ -12,13 +13,9 @@ jobs:
|
||||
configuration: [FastDebug, Release]
|
||||
include:
|
||||
- configuration: FastDebug
|
||||
latest_tag: latest_debug
|
||||
nightly_tag: nightly_debug
|
||||
release_tag: release_debug
|
||||
tag_suffix: -debug
|
||||
- configuration: Release
|
||||
latest_tag: latest
|
||||
nightly_tag: nightly
|
||||
release_tag: release
|
||||
tag_suffix: ""
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -26,23 +23,30 @@ jobs:
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true
|
||||
- name: Docker login
|
||||
run: echo ${{ secrets.DOCKER_ACCESSTOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
- name: Docker build
|
||||
run: docker build --build-arg build_type=${{ matrix.configuration }} -f docker/standalone/Dockerfile -t ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} .
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Push latest tag
|
||||
run: |
|
||||
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }}
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
with:
|
||||
images: ghcr.io/scp-fs2open/standalone_server
|
||||
tag-latest: false
|
||||
|
||||
- name: Push nightly tag
|
||||
if: contains(github.ref, 'refs/tags/nightly_')
|
||||
run: |
|
||||
docker tag ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.nightly_tag }}
|
||||
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.nightly_tag }}
|
||||
|
||||
- name: Push release tag
|
||||
if: contains(github.ref, 'refs/tags/release_')
|
||||
run: |
|
||||
docker tag ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.release_tag }}
|
||||
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.release_tag }}
|
||||
- name: Login to GitHub Docker Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/standalone/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}${{ matrix.tag_suffix }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
build-args: |
|
||||
build_type=${{ matrix.configuration }}
|
||||
@@ -32,4 +32,5 @@ export LD_LIBRARY_PATH
|
||||
|
||||
cmake -G Ninja -DFSO_FATAL_WARNINGS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_BUILD_APPIMAGE=ON -DCMAKE_BUILD_TYPE=$CONFIGURATION \
|
||||
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON -DFSO_BUILD_QTFRED=ON ..
|
||||
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON -DFSO_BUILD_QTFRED=ON \
|
||||
-DSHADERS_ENABLE_COMPILATION=ON ..
|
||||
@@ -1,15 +1,3 @@
|
||||
|
||||
include(WriteCompilerDetectionHeader)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED 11)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
get_property(CMAKE_CXX_KNOWN_FEATURES GLOBAL PROPERTY CMAKE_CXX_KNOWN_FEATURES)
|
||||
|
||||
write_compiler_detection_header(
|
||||
FILE "${GENERATED_SOURCE_DIR}/scp_compiler_detection.h"
|
||||
PREFIX SCP
|
||||
COMPILERS AppleClang Clang GNU MSVC
|
||||
FEATURES ${CMAKE_CXX_KNOWN_FEATURES}
|
||||
VERSION 3.2
|
||||
)
|
||||
@@ -21,8 +21,6 @@ endif()
|
||||
|
||||
set(CMAKE_EXTRA_INCLUDE_FILES "cstddef")
|
||||
CHECK_TYPE_SIZE("std::max_align_t" STD_MAX_ALIGN_T LANGUAGE CXX)
|
||||
set(CMAKE_EXTRA_INCLUDE_FILES "type_traits")
|
||||
CHECK_TYPE_SIZE("std::is_trivially_copyable<int>" STD_IS_TRIVIALLY_COPYABLE LANGUAGE CXX)
|
||||
set(CMAKE_EXTRA_INCLUDE_FILES)
|
||||
|
||||
CHECK_TYPE_SIZE("char32_t" CHAR32_T LANGUAGE CXX)
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#cmakedefine SCP_HAVE_MAX_ALIGN_T
|
||||
#cmakedefine SCP_HAVE_STD_MAX_ALIGN_T
|
||||
|
||||
#cmakedefine01 HAVE_STD_IS_TRIVIALLY_COPYABLE
|
||||
|
||||
#cmakedefine01 IS_64BIT
|
||||
|
||||
#cmakedefine01 SCP_HAVE_STRCASECMP
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
|
||||
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
|
||||
endif()
|
||||
|
||||
set_if_not_defined(FSO_VERSION_MAJOR 20)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 1)
|
||||
set_if_not_defined(FSO_VERSION_MAJOR 21)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 3)
|
||||
set_if_not_defined(FSO_VERSION_BUILD 0)
|
||||
|
||||
# A numeric revision e.g. the CI build number
|
||||
|
||||
@@ -26,6 +26,7 @@ TARGET_LINK_LIBRARIES(code PUBLIC openal)
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC ${LUA_LIBS})
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC ${PNG_LIBS})
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC ${JPEG_LIBS})
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC lz4)
|
||||
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC sdl2)
|
||||
|
||||
@@ -63,6 +64,8 @@ ENDIF(FSO_USE_SPEECH)
|
||||
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC jansson)
|
||||
|
||||
target_link_libraries(code PUBLIC variant)
|
||||
|
||||
# Headers for standalone "gui" on UNIX
|
||||
IF(UNIX)
|
||||
TARGET_LINK_LIBRARIES(code PUBLIC ${MONGOOSE_LIBS})
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
Action::~Action() = default;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
enum class ActionResult
|
||||
{
|
||||
Finished,
|
||||
Errored,
|
||||
Wait
|
||||
};
|
||||
|
||||
class Action {
|
||||
public:
|
||||
virtual ~Action() = 0;
|
||||
|
||||
virtual ActionResult execute(ProgramLocals& locals) const = 0;
|
||||
|
||||
virtual std::unique_ptr<Action> clone() const = 0;
|
||||
};
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
#include "ActionDefinition.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
ActionDefinition::ActionDefinition(SCP_string name, SCP_vector<ActionParameter> parameters)
|
||||
: m_name(std::move(name)), m_parameters(std::move(parameters))
|
||||
{
|
||||
}
|
||||
ActionDefinition::~ActionDefinition() = default;
|
||||
|
||||
const SCP_string& ActionDefinition::getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
const SCP_vector<ActionParameter>& ActionDefinition::getParameters() const
|
||||
{
|
||||
return m_parameters;
|
||||
}
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
#include "actions/expression/Value.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
struct ActionParameter {
|
||||
SCP_string name;
|
||||
expression::ValueType type;
|
||||
};
|
||||
|
||||
class ActionDefinition {
|
||||
public:
|
||||
ActionDefinition(SCP_string name, SCP_vector<ActionParameter> parameters);
|
||||
virtual ~ActionDefinition();
|
||||
|
||||
const SCP_string& getName() const;
|
||||
const SCP_vector<actions::ActionParameter>& getParameters() const;
|
||||
|
||||
virtual flagset<ProgramContextFlags> getRequiredContext() const = 0;
|
||||
|
||||
virtual std::unique_ptr<Action> createAction(
|
||||
const SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions) const = 0;
|
||||
|
||||
private:
|
||||
SCP_string m_name;
|
||||
SCP_vector<ActionParameter> m_parameters;
|
||||
};
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,145 @@
|
||||
|
||||
#include "ActionDefinitionManager.h"
|
||||
|
||||
#include "BuiltinActionDefinition.h"
|
||||
|
||||
#include "actions/types/MoveToSubmodel.h"
|
||||
#include "actions/types/ParticleEffectAction.h"
|
||||
#include "actions/types/PlaySoundAction.h"
|
||||
#include "actions/types/SetDirectionAction.h"
|
||||
#include "actions/types/SetPositionAction.h"
|
||||
#include "actions/types/WaitAction.h"
|
||||
#include "utils/join_string.h"
|
||||
|
||||
namespace actions {
|
||||
namespace {
|
||||
|
||||
void parseActionParameters(const SCP_vector<ActionParameter>& params,
|
||||
SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions,
|
||||
const expression::ParseContext& context)
|
||||
{
|
||||
// We do not impose a set order of the parameters so we need to do something similar to the outer loop and
|
||||
// check for every possibility in a loop.
|
||||
for (size_t i = 0; i < params.size(); ++i) {
|
||||
// We might be doing some empty loops here in case of errors but there is a bound so this will finish
|
||||
// eventually
|
||||
for (const auto& parameter : params) {
|
||||
// Do not check for parameters which we already found
|
||||
if (parameterExpressions.find(parameter.name) != parameterExpressions.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto paramString = "+" + parameter.name + ":";
|
||||
|
||||
if (!optional_string(paramString.c_str())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We found our parameter!
|
||||
parameterExpressions[parameter.name] =
|
||||
expression::ActionExpression::parseFromTable(parameter.type, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const ActionDefinitionManager& ActionDefinitionManager::instance()
|
||||
{
|
||||
static const ActionDefinitionManager manager;
|
||||
return manager;
|
||||
}
|
||||
void ActionDefinitionManager::addDefinition(std::unique_ptr<ActionDefinition> def)
|
||||
{
|
||||
m_definitions.push_back(std::move(def));
|
||||
}
|
||||
std::unique_ptr<Action> ActionDefinitionManager::parseAction(const flagset<ProgramContextFlags>& context_flags,
|
||||
const expression::ParseContext& context) const
|
||||
{
|
||||
// We search through all definitions for every time we want to check for an action. This is not the most efficient
|
||||
// way to handle this since it results in an O(n^2) runtime. A better alternative would be to either construct a
|
||||
// regex with all the accepted actions as alternatives (although runtime of this is also doubtful) or peek at the
|
||||
// next token, extract the relevant part (The stuff between "+" and ":") and then look that up in a map. Then we
|
||||
// would get down to O(n).
|
||||
// Since this only happens at parse time, this is good enough for now.
|
||||
for (const auto& actionDef : m_definitions) {
|
||||
const auto parseString = "+" + actionDef->getName() + ":";
|
||||
|
||||
if (!optional_string(parseString.c_str())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SCP_unordered_map<SCP_string, expression::ActionExpression> parameterExpressions;
|
||||
const auto& params = actionDef->getParameters();
|
||||
|
||||
if (params.size() == 1) {
|
||||
// Single parameters are expected to be placed directly after the action name
|
||||
parameterExpressions[params.front().name] =
|
||||
expression::ActionExpression::parseFromTable(params.front().type, context);
|
||||
} else {
|
||||
parseActionParameters(params, parameterExpressions, context);
|
||||
|
||||
if (parameterExpressions.size() != params.size()) {
|
||||
SCP_vector<ActionParameter> missingParameters;
|
||||
std::copy_if(params.cbegin(),
|
||||
params.cend(),
|
||||
std::back_inserter(missingParameters),
|
||||
[¶meterExpressions](const ActionParameter& param) {
|
||||
const auto iter = parameterExpressions.find(param.name);
|
||||
return iter == parameterExpressions.cend();
|
||||
});
|
||||
|
||||
SCP_vector<SCP_string> missingParameterNames;
|
||||
std::transform(missingParameters.cbegin(),
|
||||
missingParameters.cend(),
|
||||
std::back_inserter(missingParameterNames),
|
||||
[](const ActionParameter& param) { return "+" + param.name; });
|
||||
|
||||
const auto namesList = util::join_container(missingParameterNames, ", ");
|
||||
|
||||
error_display(0, "Missing parameters for action '%s': %s", parseString.c_str(), namesList.c_str());
|
||||
|
||||
// Fix up the parameter list so that we can continue parsing
|
||||
for (const auto& missingParam : missingParameters) {
|
||||
parameterExpressions[missingParam.name] = expression::ActionExpression();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto actionInstance = actionDef->createAction(parameterExpressions);
|
||||
|
||||
const auto requiredFlags = actionDef->getRequiredContext();
|
||||
|
||||
if ((context_flags & requiredFlags) != requiredFlags) {
|
||||
// We are missing at least one context flag so this action is not valid here. We still return the action
|
||||
// here though since that is the only way we can keep parsing the table without throwing random error
|
||||
// messages at the user. However, this will trigger an Assertion (in debug) or crashes when the action is
|
||||
// actually run later.
|
||||
error_display(0,
|
||||
"The action %s is not available in the current context. This will lead to an Assertion failure when "
|
||||
"this action is triggered.",
|
||||
parseString.c_str());
|
||||
}
|
||||
|
||||
return actionInstance;
|
||||
}
|
||||
|
||||
return std::unique_ptr<Action>();
|
||||
}
|
||||
ActionDefinitionManager::ActionDefinitionManager()
|
||||
{
|
||||
addDefinition(
|
||||
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::MoveToSubmodel>("Move To Subobject")));
|
||||
addDefinition(std::unique_ptr<ActionDefinition>(
|
||||
new BuiltinActionDefinition<types::ParticleEffectAction>("Start Particle Effect")));
|
||||
addDefinition(
|
||||
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::PlaySoundAction>("Play 3D Sound")));
|
||||
addDefinition(
|
||||
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetDirectionAction>("Set Direction")));
|
||||
addDefinition(
|
||||
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetPositionAction>("Set Position")));
|
||||
addDefinition(std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::WaitAction>("Wait")));
|
||||
}
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "ActionDefinition.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
class ActionDefinitionManager {
|
||||
public:
|
||||
// This type is supposed to be global so it may not be copied
|
||||
ActionDefinitionManager(const ActionDefinitionManager&) = delete;
|
||||
ActionDefinitionManager& operator=(const ActionDefinitionManager&) = delete;
|
||||
|
||||
ActionDefinitionManager(ActionDefinitionManager&&) noexcept = default;
|
||||
ActionDefinitionManager& operator=(ActionDefinitionManager&&) noexcept = default;
|
||||
|
||||
std::unique_ptr<Action> parseAction(const flagset<ProgramContextFlags>& context_flags,
|
||||
const expression::ParseContext& context) const;
|
||||
|
||||
static const ActionDefinitionManager& instance();
|
||||
|
||||
private:
|
||||
ActionDefinitionManager();
|
||||
|
||||
void addDefinition(std::unique_ptr<ActionDefinition> def);
|
||||
|
||||
SCP_vector<std::unique_ptr<ActionDefinition>> m_definitions;
|
||||
};
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "ActionDefinition.h"
|
||||
|
||||
#include "expression/Value.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
template <typename TAction>
|
||||
class BuiltinActionDefinition : public ActionDefinition {
|
||||
using ActionValueType = typename TAction::ValueType;
|
||||
|
||||
public:
|
||||
BuiltinActionDefinition(SCP_string name)
|
||||
: ActionDefinition(std::move(name), {{"parameter", expression::ValueTypeTraits<ActionValueType>::type}})
|
||||
{
|
||||
}
|
||||
~BuiltinActionDefinition() override = default;
|
||||
|
||||
flagset<ProgramContextFlags> getRequiredContext() const override
|
||||
{
|
||||
return flagset<ProgramContextFlags>(TAction::getRequiredExecutionContextFlags());
|
||||
}
|
||||
|
||||
std::unique_ptr<Action> createAction(
|
||||
const SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions) const override
|
||||
{
|
||||
const auto paramIter = parameterExpressions.find("parameter");
|
||||
|
||||
// This should have been caught earlier
|
||||
Assertion(paramIter != parameterExpressions.cend(), "Could not find built-in parameter!");
|
||||
|
||||
return std::unique_ptr<Action>(new TAction(paramIter->second.template asTyped<ActionValueType>()));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,174 @@
|
||||
|
||||
#include "Program.h"
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
#include "actions/ActionDefinitionManager.h"
|
||||
#include "executor/global_executors.h"
|
||||
#include "tracing/Monitor.h"
|
||||
#include "tracing/categories.h"
|
||||
#include "tracing/tracing.h"
|
||||
|
||||
namespace {
|
||||
using namespace actions;
|
||||
|
||||
tracing::Monitor<int> RunningProgramsMonitor("RunningPrograms", 0);
|
||||
|
||||
struct ProgramRunner {
|
||||
tracing::RunningCounter runningCounter{RunningProgramsMonitor};
|
||||
|
||||
ProgramInstance instance;
|
||||
|
||||
explicit ProgramRunner(const ProgramInstance& _instance) : instance(_instance) {}
|
||||
|
||||
executor::Executor::CallbackResult operator()()
|
||||
{
|
||||
TRACE_SCOPE(tracing::ProgramStepOne);
|
||||
|
||||
auto res = instance.step();
|
||||
|
||||
switch (res) {
|
||||
case ProgramState::Running:
|
||||
// If the program is still running then we need to reschedule our execution
|
||||
return executor::Executor::CallbackResult::Reschedule;
|
||||
case ProgramState::Done:
|
||||
case ProgramState::Errored:
|
||||
default:
|
||||
return executor::Executor::CallbackResult::Done;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace actions {
|
||||
|
||||
ProgramInstance::ProgramInstance(const actions::Program* program) : _program(program) {}
|
||||
|
||||
ProgramState ProgramInstance::step()
|
||||
{
|
||||
if (!_locals.host.IsValid()) {
|
||||
return ProgramState::Done;
|
||||
}
|
||||
|
||||
auto& actions = _program->getActions();
|
||||
|
||||
for (; _currentInstruction < actions.size(); ++_currentInstruction) {
|
||||
auto& action = actions[_currentInstruction];
|
||||
|
||||
auto result = action->execute(_locals);
|
||||
|
||||
if (result == ActionResult::Errored) {
|
||||
return ProgramState::Errored;
|
||||
}
|
||||
|
||||
if (result == ActionResult::Wait) {
|
||||
// Action is not done yet, call again next frame
|
||||
return ProgramState::Running;
|
||||
}
|
||||
|
||||
// Action finished, run next action in program
|
||||
}
|
||||
|
||||
// End of program reached, we are done now
|
||||
return ProgramState::Done;
|
||||
}
|
||||
ProgramLocals& ProgramInstance::locals()
|
||||
{
|
||||
return _locals;
|
||||
}
|
||||
const ProgramLocals& ProgramInstance::locals() const
|
||||
{
|
||||
return _locals;
|
||||
}
|
||||
|
||||
const SCP_vector<std::unique_ptr<actions::Action>>& Program::getActions() const
|
||||
{
|
||||
return _actions;
|
||||
}
|
||||
|
||||
Program::Program() = default;
|
||||
|
||||
ProgramInstance Program::newInstance() const
|
||||
{
|
||||
return ProgramInstance(this);
|
||||
}
|
||||
|
||||
void Program::parseActions(const flagset<ProgramContextFlags>& context_flags, const expression::ParseContext& context)
|
||||
{
|
||||
while (true) {
|
||||
auto action = ActionDefinitionManager::instance().parseAction(context_flags, context);
|
||||
|
||||
if (!action) {
|
||||
break;
|
||||
}
|
||||
|
||||
_actions.push_back(std::move(action));
|
||||
}
|
||||
}
|
||||
|
||||
Program::Program(const Program& other) { *this = other; }
|
||||
Program& Program::operator=(const Program& other)
|
||||
{
|
||||
_actions.clear();
|
||||
_actions.reserve(other._actions.size());
|
||||
|
||||
for (auto& action : other._actions) {
|
||||
_actions.emplace_back(action->clone());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::operator=(Program&&) noexcept = default;
|
||||
Program::Program(Program&&) noexcept = default;
|
||||
|
||||
bool ProgramSet::isEmpty() const
|
||||
{
|
||||
return _programs.empty();
|
||||
}
|
||||
|
||||
ProgramSet::ProgramSet() = default;
|
||||
|
||||
ProgramSet::ProgramSet(const flagset<ProgramContextFlags>& context_flags) : _contextFlags(context_flags) {}
|
||||
|
||||
void ProgramSet::start(object* objp, const vec3d* local_pos, const matrix* local_orient, int submodel) const
|
||||
{
|
||||
if (_contextFlags[ProgramContextFlags::HasObject]) {
|
||||
Assertion(objp != nullptr, "Invalid object pointer supplied.");
|
||||
}
|
||||
if (_contextFlags[ProgramContextFlags::HasSubobject]) {
|
||||
Assertion(submodel >= 0, "Invalid subobject supplied.");
|
||||
}
|
||||
|
||||
for (const auto& prog : _programs) {
|
||||
auto instance = prog.newInstance();
|
||||
|
||||
instance.locals().host = object_h(objp);
|
||||
instance.locals().localPosition = *local_pos;
|
||||
instance.locals().localOrient = *local_orient;
|
||||
instance.locals().hostSubobject = submodel;
|
||||
instance.locals().variables = getDefaultTableVariables();
|
||||
|
||||
// Just use the executor system to take care of running this program
|
||||
executor::OnSimulationExecutor->post(ProgramRunner(instance));
|
||||
}
|
||||
}
|
||||
|
||||
ProgramSet ProgramSet::parseProgramSet(const char* tag, const flagset<ProgramContextFlags>& context_flags)
|
||||
{
|
||||
ProgramSet set(context_flags);
|
||||
|
||||
const auto parseContext = getTableParseContext();
|
||||
|
||||
while (optional_string(tag)) {
|
||||
Program p;
|
||||
p.parseActions(context_flags, parseContext);
|
||||
|
||||
set._programs.push_back(std::move(p));
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include "Action.h"
|
||||
#include "common.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
class Program;
|
||||
class ProgramSet;
|
||||
|
||||
enum class ProgramState
|
||||
{
|
||||
Running,
|
||||
Done,
|
||||
Errored
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An instance of a program execution
|
||||
*
|
||||
* A program instance contains all information related to a single execution of a program. A program instance is
|
||||
* independent from other instances. Local variables used by actions are stored in a specific structure which can be
|
||||
* used by actions to keep track of state in this specific instance.
|
||||
*/
|
||||
class ProgramInstance {
|
||||
size_t _currentInstruction = 0;
|
||||
ProgramLocals _locals;
|
||||
const Program* _program = nullptr;
|
||||
|
||||
public:
|
||||
explicit ProgramInstance(const Program* program);
|
||||
|
||||
ProgramState step();
|
||||
|
||||
ProgramLocals& locals();
|
||||
const ProgramLocals& locals() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A program specified in a table
|
||||
*
|
||||
* A program is a sequential collection of Actions which are executed in sequence.
|
||||
*/
|
||||
class Program {
|
||||
SCP_vector<std::unique_ptr<Action>> _actions;
|
||||
|
||||
public:
|
||||
Program();
|
||||
|
||||
Program(const Program& other);
|
||||
Program& operator=(const Program& other);
|
||||
|
||||
Program(Program&& other) noexcept;
|
||||
Program& operator=(Program&& other) noexcept;
|
||||
|
||||
const SCP_vector<std::unique_ptr<actions::Action>>& getActions() const;
|
||||
|
||||
/**
|
||||
* @brief Creates a new program instance from this program
|
||||
*
|
||||
* The program instance is initialized to its initial values.
|
||||
*
|
||||
* @return The new program instance
|
||||
*/
|
||||
ProgramInstance newInstance() const;
|
||||
|
||||
void parseActions(const flagset<ProgramContextFlags>& context_flags, const expression::ParseContext& context);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A set of parsed programs
|
||||
*
|
||||
* Programs are parsed in sets of zero or more programs to make it possible for the content creator to split up
|
||||
* programs.
|
||||
*/
|
||||
class ProgramSet {
|
||||
flagset<ProgramContextFlags> _contextFlags;
|
||||
|
||||
SCP_vector<Program> _programs;
|
||||
|
||||
public:
|
||||
ProgramSet();
|
||||
explicit ProgramSet(const flagset<ProgramContextFlags>& context_flags);
|
||||
|
||||
bool isEmpty() const;
|
||||
|
||||
/**
|
||||
* @brief Starts the execution of all programs within this set with the specified parameters
|
||||
* @param objp The object on which this program is executed
|
||||
* @param local_pos The local position relative to the object or the subsystem if specified
|
||||
* @param local_orien The local orientation relative to the object or the subsystem if specified
|
||||
* @param submodel The submodel of the model to which this program is attached to.
|
||||
*/
|
||||
void start(object* objp, const vec3d* local_pos, const matrix* local_orient, int submodel = -1) const;
|
||||
|
||||
/**
|
||||
* @brief Parses a program set of zero or more programs
|
||||
*
|
||||
* The tag is entirely optional and if it doesn't appear in the current table then an empty set will be returned
|
||||
* which does nothing if started.
|
||||
*
|
||||
* @param tag The tag which is required before a program can be specified
|
||||
* @param context_flags A set of flags which specify in which environment the program will be used. This is used
|
||||
* mainly for validating the used actions.
|
||||
* @return The parsed program set.
|
||||
*/
|
||||
static ProgramSet parseProgramSet(const char* tag, const flagset<ProgramContextFlags>& context_flags = {});
|
||||
};
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace actions {
|
||||
|
||||
expression::ParseContext getTableParseContext()
|
||||
{
|
||||
expression::ParseContext ctx;
|
||||
|
||||
auto& locals = ctx.variables.addScope("locals");
|
||||
|
||||
locals.addMember("position", expression::ValueType::Vector);
|
||||
locals.addMember("direction", expression::ValueType::Vector);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
expression::ProgramVariables getDefaultTableVariables()
|
||||
{
|
||||
expression::ProgramVariables vars;
|
||||
|
||||
vars.setValue({"locals", "position"}, expression::Value(vmd_zero_vector));
|
||||
vars.setValue({"locals", "direction"}, expression::Value(vmd_zero_vector));
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "globalincs/flagset.h"
|
||||
|
||||
#include "actions/expression/ParseContext.h"
|
||||
#include "actions/expression/ProgramVariables.h"
|
||||
#include "math/vecmat.h"
|
||||
#include "object/object.h"
|
||||
|
||||
class object;
|
||||
class ship_subsys;
|
||||
|
||||
namespace actions {
|
||||
|
||||
struct ProgramLocals {
|
||||
object_h host;
|
||||
vec3d localPosition = vmd_zero_vector;
|
||||
matrix localOrient = vmd_identity_matrix;
|
||||
int hostSubobject = -1;
|
||||
|
||||
int waitTimestamp = 0;
|
||||
|
||||
expression::ProgramVariables variables;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A list of flags for altering the program parsing behavior
|
||||
*
|
||||
* This should be used for differentiating between different program environments when validating the program (e.g. a
|
||||
* particle effect requires an object host).
|
||||
*/
|
||||
// clang-format off
|
||||
FLAG_LIST(ProgramContextFlags) {
|
||||
HasObject = 0, //!< The program will be started with an object host
|
||||
HasSubobject, //!< The program will be started with a subobject host
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @brief Constructs the "table" parse context
|
||||
*
|
||||
* This is the context that is available when parsing a program from an external table (e.g. ships or weapons).
|
||||
*
|
||||
* @return The parse context
|
||||
*/
|
||||
expression::ParseContext getTableParseContext();
|
||||
|
||||
/**
|
||||
* @brief Gets a program variables instance initialized with the default values
|
||||
*
|
||||
* The values are consistent with what is required for the default table parse context;
|
||||
*
|
||||
* @return The program variables
|
||||
*/
|
||||
expression::ProgramVariables getDefaultTableVariables();
|
||||
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
#include "ActionExpression.h"
|
||||
|
||||
#include "ExpressionParser.h"
|
||||
#include "TypeDefinition.h"
|
||||
|
||||
#include "actions/expression/nodes/AbstractExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace {
|
||||
|
||||
SCP_string getTypeName(ValueType type)
|
||||
{
|
||||
const auto& typeDef = TypeDefinition::forValueType(type);
|
||||
return typeDef.getName();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ActionExpression ActionExpression::parseFromTable(ValueType expectedReturnType, const ParseContext& context)
|
||||
{
|
||||
SCP_string expressionText;
|
||||
|
||||
stuff_string(expressionText, F_NAME);
|
||||
|
||||
ExpressionParser parser(expressionText);
|
||||
|
||||
auto expression = parser.parse(context);
|
||||
|
||||
if (!expression) {
|
||||
error_display(0, "Failed to parse action expression:\n%s", parser.getErrorText().c_str());
|
||||
return ActionExpression();
|
||||
}
|
||||
|
||||
auto type = expression->getExpressionType();
|
||||
|
||||
// Check if the type this evaluates to matches what we expect
|
||||
if (!checkTypeWithImplicitConversion(type, expectedReturnType)) {
|
||||
error_display(0,
|
||||
"Expression evaluates to type <%s> but <%s> was expected!",
|
||||
getTypeName(type).c_str(),
|
||||
getTypeName(expectedReturnType).c_str());
|
||||
return ActionExpression();
|
||||
}
|
||||
|
||||
// Everything is valid
|
||||
return ActionExpression(expression);
|
||||
}
|
||||
|
||||
Value ActionExpression::execute(const ProgramVariables& variables) const
|
||||
{
|
||||
// Do not crash on invalid expressions
|
||||
if (!isValid()) {
|
||||
return Value();
|
||||
}
|
||||
|
||||
return m_expression->execute(variables);
|
||||
}
|
||||
bool ActionExpression::isValid() const
|
||||
{
|
||||
return m_expression != nullptr;
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "ParseContext.h"
|
||||
#include "ProgramVariables.h"
|
||||
#include "TypeDefinition.h"
|
||||
|
||||
#include "parse/parselo.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
class AbstractExpression;
|
||||
}
|
||||
template <typename T>
|
||||
class TypedActionExpression;
|
||||
|
||||
/**
|
||||
* @brief An expression that can be executed to compute a value at runtime
|
||||
*
|
||||
* This is the basis of having dynamic values in actions by allowing mods to specify complex expressions that make use
|
||||
* of variables and functions for computing their values at runtime.
|
||||
*/
|
||||
class ActionExpression {
|
||||
public:
|
||||
ActionExpression() = default;
|
||||
|
||||
Value execute(const ProgramVariables& variables) const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
template <typename T>
|
||||
TypedActionExpression<T> asTyped() const;
|
||||
|
||||
static ActionExpression parseFromTable(ValueType expectedReturnType, const ParseContext& context);
|
||||
|
||||
protected:
|
||||
explicit ActionExpression(std::shared_ptr<nodes::AbstractExpression> expression)
|
||||
: m_expression(std::move(expression))
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<nodes::AbstractExpression> m_expression;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An action expression with a specific type specified at compile time
|
||||
*
|
||||
* This is most useful for built-in actions that have the required type fixed at compile time.
|
||||
*
|
||||
* @tparam T The type of the expression value.
|
||||
*/
|
||||
template <typename T>
|
||||
class TypedActionExpression {
|
||||
public:
|
||||
explicit TypedActionExpression(ActionExpression expression) : m_expression(std::move(expression)) {}
|
||||
|
||||
T execute(const ProgramVariables& variables) const
|
||||
{
|
||||
// Do not crash on invalid expressions
|
||||
if (!m_expression.isValid()) {
|
||||
return T();
|
||||
}
|
||||
|
||||
return m_expression.execute(variables).template get<T>();
|
||||
}
|
||||
|
||||
static TypedActionExpression<T> parseFromTable(const ParseContext& context)
|
||||
{
|
||||
return ActionExpression::parseFromTable(ValueTypeTraits<T>::type, context).template asTyped<T>();
|
||||
}
|
||||
|
||||
private:
|
||||
ActionExpression m_expression;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
TypedActionExpression<T> ActionExpression::asTyped() const
|
||||
{
|
||||
return TypedActionExpression<T>(*this);
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,194 @@
|
||||
|
||||
#include "ExpressionParser.h"
|
||||
|
||||
#include "actions/expression/nodes/FunctionCallExpression.h"
|
||||
#include "actions/expression/nodes/LiteralExpression.h"
|
||||
#include "actions/expression/nodes/RandomRangeExpression.h"
|
||||
#include "actions/expression/nodes/VariableReferenceExpression.h"
|
||||
#include "actions/expression/nodes/VectorConstructorExpression.h"
|
||||
#include "libs/antlr/ErrorListener.h"
|
||||
|
||||
PUSH_SUPPRESS_WARNINGS
|
||||
#include "action_expression/generated/ActionExpressionLexer.h"
|
||||
#include "action_expression/generated/ActionExpressionParser.h"
|
||||
#include "action_expression/generated/ActionExpressionVisitor.h"
|
||||
POP_SUPPRESS_WARNINGS
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
namespace {
|
||||
|
||||
class ExpressionBuilderVisitor : public ActionExpressionVisitor {
|
||||
public:
|
||||
antlrcpp::Any visitExpression_main(ActionExpressionParser::Expression_mainContext* context) override
|
||||
{
|
||||
return visit(context->expression());
|
||||
}
|
||||
antlrcpp::Any visitExpression(ActionExpressionParser::ExpressionContext* context) override
|
||||
{
|
||||
if (context->value_expression() != nullptr) {
|
||||
return visit(context->value_expression());
|
||||
}
|
||||
if (context->parenthesis_expression()) {
|
||||
return visit(context->parenthesis_expression());
|
||||
}
|
||||
if (context->random_range_expression()) {
|
||||
return visit(context->random_range_expression());
|
||||
}
|
||||
if (context->variable_reference_expression()) {
|
||||
return visit(context->variable_reference_expression());
|
||||
}
|
||||
|
||||
// This is an operator so treat it as one.
|
||||
antlr4::Token* operatorTok;
|
||||
SCP_string operatorText;
|
||||
if (context->PLUS() != nullptr) {
|
||||
operatorTok = context->PLUS()->getSymbol();
|
||||
operatorText = context->PLUS()->getText();
|
||||
} else {
|
||||
operatorTok = context->MINUS()->getSymbol();
|
||||
operatorText = context->MINUS()->getText();
|
||||
}
|
||||
|
||||
const auto leftExpr = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
const auto rightExpr = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
|
||||
auto operatorExpression = std::make_shared<nodes::FunctionCallExpression>(operatorTok,
|
||||
true,
|
||||
std::move(operatorText),
|
||||
SCP_vector<std::shared_ptr<nodes::AbstractExpression>>{leftExpr, rightExpr});
|
||||
return std::static_pointer_cast<nodes::AbstractExpression>(operatorExpression);
|
||||
}
|
||||
antlrcpp::Any visitVariable_reference_expression(
|
||||
ActionExpressionParser::Variable_reference_expressionContext* context) override
|
||||
{
|
||||
const auto identifiers = context->IDENTIFIER();
|
||||
|
||||
SCP_vector<SCP_string> partNames;
|
||||
partNames.reserve(identifiers.size());
|
||||
|
||||
std::transform(identifiers.begin(),
|
||||
identifiers.end(),
|
||||
std::back_inserter(partNames),
|
||||
[](antlr4::tree::TerminalNode* node) { return node->getText(); });
|
||||
|
||||
SCP_vector<antlr4::Token*> partTokens;
|
||||
partTokens.reserve(identifiers.size());
|
||||
|
||||
std::transform(identifiers.begin(),
|
||||
identifiers.end(),
|
||||
std::back_inserter(partTokens),
|
||||
[](antlr4::tree::TerminalNode* node) { return node->getSymbol(); });
|
||||
|
||||
auto expression =
|
||||
std::make_shared<nodes::VariableReferenceExpression>(std::move(partNames), std::move(partTokens));
|
||||
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
|
||||
}
|
||||
antlrcpp::Any visitRandom_range_expression(ActionExpressionParser::Random_range_expressionContext* context) override
|
||||
{
|
||||
auto expression = std::make_shared<nodes::RandomRangeExpression>(context->RAND_L_PAREN()->getSymbol(),
|
||||
visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>(),
|
||||
visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>());
|
||||
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
|
||||
}
|
||||
antlrcpp::Any visitLiteral_expression(ActionExpressionParser::Literal_expressionContext* context) override
|
||||
{
|
||||
std::shared_ptr<nodes::LiteralExpression> expression;
|
||||
|
||||
if (context->FLOAT() != nullptr) {
|
||||
expression = std::make_shared<nodes::LiteralExpression>(context->FLOAT()->getSymbol(),
|
||||
Value(std::stof(context->FLOAT()->getText())));
|
||||
} else if (context->INT() != nullptr) {
|
||||
expression = std::make_shared<nodes::LiteralExpression>(context->INT()->getSymbol(),
|
||||
Value(std::stoi(context->INT()->getText())));
|
||||
} else {
|
||||
const auto idText = context->STRING()->getText();
|
||||
|
||||
// Cut off the first and last characters since those are the < > braces
|
||||
expression = std::make_shared<nodes::LiteralExpression>(context->STRING()->getSymbol(),
|
||||
Value(idText.substr(1, idText.size() - 2)));
|
||||
}
|
||||
|
||||
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
|
||||
}
|
||||
antlrcpp::Any visitParenthesis_expression(ActionExpressionParser::Parenthesis_expressionContext* context) override
|
||||
{
|
||||
return visit(context->expression());
|
||||
}
|
||||
antlrcpp::Any visitValue_expression(ActionExpressionParser::Value_expressionContext* context) override
|
||||
{
|
||||
return visitChildren(context);
|
||||
}
|
||||
antlrcpp::Any visitVec3d_constructor(ActionExpressionParser::Vec3d_constructorContext* context) override
|
||||
{
|
||||
auto xExpression = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
auto yExpression = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
auto zExpression = visit(context->expression(2)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
|
||||
auto expression = std::make_shared<nodes::VectorConstructorExpression>(context->L_PAREN()->getSymbol(),
|
||||
xExpression,
|
||||
yExpression,
|
||||
zExpression);
|
||||
|
||||
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ExpressionParser::ExpressionParser(SCP_string expressionText) : m_expressionText(std::move(expressionText)) {}
|
||||
|
||||
std::shared_ptr<nodes::AbstractExpression> ExpressionParser::parse(const ParseContext& context)
|
||||
{
|
||||
antlr4::ANTLRInputStream input(m_expressionText);
|
||||
ActionExpressionLexer lexer(&input);
|
||||
antlr4::CommonTokenStream tokens(&lexer);
|
||||
|
||||
::ActionExpressionParser parser(&tokens);
|
||||
// By default we log to stderr which we do not want
|
||||
parser.removeErrorListeners();
|
||||
libs::antlr::ErrorListener errListener;
|
||||
parser.addErrorListener(&errListener);
|
||||
|
||||
antlr4::tree::ParseTree* tree = parser.expression_main();
|
||||
|
||||
std::shared_ptr<nodes::AbstractExpression> exprReturn;
|
||||
if (errListener.diagnostics.empty()) {
|
||||
// Only do semantic checking if syntax is valid
|
||||
ExpressionBuilderVisitor builder;
|
||||
exprReturn = tree->accept(&builder).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
|
||||
if (!exprReturn->validate(&parser, context)) {
|
||||
exprReturn = nullptr;
|
||||
} else {
|
||||
exprReturn->validationDone();
|
||||
}
|
||||
}
|
||||
|
||||
if (!errListener.diagnostics.empty()) {
|
||||
SCP_stringstream outStream;
|
||||
for (const auto& diag : errListener.diagnostics) {
|
||||
SCP_string tokenUnderline;
|
||||
if (diag.tokenLength > 1) {
|
||||
tokenUnderline = SCP_string(diag.tokenLength - 1, '~');
|
||||
}
|
||||
outStream << m_expressionText << "\n"
|
||||
<< SCP_string(diag.columnInLine, ' ') << "^" << tokenUnderline << "\n"
|
||||
<< diag.errorMessage << "\n";
|
||||
}
|
||||
|
||||
m_errorText = outStream.str();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return exprReturn;
|
||||
}
|
||||
|
||||
const SCP_string& ExpressionParser::getErrorText() const
|
||||
{
|
||||
return m_errorText;
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include "ParseContext.h"
|
||||
|
||||
#include "actions/expression/nodes/AbstractExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
class ExpressionParser {
|
||||
public:
|
||||
ExpressionParser(SCP_string expressionText);
|
||||
|
||||
std::shared_ptr<nodes::AbstractExpression> parse(const ParseContext& context);
|
||||
|
||||
const SCP_string& getErrorText() const;
|
||||
|
||||
private:
|
||||
SCP_string m_expressionText;
|
||||
|
||||
SCP_string m_errorText;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
#include "FunctionManager.h"
|
||||
|
||||
#include "TypeDefinition.h"
|
||||
|
||||
#include "math/vecmat.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace {
|
||||
template <typename T>
|
||||
Value value_plus(const SCP_vector<Value>& parameters)
|
||||
{
|
||||
return Value(parameters[0].get<T>() + parameters[1].get<T>());
|
||||
}
|
||||
template <typename T>
|
||||
Value value_minus(const SCP_vector<Value>& parameters)
|
||||
{
|
||||
return Value(parameters[0].get<T>() - parameters[1].get<T>());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const FunctionManager& FunctionManager::instance()
|
||||
{
|
||||
static const FunctionManager manager;
|
||||
return manager;
|
||||
}
|
||||
void FunctionManager::addOperator(const SCP_string& name,
|
||||
std::initializer_list<ValueType> parameterTypes,
|
||||
ValueType returnType,
|
||||
FunctionImplementation implementation)
|
||||
{
|
||||
m_operators[name].push_back({name, parameterTypes, returnType, std::move(implementation)});
|
||||
}
|
||||
const FunctionDefinition* FunctionManager::findOperator(const SCP_string& name,
|
||||
const SCP_vector<ValueType>& parameters) const
|
||||
{
|
||||
const auto iter = m_operators.find(name);
|
||||
|
||||
if (iter == m_operators.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Prefer exact matches
|
||||
for (const auto& funcDef : iter->second) {
|
||||
if (funcDef.parameterTypes == parameters) {
|
||||
return &funcDef;
|
||||
}
|
||||
}
|
||||
|
||||
// Now check if there are matches with implicit conversions
|
||||
for (const auto& funcDef : iter->second) {
|
||||
// Reject functions with different amount of elements
|
||||
if (funcDef.parameterTypes.size() != parameters.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto funcParamIter = funcDef.parameterTypes.cbegin();
|
||||
auto actualParamIter = parameters.cbegin();
|
||||
|
||||
bool match = true;
|
||||
for (; funcParamIter != funcDef.parameterTypes.cend(); ++funcParamIter, ++actualParamIter) {
|
||||
if (!checkTypeWithImplicitConversion(*actualParamIter, *funcParamIter)) {
|
||||
// Also doesn't match with implicit conversions
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
return &funcDef;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FunctionManager::FunctionManager()
|
||||
{
|
||||
addOperator("+", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_plus<int>);
|
||||
addOperator("+", {ValueType::Float, ValueType::Float}, ValueType::Float, value_plus<float>);
|
||||
addOperator("+", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_plus<vec3d>);
|
||||
|
||||
addOperator("-", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_minus<int>);
|
||||
addOperator("-", {ValueType::Float, ValueType::Float}, ValueType::Float, value_minus<float>);
|
||||
addOperator("-", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_minus<vec3d>);
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "Value.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
using FunctionImplementation = std::function<Value(const SCP_vector<Value>& parameters)>;
|
||||
|
||||
struct FunctionDefinition {
|
||||
SCP_string name;
|
||||
|
||||
SCP_vector<ValueType> parameterTypes;
|
||||
|
||||
ValueType returnType;
|
||||
|
||||
FunctionImplementation implementation;
|
||||
};
|
||||
|
||||
class FunctionManager {
|
||||
public:
|
||||
// This type is supposed to be global so it may not be copied
|
||||
FunctionManager(const FunctionManager&) = delete;
|
||||
FunctionManager& operator=(const FunctionManager&) = delete;
|
||||
|
||||
FunctionManager(FunctionManager&&) noexcept = default;
|
||||
FunctionManager& operator=(FunctionManager&&) noexcept = default;
|
||||
|
||||
void addOperator(const SCP_string& name,
|
||||
std::initializer_list<ValueType> parameterTypes,
|
||||
ValueType returnType,
|
||||
FunctionImplementation implementation);
|
||||
|
||||
const FunctionDefinition* findOperator(const SCP_string& name, const SCP_vector<ValueType>& parameterTypes) const;
|
||||
|
||||
static const FunctionManager& instance();
|
||||
|
||||
private:
|
||||
FunctionManager();
|
||||
|
||||
SCP_unordered_map<SCP_string, SCP_vector<FunctionDefinition>> m_operators;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProgramVariables.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
struct ParseContext {
|
||||
ProgramVariablesDefinition variables;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
#include "ProgramVariables.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
ProgramVariablesDefinition& ProgramVariablesDefinition::addScope(const SCP_string& name)
|
||||
{
|
||||
auto scope = std::unique_ptr<ProgramVariablesDefinition>(new ProgramVariablesDefinition());
|
||||
|
||||
auto scopePtr = scope.get();
|
||||
|
||||
m_scopes.insert(std::make_pair(name, std::move(scope)));
|
||||
|
||||
return *scopePtr;
|
||||
}
|
||||
void ProgramVariablesDefinition::addMember(const SCP_string& name, ValueType type)
|
||||
{
|
||||
m_memberTypes[name] = type;
|
||||
}
|
||||
ProgramVariablesDefinition* ProgramVariablesDefinition::getScope(const SCP_string& name) const
|
||||
{
|
||||
const auto iter = m_scopes.find(name);
|
||||
|
||||
if (iter == m_scopes.cend()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return iter->second.get();
|
||||
}
|
||||
ValueType ProgramVariablesDefinition::getMemberType(const SCP_string& name) const
|
||||
{
|
||||
const auto iter = m_memberTypes.find(name);
|
||||
|
||||
if (iter == m_memberTypes.cend()) {
|
||||
return ValueType::Invalid;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
Value ProgramVariables::getValue(std::initializer_list<const char*> memberPathParts) const
|
||||
{
|
||||
return getValue<decltype(memberPathParts)>(memberPathParts);
|
||||
}
|
||||
void ProgramVariables::setValue(std::initializer_list<const char*> memberPathParts, Value value)
|
||||
{
|
||||
setValue<decltype(memberPathParts)>(memberPathParts, std::move(value));
|
||||
}
|
||||
Value ProgramVariables::getValue(const SCP_string& fullMemberName) const
|
||||
{
|
||||
const auto iter = m_values.find(fullMemberName);
|
||||
|
||||
// This should have been validated earlier
|
||||
Assertion(iter != m_values.end(), "Could not find member %s in variables!", fullMemberName.c_str());
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "Value.h"
|
||||
|
||||
#include "utils/join_string.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
struct ProgramVariables {
|
||||
public:
|
||||
template <typename TContainer>
|
||||
static SCP_string getMemberName(const TContainer& memberPathParts)
|
||||
{
|
||||
return util::join_container(memberPathParts, ".");
|
||||
}
|
||||
|
||||
template <typename TContainer>
|
||||
Value getValue(const TContainer& memberPathParts) const
|
||||
{
|
||||
const auto name = getMemberName(memberPathParts);
|
||||
|
||||
return getValue(name);
|
||||
}
|
||||
|
||||
Value getValue(std::initializer_list<const char*> memberPathParts) const;
|
||||
|
||||
Value getValue(const SCP_string& fullMemberName) const;
|
||||
|
||||
template <typename TContainer>
|
||||
void setValue(const TContainer& memberPathParts, Value value)
|
||||
{
|
||||
const auto name = getMemberName(memberPathParts);
|
||||
|
||||
m_values[name] = std::move(value);
|
||||
}
|
||||
|
||||
void setValue(std::initializer_list<const char*> memberPathParts, Value value);
|
||||
|
||||
private:
|
||||
SCP_unordered_map<SCP_string, Value> m_values;
|
||||
};
|
||||
|
||||
class ProgramVariablesDefinition {
|
||||
public:
|
||||
ProgramVariablesDefinition& addScope(const SCP_string& name);
|
||||
|
||||
void addMember(const SCP_string& name, ValueType type);
|
||||
|
||||
ProgramVariablesDefinition* getScope(const SCP_string& name) const;
|
||||
|
||||
ValueType getMemberType(const SCP_string& name) const;
|
||||
|
||||
private:
|
||||
SCP_unordered_map<SCP_string, std::unique_ptr<ProgramVariablesDefinition>> m_scopes;
|
||||
|
||||
SCP_unordered_map<SCP_string, ValueType> m_memberTypes;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
#include "TypeDefinition.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
// Integers can be safely converted to floats (for the most part)
|
||||
TypeDefinition TypeDefinition::s_integer("Integer", {ValueType::Float});
|
||||
// No implicit conversions here
|
||||
TypeDefinition TypeDefinition::s_float("Float", {});
|
||||
TypeDefinition TypeDefinition::s_vector("Vector", {});
|
||||
// Identifiers are a technicality needed for making non-math values work but they have no further use other than their
|
||||
// string content
|
||||
TypeDefinition TypeDefinition::s_identifier("Identifier", {});
|
||||
|
||||
TypeDefinition::TypeDefinition(SCP_string name, SCP_vector<actions::expression::ValueType> allowedImplicitConversion)
|
||||
: m_name(std::move(name)), m_allowedImplicitConversions(std::move(allowedImplicitConversion))
|
||||
{
|
||||
}
|
||||
|
||||
const SCP_string& TypeDefinition::getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
const SCP_vector<actions::expression::ValueType>& TypeDefinition::getAllowedImplicitConversions() const
|
||||
{
|
||||
return m_allowedImplicitConversions;
|
||||
}
|
||||
const TypeDefinition& TypeDefinition::forValueType(ValueType type)
|
||||
{
|
||||
switch (type) {
|
||||
case ValueType::Integer:
|
||||
return s_integer;
|
||||
case ValueType::Float:
|
||||
return s_float;
|
||||
case ValueType::Vector:
|
||||
return s_vector;
|
||||
case ValueType::String:
|
||||
return s_identifier;
|
||||
default:
|
||||
UNREACHABLE("Invalid value type!");
|
||||
return s_integer; // Make compiler happy
|
||||
}
|
||||
}
|
||||
|
||||
bool checkTypeWithImplicitConversion(ValueType currentType, ValueType expectedType)
|
||||
{
|
||||
if (currentType == expectedType) {
|
||||
// No need to consider implicit conversions;
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& currentTypeDef = TypeDefinition::forValueType(currentType);
|
||||
const auto& allowedConversions = currentTypeDef.getAllowedImplicitConversions();
|
||||
|
||||
// For now, no transitiv conversions are allowed
|
||||
return std::find(allowedConversions.cbegin(), allowedConversions.cend(), expectedType) != allowedConversions.cend();
|
||||
}
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "Value.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
class TypeDefinition {
|
||||
public:
|
||||
TypeDefinition(SCP_string name, SCP_vector<actions::expression::ValueType> allowedImplicitConversion);
|
||||
|
||||
const SCP_string& getName() const;
|
||||
|
||||
const SCP_vector<actions::expression::ValueType>& getAllowedImplicitConversions() const;
|
||||
|
||||
static const TypeDefinition& forValueType(ValueType type);
|
||||
|
||||
// This type is supposed to be global so it may not be copied
|
||||
TypeDefinition(const TypeDefinition&) = delete;
|
||||
TypeDefinition& operator=(const TypeDefinition&) = delete;
|
||||
|
||||
TypeDefinition(TypeDefinition&&) = delete;
|
||||
TypeDefinition& operator=(TypeDefinition&&) = delete;
|
||||
|
||||
private:
|
||||
SCP_string m_name;
|
||||
|
||||
// The types this type is allowed to implicitly convert to
|
||||
SCP_vector<ValueType> m_allowedImplicitConversions;
|
||||
|
||||
static TypeDefinition s_integer;
|
||||
static TypeDefinition s_float;
|
||||
static TypeDefinition s_vector;
|
||||
static TypeDefinition s_identifier;
|
||||
};
|
||||
|
||||
bool checkTypeWithImplicitConversion(ValueType currentType, ValueType expectedType);
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
#include "Value.h"
|
||||
#include "math/vecmat.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
Value::Value(int val) : m_value(val) {}
|
||||
Value::Value(float val) : m_value(val) {}
|
||||
Value::Value(const vec3d& val) : m_value(val) {}
|
||||
Value::Value(SCP_string val) : m_value(std::move(val)) {}
|
||||
|
||||
ValueType actions::expression::Value::getType() const
|
||||
{
|
||||
switch (m_value.index()) {
|
||||
case 1:
|
||||
return ValueType::Integer;
|
||||
case 2:
|
||||
return ValueType::Float;
|
||||
case 3:
|
||||
return ValueType::Vector;
|
||||
case 4:
|
||||
return ValueType::String;
|
||||
default:
|
||||
return ValueType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
int Value::getInteger() const
|
||||
{
|
||||
return get<int>();
|
||||
}
|
||||
float Value::getFloat() const
|
||||
{
|
||||
return get<float>();
|
||||
}
|
||||
vec3d Value::getVector() const
|
||||
{
|
||||
return get<vec3d>();
|
||||
}
|
||||
SCP_string Value::getIdentifier() const
|
||||
{
|
||||
return get<SCP_string>();
|
||||
}
|
||||
bool operator==(const Value& lhs, const Value& rhs)
|
||||
{
|
||||
return lhs.m_value == rhs.m_value;
|
||||
}
|
||||
bool operator!=(const Value& lhs, const Value& rhs)
|
||||
{
|
||||
return !(rhs == lhs);
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& os, const Value& value)
|
||||
{
|
||||
os << "Value<";
|
||||
|
||||
switch (value.getType()) {
|
||||
case ValueType::Invalid:
|
||||
os << "INVALID";
|
||||
break;
|
||||
case ValueType::Integer:
|
||||
os << value.get<int>();
|
||||
break;
|
||||
case ValueType::Float:
|
||||
os << value.get<float>();
|
||||
break;
|
||||
case ValueType::Vector:
|
||||
os << value.get<vec3d>();
|
||||
break;
|
||||
case ValueType::String:
|
||||
os << value.get<SCP_string>();
|
||||
break;
|
||||
}
|
||||
|
||||
os << ">";
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include <mpark/variant.hpp>
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
|
||||
namespace detail {
|
||||
template <typename ToType>
|
||||
struct implicit_conversion {
|
||||
template <typename T>
|
||||
ToType operator()(const T&)
|
||||
{
|
||||
throw std::runtime_error("Invalid implicit conversion.");
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct implicit_conversion<float> {
|
||||
inline float operator()(int val) {
|
||||
return i2fl(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
float operator()(const T&)
|
||||
{
|
||||
throw std::runtime_error("Invalid implicit conversion.");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum class ValueType
|
||||
{
|
||||
Invalid,
|
||||
Integer,
|
||||
Float,
|
||||
Vector,
|
||||
String,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ValueTypeTraits;
|
||||
|
||||
template <>
|
||||
struct ValueTypeTraits<int> {
|
||||
static constexpr ValueType type = ValueType::Integer;
|
||||
};
|
||||
template <>
|
||||
struct ValueTypeTraits<float> {
|
||||
static constexpr ValueType type = ValueType::Float;
|
||||
};
|
||||
template <>
|
||||
struct ValueTypeTraits<vec3d> {
|
||||
static constexpr ValueType type = ValueType::Vector;
|
||||
};
|
||||
template <>
|
||||
struct ValueTypeTraits<SCP_string> {
|
||||
static constexpr ValueType type = ValueType::String;
|
||||
};
|
||||
|
||||
class Value {
|
||||
public:
|
||||
Value() = default;
|
||||
|
||||
explicit Value(int val);
|
||||
explicit Value(float val);
|
||||
explicit Value(const vec3d& val);
|
||||
explicit Value(SCP_string val);
|
||||
|
||||
ValueType getType() const;
|
||||
|
||||
int getInteger() const;
|
||||
float getFloat() const;
|
||||
vec3d getVector() const;
|
||||
SCP_string getIdentifier() const;
|
||||
|
||||
template <typename T>
|
||||
T get() const
|
||||
{
|
||||
const auto currentType = getType();
|
||||
const auto expectedType = ValueTypeTraits<T>::type;
|
||||
|
||||
if (currentType == expectedType) {
|
||||
// No conversion necessary
|
||||
return mpark::get<T>(m_value);
|
||||
}
|
||||
|
||||
return mpark::visit(detail::implicit_conversion<T>{}, m_value);
|
||||
}
|
||||
|
||||
friend bool operator==(const Value& lhs, const Value& rhs);
|
||||
friend bool operator!=(const Value& lhs, const Value& rhs);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Value& value);
|
||||
|
||||
private:
|
||||
mpark::variant<mpark::monostate, int, float, vec3d, SCP_string> m_value;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
#include "AbstractExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
AbstractExpression::AbstractExpression(antlr4::Token* token) : m_token(token) {}
|
||||
AbstractExpression::~AbstractExpression() = default;
|
||||
|
||||
antlr4::Token* AbstractExpression::getToken() const
|
||||
{
|
||||
return m_token;
|
||||
}
|
||||
ValueType AbstractExpression::getExpressionType() const
|
||||
{
|
||||
// Since expressions are immutable, caching this will be more efficient
|
||||
if (m_cachedType == ValueType::Invalid) {
|
||||
m_cachedType = determineReturnType();
|
||||
}
|
||||
return m_cachedType;
|
||||
}
|
||||
void AbstractExpression::validationDone()
|
||||
{
|
||||
// This pointer will become invalid after this so reset it now to avoid issues
|
||||
m_token = nullptr;
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "actions/expression/ParseContext.h"
|
||||
#include "actions/expression/Value.h"
|
||||
|
||||
#include <Parser.h>
|
||||
#include <Token.h>
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
class AbstractExpression {
|
||||
public:
|
||||
AbstractExpression(antlr4::Token* token);
|
||||
virtual ~AbstractExpression();
|
||||
|
||||
virtual Value execute(const ProgramVariables& variables) const = 0;
|
||||
|
||||
virtual bool validate(antlr4::Parser* parser, const ParseContext& context) = 0;
|
||||
|
||||
virtual void validationDone();
|
||||
|
||||
ValueType getExpressionType() const;
|
||||
|
||||
antlr4::Token* getToken() const;
|
||||
|
||||
protected:
|
||||
virtual ValueType determineReturnType() const = 0;
|
||||
|
||||
antlr4::Token* m_token = nullptr;
|
||||
|
||||
mutable ValueType m_cachedType = ValueType::Invalid;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
#include "FunctionCallExpression.h"
|
||||
|
||||
#include "actions/expression/TypeDefinition.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
FunctionCallExpression::FunctionCallExpression(antlr4::Token* token,
|
||||
bool isOperator,
|
||||
SCP_string functionName,
|
||||
SCP_vector<std::shared_ptr<AbstractExpression>> parameterExpressions)
|
||||
: AbstractExpression(token), m_isOperator(isOperator), m_functionName(std::move(functionName)),
|
||||
m_parameterExpressions(std::move(parameterExpressions))
|
||||
{
|
||||
}
|
||||
FunctionCallExpression::~FunctionCallExpression() = default;
|
||||
|
||||
Value FunctionCallExpression::execute(const ProgramVariables& variables) const
|
||||
{
|
||||
SCP_vector<Value> parameterValues;
|
||||
parameterValues.reserve(m_parameterExpressions.size());
|
||||
|
||||
std::transform(m_parameterExpressions.cbegin(),
|
||||
m_parameterExpressions.cend(),
|
||||
std::back_inserter(parameterValues),
|
||||
[&variables](const std::shared_ptr<AbstractExpression>& expression) { return expression->execute(variables); });
|
||||
|
||||
return m_functionDef->implementation(parameterValues);
|
||||
}
|
||||
bool FunctionCallExpression::validate(antlr4::Parser* parser, const ParseContext& context)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
for (const auto& paramExpression : m_parameterExpressions) {
|
||||
valid &= paramExpression->validate(parser, context);
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
// Exit early if there were errors since the values below are meaningless otherwise
|
||||
return false;
|
||||
}
|
||||
|
||||
Assertion(m_isOperator, "Non-operator functions are not supported yet!");
|
||||
|
||||
SCP_vector<ValueType> parameterTypes;
|
||||
|
||||
std::transform(m_parameterExpressions.cbegin(),
|
||||
m_parameterExpressions.cend(),
|
||||
std::back_inserter(parameterTypes),
|
||||
[](const std::shared_ptr<AbstractExpression>& expression) { return expression->getExpressionType(); });
|
||||
|
||||
const auto functionDef = FunctionManager::instance().findOperator(m_functionName, parameterTypes);
|
||||
|
||||
if (functionDef == nullptr) {
|
||||
SCP_stringstream paramListStream;
|
||||
if (!parameterTypes.empty()) {
|
||||
paramListStream << "<" << TypeDefinition::forValueType(parameterTypes.front()).getName() << ">";
|
||||
|
||||
for (auto iter = parameterTypes.cbegin() + 1; iter != parameterTypes.cend(); ++iter) {
|
||||
paramListStream << ", <" << TypeDefinition::forValueType(*iter).getName() << ">";
|
||||
}
|
||||
}
|
||||
|
||||
parser->notifyErrorListeners(m_token,
|
||||
SCP_string("Could not find a definition for the ") + (m_isOperator ? "operator" : "function") + " '" +
|
||||
m_functionName + "' with parameters (" + paramListStream.str() + ")",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_functionDef = functionDef;
|
||||
return true;
|
||||
}
|
||||
ValueType FunctionCallExpression::determineReturnType() const
|
||||
{
|
||||
return m_functionDef->returnType;
|
||||
}
|
||||
void FunctionCallExpression::validationDone()
|
||||
{
|
||||
AbstractExpression::validationDone();
|
||||
|
||||
// We no longer need this so we can save some memory
|
||||
m_functionName.clear();
|
||||
m_functionName.shrink_to_fit();
|
||||
|
||||
for (const auto& paramExpression : m_parameterExpressions) {
|
||||
paramExpression->validationDone();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractExpression.h"
|
||||
|
||||
#include "actions/expression/FunctionManager.h"
|
||||
#include "actions/expression/Value.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
class FunctionCallExpression : public AbstractExpression {
|
||||
public:
|
||||
FunctionCallExpression(antlr4::Token* token,
|
||||
bool isOperator,
|
||||
SCP_string functionName,
|
||||
SCP_vector<std::shared_ptr<AbstractExpression>> parameterExpressions);
|
||||
~FunctionCallExpression() override;
|
||||
|
||||
Value execute(const ProgramVariables& variables) const override;
|
||||
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
|
||||
void validationDone() override;
|
||||
|
||||
protected:
|
||||
ValueType determineReturnType() const override;
|
||||
|
||||
private:
|
||||
bool m_isOperator;
|
||||
SCP_string m_functionName;
|
||||
|
||||
const FunctionDefinition* m_functionDef = nullptr;
|
||||
|
||||
SCP_vector<std::shared_ptr<AbstractExpression>> m_parameterExpressions;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
#include "LiteralExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
LiteralExpression::LiteralExpression(antlr4::Token* token, Value literalValue)
|
||||
: AbstractExpression(token), m_literalValue(std::move(literalValue))
|
||||
{
|
||||
}
|
||||
LiteralExpression::~LiteralExpression() = default;
|
||||
|
||||
Value LiteralExpression::execute(const ProgramVariables& /*variables*/) const
|
||||
{
|
||||
return m_literalValue;
|
||||
}
|
||||
bool LiteralExpression::validate(antlr4::Parser* /*parser*/, const ParseContext& /*context*/)
|
||||
{
|
||||
// Literals are always valid
|
||||
return true;
|
||||
}
|
||||
ValueType LiteralExpression::determineReturnType() const
|
||||
{
|
||||
return m_literalValue.getType();
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractExpression.h"
|
||||
|
||||
#include "actions/expression/Value.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
class LiteralExpression : public AbstractExpression {
|
||||
public:
|
||||
LiteralExpression(antlr4::Token* token, Value literalValue);
|
||||
~LiteralExpression() override;
|
||||
|
||||
Value execute(const ProgramVariables& variables) const override;
|
||||
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
|
||||
|
||||
protected:
|
||||
ValueType determineReturnType() const override;
|
||||
|
||||
private:
|
||||
Value m_literalValue;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
#include "RandomRangeExpression.h"
|
||||
|
||||
#include "actions/expression/TypeDefinition.h"
|
||||
#include "utils/RandomRange.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
Value executeHelper(const Value& leftVal, const Value& rightVal)
|
||||
{
|
||||
auto leftContentVal = leftVal.get<T>();
|
||||
auto rightContentVal = rightVal.get<T>();
|
||||
|
||||
if (leftContentVal == rightContentVal) {
|
||||
// no need to do random stuff here
|
||||
return Value(rightContentVal);
|
||||
}
|
||||
|
||||
if (rightContentVal < leftContentVal) {
|
||||
// We can fix this issue
|
||||
std::swap(leftContentVal, rightContentVal);
|
||||
}
|
||||
|
||||
// Might be a bit inefficient not to cache this somehow but the bounds can be dynamic
|
||||
return Value(util::UniformRange<T>(leftContentVal, rightContentVal).next());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
RandomRangeExpression::RandomRangeExpression(antlr4::Token* token,
|
||||
std::shared_ptr<AbstractExpression> leftExpression,
|
||||
std::shared_ptr<AbstractExpression> rightExpression)
|
||||
: AbstractExpression(token), m_leftExpression(std::move(leftExpression)),
|
||||
m_rightExpression(std::move(rightExpression))
|
||||
{
|
||||
}
|
||||
RandomRangeExpression::~RandomRangeExpression() = default;
|
||||
|
||||
Value RandomRangeExpression::execute(const ProgramVariables& variables) const
|
||||
{
|
||||
const auto leftVal = m_leftExpression->execute(variables);
|
||||
const auto rightVal = m_rightExpression->execute(variables);
|
||||
|
||||
if (leftVal.getType() == ValueType::Integer) {
|
||||
return executeHelper<int>(leftVal, rightVal);
|
||||
} else {
|
||||
return executeHelper<float>(leftVal, rightVal);
|
||||
}
|
||||
}
|
||||
bool RandomRangeExpression::validate(antlr4::Parser* parser, const ParseContext& context)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
valid &= m_leftExpression->validate(parser, context);
|
||||
valid &= m_rightExpression->validate(parser, context);
|
||||
|
||||
if (!valid) {
|
||||
// Exit early if there were errors since the values below are meaningless otherwise
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto leftType = m_leftExpression->getExpressionType();
|
||||
const auto rightType = m_rightExpression->getExpressionType();
|
||||
|
||||
const auto& leftTypedef = TypeDefinition::forValueType(leftType);
|
||||
const auto& rightTypedef = TypeDefinition::forValueType(rightType);
|
||||
|
||||
// Both values must be the same
|
||||
if (leftType != rightType) {
|
||||
parser->notifyErrorListeners(m_rightExpression->getToken(),
|
||||
"Inconsistent types for random range. Must be the same but got <" + leftTypedef.getName() + "> and <" +
|
||||
rightTypedef.getName() + ">.",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// And we only support floats or integers
|
||||
if (leftType != ValueType::Float && leftType != ValueType::Integer) {
|
||||
parser->notifyErrorListeners(m_leftExpression->getToken(),
|
||||
"Invalid parameter type for random range. Must be <Float> or <Integer> but got <" + leftTypedef.getName() +
|
||||
">.",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Everything is fine.
|
||||
return true;
|
||||
}
|
||||
ValueType RandomRangeExpression::determineReturnType() const
|
||||
{
|
||||
// Return type is the same as the contained return types
|
||||
return m_leftExpression->getExpressionType();
|
||||
}
|
||||
void RandomRangeExpression::validationDone()
|
||||
{
|
||||
AbstractExpression::validationDone();
|
||||
|
||||
m_leftExpression->validationDone();
|
||||
m_rightExpression->validationDone();
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
class RandomRangeExpression : public AbstractExpression {
|
||||
public:
|
||||
RandomRangeExpression(antlr4::Token* token,
|
||||
std::shared_ptr<AbstractExpression> leftExpression,
|
||||
std::shared_ptr<AbstractExpression> rightExpression);
|
||||
~RandomRangeExpression() override;
|
||||
|
||||
Value execute(const ProgramVariables& variables) const override;
|
||||
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
|
||||
void validationDone() override;
|
||||
|
||||
protected:
|
||||
ValueType determineReturnType() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AbstractExpression> m_leftExpression;
|
||||
std::shared_ptr<AbstractExpression> m_rightExpression;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
#include "VariableReferenceExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
VariableReferenceExpression::VariableReferenceExpression(SCP_vector<SCP_string> references,
|
||||
SCP_vector<antlr4::Token*> referenceTokens)
|
||||
: AbstractExpression(referenceTokens.back()), m_references(std::move(references)),
|
||||
m_referenceTokens(std::move(referenceTokens))
|
||||
{
|
||||
}
|
||||
VariableReferenceExpression::~VariableReferenceExpression() = default;
|
||||
|
||||
Value VariableReferenceExpression::execute(const ProgramVariables& variables) const
|
||||
{
|
||||
return variables.getValue(m_fullVariablePath);
|
||||
}
|
||||
bool VariableReferenceExpression::validate(antlr4::Parser* parser, const ParseContext& context)
|
||||
{
|
||||
// Start out in the global scope
|
||||
auto currentScope = &context.variables;
|
||||
SCP_string currentScopeName("<global>");
|
||||
|
||||
for (size_t i = 0; i < m_references.size() - 1; ++i) {
|
||||
const auto nextScope = currentScope->getScope(m_references[i]);
|
||||
|
||||
if (nextScope == nullptr) {
|
||||
parser->notifyErrorListeners(m_referenceTokens[i],
|
||||
"Could not find scope " + m_references[i] + " in scope " + currentScopeName + ".",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentScope = nextScope;
|
||||
currentScopeName = m_references[i];
|
||||
}
|
||||
|
||||
auto variableType = currentScope->getMemberType(m_references.back());
|
||||
|
||||
if (variableType == ValueType::Invalid) {
|
||||
parser->notifyErrorListeners(m_referenceTokens.back(),
|
||||
"Could not find variable " + m_references.back() + " in scope " + currentScopeName + ".",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cache this so we don't duplicate all this code in determineReturnType()
|
||||
m_variableType = variableType;
|
||||
m_fullVariablePath = ProgramVariables::getMemberName(m_references);
|
||||
return true;
|
||||
}
|
||||
ValueType VariableReferenceExpression::determineReturnType() const
|
||||
{
|
||||
return m_variableType;
|
||||
}
|
||||
void VariableReferenceExpression::validationDone()
|
||||
{
|
||||
AbstractExpression::validationDone();
|
||||
|
||||
// Clear these since they are not necessary anymore
|
||||
m_references.clear();
|
||||
m_referenceTokens.clear();
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
class VariableReferenceExpression : public AbstractExpression {
|
||||
public:
|
||||
VariableReferenceExpression(const SCP_vector<SCP_string> references,
|
||||
SCP_vector<antlr4::Token*> referenceTokens);
|
||||
~VariableReferenceExpression() override;
|
||||
|
||||
Value execute(const ProgramVariables& variables) const override;
|
||||
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
|
||||
void validationDone() override;
|
||||
|
||||
protected:
|
||||
ValueType determineReturnType() const override;
|
||||
|
||||
private:
|
||||
ValueType m_variableType = ValueType::Invalid;
|
||||
SCP_string m_fullVariablePath;
|
||||
|
||||
SCP_vector<SCP_string> m_references;
|
||||
SCP_vector<antlr4::Token*> m_referenceTokens;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
#include "VectorConstructorExpression.h"
|
||||
|
||||
#include "actions/expression/TypeDefinition.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
VectorConstructorExpression::VectorConstructorExpression(antlr4::Token* token,
|
||||
std::shared_ptr<AbstractExpression> xExpression,
|
||||
std::shared_ptr<AbstractExpression> yExpression,
|
||||
std::shared_ptr<AbstractExpression> zExpression)
|
||||
: AbstractExpression(token), m_xExpression(std::move(xExpression)), m_yExpression(std::move(yExpression)),
|
||||
m_zExpression(std::move(zExpression))
|
||||
{
|
||||
}
|
||||
VectorConstructorExpression::~VectorConstructorExpression() = default;
|
||||
|
||||
Value VectorConstructorExpression::execute(const ProgramVariables& variables) const
|
||||
{
|
||||
auto xValue = m_xExpression->execute(variables);
|
||||
auto yValue = m_yExpression->execute(variables);
|
||||
auto zValue = m_zExpression->execute(variables);
|
||||
|
||||
vec3d vecVal;
|
||||
|
||||
vecVal.xyz.x = xValue.getFloat();
|
||||
vecVal.xyz.y = yValue.getFloat();
|
||||
vecVal.xyz.z = zValue.getFloat();
|
||||
|
||||
return Value(vecVal);
|
||||
}
|
||||
bool VectorConstructorExpression::validate(antlr4::Parser* parser, const ParseContext& context)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
valid &= m_xExpression->validate(parser, context);
|
||||
valid &= m_yExpression->validate(parser, context);
|
||||
valid &= m_zExpression->validate(parser, context);
|
||||
|
||||
if (!valid) {
|
||||
// Exit early if there were errors since the values below are meaningless otherwise
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkTypeWithImplicitConversion(m_xExpression->getExpressionType(), ValueType::Float)) {
|
||||
const auto& typeDef = TypeDefinition::forValueType(m_xExpression->getExpressionType());
|
||||
parser->notifyErrorListeners(m_xExpression->getToken(),
|
||||
"Invalid value type for vector constructor. Expected <Float>, got <" + typeDef.getName() + ">",
|
||||
nullptr);
|
||||
valid = false;
|
||||
}
|
||||
if (!checkTypeWithImplicitConversion(m_yExpression->getExpressionType(), ValueType::Float)) {
|
||||
const auto& typeDef = TypeDefinition::forValueType(m_yExpression->getExpressionType());
|
||||
parser->notifyErrorListeners(m_yExpression->getToken(),
|
||||
"Invalid value type for vector constructor. Expected <Float>, got <" + typeDef.getName() + ">",
|
||||
nullptr);
|
||||
valid = false;
|
||||
}
|
||||
if (!checkTypeWithImplicitConversion(m_zExpression->getExpressionType(), ValueType::Float)) {
|
||||
const auto& typeDef = TypeDefinition::forValueType(m_zExpression->getExpressionType());
|
||||
parser->notifyErrorListeners(m_zExpression->getToken(),
|
||||
"Invalid value type for vector constructor. Expected <Float>, got <" + typeDef.getName() + ">",
|
||||
nullptr);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
ValueType VectorConstructorExpression::determineReturnType() const
|
||||
{
|
||||
// No need to look at the expressions here since we will always be a vector
|
||||
return ValueType::Vector;
|
||||
}
|
||||
void VectorConstructorExpression::validationDone()
|
||||
{
|
||||
AbstractExpression::validationDone();
|
||||
|
||||
m_xExpression->validationDone();
|
||||
m_yExpression->validationDone();
|
||||
m_zExpression->validationDone();
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractExpression.h"
|
||||
|
||||
#include "actions/expression/Value.h"
|
||||
|
||||
namespace actions {
|
||||
namespace expression {
|
||||
namespace nodes {
|
||||
|
||||
class VectorConstructorExpression : public AbstractExpression {
|
||||
public:
|
||||
VectorConstructorExpression(antlr4::Token* token,
|
||||
std::shared_ptr<AbstractExpression> xExpression,
|
||||
std::shared_ptr<AbstractExpression> yExpression,
|
||||
std::shared_ptr<AbstractExpression> zExpression);
|
||||
~VectorConstructorExpression() override;
|
||||
|
||||
Value execute(const ProgramVariables& variables) const override;
|
||||
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
|
||||
void validationDone() override;
|
||||
|
||||
protected:
|
||||
ValueType determineReturnType() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AbstractExpression> m_xExpression;
|
||||
std::shared_ptr<AbstractExpression> m_yExpression;
|
||||
std::shared_ptr<AbstractExpression> m_zExpression;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace expression
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
#include "MoveToSubmodel.h"
|
||||
|
||||
#include "math/vecmat.h"
|
||||
#include "parse/parselo.h"
|
||||
#include "ship/ship.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
flagset<ProgramContextFlags> MoveToSubmodel::getRequiredExecutionContextFlags()
|
||||
{
|
||||
return flagset<ProgramContextFlags>{ProgramContextFlags::HasObject, ProgramContextFlags::HasSubobject};
|
||||
}
|
||||
|
||||
MoveToSubmodel::MoveToSubmodel(expression::TypedActionExpression<ValueType> subObjectExpression)
|
||||
: m_subObjectExpression(std::move(subObjectExpression))
|
||||
{
|
||||
}
|
||||
MoveToSubmodel::~MoveToSubmodel() = default;
|
||||
|
||||
ActionResult MoveToSubmodel::execute(ProgramLocals& locals) const
|
||||
{
|
||||
// The calling code should ensure that this never happens
|
||||
Assertion(locals.hostSubobject >= 0, "Did not have a valid host subobject.");
|
||||
|
||||
auto instance = object_get_model_instance(locals.host.objp);
|
||||
Assertion(instance != -1, "Model instances are required if a host subobject is specified.");
|
||||
|
||||
auto pmi = model_get_instance(instance);
|
||||
auto pm = model_get(pmi->model_num);
|
||||
|
||||
const auto destinationSubObject = m_subObjectExpression.execute(locals.variables);
|
||||
bool goToParent = false;
|
||||
if (destinationSubObject == "<parent>") {
|
||||
goToParent = true;
|
||||
}
|
||||
|
||||
if (goToParent) {
|
||||
// Special case when we simply want to go to our parent without explicitly specifying its name
|
||||
auto parentSubobject = pm->submodel[locals.hostSubobject].parent;
|
||||
|
||||
if (parentSubobject < 0) {
|
||||
Warning(LOCATION,
|
||||
"Current program subobject %s on model %s has no parent!",
|
||||
pm->submodel[locals.hostSubobject].name,
|
||||
pm->filename);
|
||||
return ActionResult::Errored;
|
||||
}
|
||||
|
||||
// We have a valid parent so this move will work properly
|
||||
locals.hostSubobject = parentSubobject;
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
|
||||
// We need to do a linear search for the right subobject
|
||||
for (int i = 0; i < pm->n_models; ++i) {
|
||||
const auto submodel = pm->submodel[i];
|
||||
|
||||
if (subsystem_stricmp(destinationSubObject.c_str(), submodel.name) == 0) {
|
||||
// Found something!
|
||||
locals.hostSubobject = i;
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
}
|
||||
|
||||
Warning(LOCATION, "Could not find subobject %s in model %s!", destinationSubObject.c_str(), pm->filename);
|
||||
return ActionResult::Errored;
|
||||
}
|
||||
|
||||
std::unique_ptr<Action> MoveToSubmodel::clone() const
|
||||
{
|
||||
return std::unique_ptr<Action>(new MoveToSubmodel(*this));
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "globalincs/flagset.h"
|
||||
|
||||
#include "actions/Action.h"
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
class MoveToSubmodel : public Action {
|
||||
public:
|
||||
using ValueType = SCP_string;
|
||||
|
||||
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
|
||||
|
||||
explicit MoveToSubmodel(expression::TypedActionExpression<ValueType> subObjectExpression);
|
||||
~MoveToSubmodel() override;
|
||||
|
||||
ActionResult execute(ProgramLocals& locals) const override;
|
||||
|
||||
std::unique_ptr<Action> clone() const override;
|
||||
|
||||
private:
|
||||
expression::TypedActionExpression<ValueType> m_subObjectExpression;
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
#include "ParticleEffectAction.h"
|
||||
|
||||
#include "math/vecmat.h"
|
||||
#include "parse/parselo.h"
|
||||
#include "ship/ship.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
flagset<ProgramContextFlags> ParticleEffectAction::getRequiredExecutionContextFlags()
|
||||
{
|
||||
return flagset<ProgramContextFlags>{ProgramContextFlags::HasObject};
|
||||
}
|
||||
|
||||
ParticleEffectAction::ParticleEffectAction(expression::TypedActionExpression<ValueType> effectExpression)
|
||||
: m_effectExpression(std::move(effectExpression))
|
||||
{
|
||||
}
|
||||
ParticleEffectAction::~ParticleEffectAction() = default;
|
||||
|
||||
ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
|
||||
{
|
||||
auto effectIdx = particle::ParticleManager::get()->getEffectByName(m_effectExpression.execute(locals.variables));
|
||||
|
||||
if (!effectIdx.isValid()) {
|
||||
// In case the parsing code failed
|
||||
return ActionResult::Errored;
|
||||
}
|
||||
|
||||
using namespace particle;
|
||||
|
||||
auto source = ParticleManager::get()->createSource(effectIdx);
|
||||
|
||||
vec3d local_pos;
|
||||
matrix local_orient;
|
||||
if (locals.hostSubobject != -1) {
|
||||
auto instance = object_get_model_instance(locals.host.objp);
|
||||
Assertion(instance != -1, "Model instances are required if a host subobject is specified.");
|
||||
|
||||
auto pmi = model_get_instance(instance);
|
||||
auto pm = model_get(pmi->model_num);
|
||||
|
||||
find_submodel_instance_point_orient(&local_pos,
|
||||
&local_orient,
|
||||
pm,
|
||||
pmi,
|
||||
locals.hostSubobject,
|
||||
&locals.localPosition,
|
||||
&locals.localOrient);
|
||||
} else {
|
||||
local_pos = locals.localPosition;
|
||||
local_orient = locals.localOrient;
|
||||
}
|
||||
|
||||
local_pos += locals.variables.getValue({"locals", "position"}).getVector();
|
||||
|
||||
auto direction = locals.variables.getValue({"locals", "direction"}).getVector();
|
||||
|
||||
source.moveToObject(locals.host.objp, &local_pos);
|
||||
source.setOrientationFromNormalizedVec(&direction, true);
|
||||
|
||||
source.finish();
|
||||
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
|
||||
std::unique_ptr<Action> ParticleEffectAction::clone() const
|
||||
{
|
||||
return std::unique_ptr<Action>(new ParticleEffectAction(*this));
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "actions/Action.h"
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
#include "particle/ParticleManager.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
class ParticleEffectAction : public Action {
|
||||
|
||||
public:
|
||||
using ValueType = SCP_string;
|
||||
|
||||
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
|
||||
|
||||
explicit ParticleEffectAction(expression::TypedActionExpression<ValueType> effectExpression);
|
||||
~ParticleEffectAction() override;
|
||||
|
||||
ActionResult execute(ProgramLocals& locals) const override;
|
||||
|
||||
std::unique_ptr<Action> clone() const override;
|
||||
|
||||
private:
|
||||
expression::TypedActionExpression<ValueType> m_effectExpression;
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
//
|
||||
|
||||
#include "PlaySoundAction.h"
|
||||
|
||||
#include "ship/ship.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
flagset<ProgramContextFlags> PlaySoundAction::getRequiredExecutionContextFlags()
|
||||
{
|
||||
return flagset<ProgramContextFlags>{ProgramContextFlags::HasObject};
|
||||
}
|
||||
|
||||
PlaySoundAction::PlaySoundAction(expression::TypedActionExpression<ValueType> soundIdExpression)
|
||||
: m_soundIdExpression(std::move(soundIdExpression))
|
||||
{
|
||||
}
|
||||
PlaySoundAction::~PlaySoundAction() = default;
|
||||
|
||||
ActionResult PlaySoundAction::execute(ProgramLocals& locals) const
|
||||
{
|
||||
vec3d local_pos;
|
||||
matrix local_orient;
|
||||
if (locals.hostSubobject != -1) {
|
||||
auto instance = object_get_model_instance(locals.host.objp);
|
||||
Assertion(instance != -1, "Model instances are required if a host subobject is specified.");
|
||||
|
||||
auto pmi = model_get_instance(instance);
|
||||
auto pm = model_get(pmi->model_num);
|
||||
|
||||
find_submodel_instance_point_orient(&local_pos,
|
||||
&local_orient,
|
||||
pm,
|
||||
pmi,
|
||||
locals.hostSubobject,
|
||||
&locals.localPosition,
|
||||
&locals.localOrient);
|
||||
} else {
|
||||
local_pos = locals.localPosition;
|
||||
local_orient = locals.localOrient;
|
||||
}
|
||||
|
||||
local_pos += locals.variables.getValue({"locals", "position"}).getVector();
|
||||
|
||||
vec3d global_pos;
|
||||
vm_vec_unrotate(&global_pos, &local_pos, &locals.host.objp->orient);
|
||||
global_pos += locals.host.objp->pos;
|
||||
|
||||
const auto soundId = gamesnd_get_by_name(m_soundIdExpression.execute(locals.variables).c_str());
|
||||
|
||||
// Sound is not attached to the host but the object sound system currently only supports persistent sounds
|
||||
snd_play_3d(gamesnd_get_game_sound(soundId), &global_pos, &Eye_position);
|
||||
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
std::unique_ptr<Action> PlaySoundAction::clone() const
|
||||
{
|
||||
return std::unique_ptr<Action>(new PlaySoundAction(*this));
|
||||
}
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "actions/Action.h"
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
#include "gamesnd/gamesnd.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
class PlaySoundAction : public Action {
|
||||
public:
|
||||
using ValueType = SCP_string;
|
||||
|
||||
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
|
||||
|
||||
explicit PlaySoundAction(expression::TypedActionExpression<ValueType> soundIdExpression);
|
||||
~PlaySoundAction() override;
|
||||
|
||||
ActionResult execute(ProgramLocals& locals) const override;
|
||||
|
||||
std::unique_ptr<Action> clone() const override;
|
||||
|
||||
private:
|
||||
expression::TypedActionExpression<ValueType> m_soundIdExpression;
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
#include "SetDirectionAction.h"
|
||||
|
||||
#include "math/vecmat.h"
|
||||
#include "parse/parselo.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
flagset<ProgramContextFlags> SetDirectionAction::getRequiredExecutionContextFlags()
|
||||
{
|
||||
return flagset<ProgramContextFlags>{};
|
||||
}
|
||||
|
||||
SetDirectionAction::SetDirectionAction(expression::TypedActionExpression<vec3d> newDirExpression)
|
||||
: m_newDirExpression(std::move(newDirExpression))
|
||||
{
|
||||
}
|
||||
SetDirectionAction::~SetDirectionAction() = default;
|
||||
|
||||
ActionResult SetDirectionAction::execute(ProgramLocals& locals) const
|
||||
{
|
||||
auto dir = m_newDirExpression.execute(locals.variables);
|
||||
|
||||
vm_vec_normalize_safe(&dir);
|
||||
|
||||
locals.variables.setValue({"locals", "direction"}, expression::Value(dir));
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
|
||||
std::unique_ptr<Action> SetDirectionAction::clone() const
|
||||
{
|
||||
return std::unique_ptr<Action>(new SetDirectionAction(*this));
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "actions/Action.h"
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
class SetDirectionAction : public Action {
|
||||
public:
|
||||
using ValueType = vec3d;
|
||||
|
||||
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
|
||||
|
||||
SetDirectionAction(expression::TypedActionExpression<ValueType> newDirExpression);
|
||||
~SetDirectionAction() override;
|
||||
|
||||
ActionResult execute(ProgramLocals& locals) const override;
|
||||
|
||||
std::unique_ptr<Action> clone() const override;
|
||||
|
||||
private:
|
||||
expression::TypedActionExpression<ValueType> m_newDirExpression;
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
#include "SetPositionAction.h"
|
||||
|
||||
#include "parse/parselo.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
flagset<ProgramContextFlags> SetPositionAction::getRequiredExecutionContextFlags()
|
||||
{
|
||||
return flagset<ProgramContextFlags>{};
|
||||
}
|
||||
|
||||
SetPositionAction::SetPositionAction(expression::TypedActionExpression<ValueType> newPositionExpression)
|
||||
: m_newPosExpression(std::move(newPositionExpression))
|
||||
{
|
||||
}
|
||||
SetPositionAction::~SetPositionAction() = default;
|
||||
|
||||
ActionResult SetPositionAction::execute(ProgramLocals& locals) const
|
||||
{
|
||||
locals.variables.setValue({"locals", "position"}, expression::Value(m_newPosExpression.execute(locals.variables)));
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
|
||||
std::unique_ptr<Action> SetPositionAction::clone() const
|
||||
{
|
||||
return std::unique_ptr<Action>(new SetPositionAction(*this));
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "actions/Action.h"
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
class SetPositionAction : public Action {
|
||||
|
||||
public:
|
||||
using ValueType = vec3d;
|
||||
|
||||
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
|
||||
|
||||
SetPositionAction(expression::TypedActionExpression<ValueType> newPositionExpression);
|
||||
~SetPositionAction() override;
|
||||
|
||||
ActionResult execute(ProgramLocals& locals) const override;
|
||||
|
||||
std::unique_ptr<Action> clone() const override;
|
||||
|
||||
private:
|
||||
expression::TypedActionExpression<vec3d> m_newPosExpression;
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
#include "WaitAction.h"
|
||||
|
||||
#include "io/timer.h"
|
||||
#include "parse/parselo.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
flagset<ProgramContextFlags> WaitAction::getRequiredExecutionContextFlags()
|
||||
{
|
||||
return flagset<ProgramContextFlags>{};
|
||||
}
|
||||
|
||||
WaitAction::WaitAction(expression::TypedActionExpression<float> waitTimeExpression)
|
||||
: _waitTimeExpression(std::move(waitTimeExpression))
|
||||
{
|
||||
}
|
||||
WaitAction::~WaitAction() = default;
|
||||
|
||||
ActionResult WaitAction::execute(actions::ProgramLocals& locals) const
|
||||
{
|
||||
if (!timestamp_valid(locals.waitTimestamp)) {
|
||||
auto waitTime = _waitTimeExpression.execute(locals.variables);
|
||||
|
||||
// Catch errors in the expression
|
||||
if (waitTime < 0.001f) {
|
||||
waitTime = 0.001f;
|
||||
}
|
||||
|
||||
locals.waitTimestamp = timestamp(fl2i(waitTime * TIMESTAMP_FREQUENCY));
|
||||
}
|
||||
if (!timestamp_elapsed(locals.waitTimestamp)) {
|
||||
// Wait until the timestamp is elapsed
|
||||
return ActionResult::Wait;
|
||||
}
|
||||
// Timestamp is elapsed. The timestamp is elapsed so that timestamp_valid above returns false for the next wait
|
||||
// action
|
||||
locals.waitTimestamp = 0;
|
||||
return ActionResult::Finished;
|
||||
}
|
||||
std::unique_ptr<Action> WaitAction::clone() const
|
||||
{
|
||||
return std::unique_ptr<Action>(new WaitAction(*this));
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "actions/Action.h"
|
||||
#include "actions/common.h"
|
||||
#include "actions/expression/ActionExpression.h"
|
||||
#include "utils/RandomRange.h"
|
||||
|
||||
namespace actions {
|
||||
namespace types {
|
||||
|
||||
class WaitAction : public Action {
|
||||
public:
|
||||
using ValueType = float;
|
||||
|
||||
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
|
||||
|
||||
explicit WaitAction(expression::TypedActionExpression<ValueType> waitTimeExpression);
|
||||
~WaitAction() override;
|
||||
|
||||
ActionResult execute(ProgramLocals& locals) const override;
|
||||
|
||||
std::unique_ptr<Action> clone() const override;
|
||||
|
||||
private:
|
||||
expression::TypedActionExpression<ValueType> _waitTimeExpression;
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace actions
|
||||
+4
-1
@@ -491,6 +491,9 @@ typedef struct ai_info {
|
||||
int ai_override_rot_timestamp; // mark for when to end the current rotational maneuver override
|
||||
|
||||
int form_obj_slotnum; // for flying in formation object mode, the position in the formation
|
||||
|
||||
int multilock_check_timestamp; // when to check for multilock next
|
||||
SCP_vector<std::pair<int, ship_subsys*>> ai_missile_locks_firing; // a list of missile locks (locked objnum, locked subsys) the ai is currently firing
|
||||
} ai_info;
|
||||
|
||||
// Goober5000
|
||||
@@ -561,7 +564,7 @@ const char *ai_get_goal_target_name(const char *name, int *index);
|
||||
void ai_clear_goal_target_names();
|
||||
|
||||
extern void init_ai_system(void);
|
||||
extern void ai_attack_object(object *attacker, object *attacked, ship_subsys *ssp, int ship_info_index = -1);
|
||||
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1);
|
||||
extern void ai_evade_object(object *evader, object *evaded);
|
||||
extern void ai_ignore_object(object *ignorer, object *ignored, int ignore_new);
|
||||
extern void ai_ignore_wing(object *ignorer, int wingnum);
|
||||
|
||||
+9
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace AI {
|
||||
FLAG_LIST(AI_Flags) {
|
||||
Formation_wing, // Fly in formation as part of wing.
|
||||
Formation_wing, // Fly in formation as part of wing. Also used when flying waypoints.
|
||||
Awaiting_repair, // Awaiting a repair ship.
|
||||
Being_repaired, // Currently docked with repair ship.
|
||||
Repairing, // Repairing a ship (or going to repair a ship)
|
||||
@@ -130,9 +130,16 @@ namespace AI {
|
||||
Use_only_single_fov_for_turrets,
|
||||
No_turning_directional_bias,
|
||||
Use_axial_turnrate_differences,
|
||||
nonshielded_ships_can_manage_ets,
|
||||
all_nonshielded_ships_can_manage_ets,
|
||||
fightercraft_nonshielded_ships_can_manage_ets,
|
||||
Better_collision_avoidance,
|
||||
Require_exact_los,
|
||||
Improved_missile_avoidance,
|
||||
Friendlies_use_countermeasure_firechance,
|
||||
Improved_subsystem_attack_pathing,
|
||||
Fixed_ship_weapon_collision,
|
||||
No_shield_damage_from_ship_collisions,
|
||||
Reset_last_hit_target_time_for_player_hits,
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
|
||||
+69
-9
@@ -58,6 +58,26 @@ int ai_path_type_match(char *p)
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
const char* AI_secondary_range_aware_select_modes[] = {
|
||||
"retail",
|
||||
"aware",
|
||||
};
|
||||
|
||||
int Num_ai_secondary_range_aware_select_modes = sizeof(AI_secondary_range_aware_select_modes) / sizeof(char*);
|
||||
|
||||
int ai_secondary_range_select_mode_match(char* p)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < Num_ai_secondary_range_aware_select_modes; i++)
|
||||
{
|
||||
if (!stricmp(AI_secondary_range_aware_select_modes[i], p))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void parse_ai_profiles_tbl(const char *filename)
|
||||
{
|
||||
int i;
|
||||
@@ -448,7 +468,6 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$firing requires exact los:", AI::Profile_Flags::Require_exact_los);
|
||||
|
||||
profile->ai_path_mode = AI_PATH_MODE_NORMAL;
|
||||
if (optional_string("$ai path mode:"))
|
||||
{
|
||||
stuff_string(buf, F_NAME, NAME_LENGTH);
|
||||
@@ -481,14 +500,12 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
if (path_radii >= Minimum_subsystem_path_pt_dist) {
|
||||
profile->subsystem_path_radii = path_radii;
|
||||
} else {
|
||||
mprintf(("Warning: \"$override radius for subsystem path points:\" should be >= %i (read %i). Value will not be used. ", Minimum_subsystem_path_pt_dist, path_radii));
|
||||
mprintf(("Warning: \"$override radius for subsystem path points:\" should be >= %i (read %i). Value will not be used.\n", Minimum_subsystem_path_pt_dist, path_radii));
|
||||
}
|
||||
}
|
||||
|
||||
set_flag(profile, "$use POF radius for subsystem path points:", AI::Profile_Flags::Use_subsystem_path_point_radii);
|
||||
|
||||
profile->bay_arrive_speed_mult = 1.0f;
|
||||
profile->bay_depart_speed_mult = 1.0f;
|
||||
if (optional_string("$bay arrive speed multiplier:")) {
|
||||
stuff_float(&profile->bay_arrive_speed_mult);
|
||||
}
|
||||
@@ -520,15 +537,50 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
profile->second_order_lead_predict_factor = 0;
|
||||
}
|
||||
}
|
||||
if (optional_string("$ships with no shields can manage ETS:"))
|
||||
{
|
||||
mprintf(("Warning: \"$ships with no shields can manage ETS\" flag is deprecated in favor of \"$any ship with no shields can manage ETS\"\n"));
|
||||
bool temp;
|
||||
stuff_boolean(&temp);
|
||||
profile->flags.set(AI::Profile_Flags::all_nonshielded_ships_can_manage_ets, temp);
|
||||
}
|
||||
|
||||
set_flag(profile, "$no directional bias for missile and ship turning:", AI::Profile_Flags::No_turning_directional_bias);
|
||||
|
||||
set_flag(profile, "$respect ship axial turnrate differences:", AI::Profile_Flags::Use_axial_turnrate_differences);
|
||||
|
||||
set_flag(profile, "$ships with no shields can manage ETS:", AI::Profile_Flags::nonshielded_ships_can_manage_ets);
|
||||
set_flag(profile, "$any ship with no shields can manage ETS:", AI::Profile_Flags::all_nonshielded_ships_can_manage_ets);
|
||||
|
||||
set_flag(profile, "$fighters/bombers with no shields can manage ETS:", AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets);
|
||||
|
||||
set_flag(profile, "$better combat collision avoidance for fightercraft:", AI::Profile_Flags::Better_collision_avoidance);
|
||||
|
||||
set_flag(profile, "$improved missile avoidance for fightercraft:", AI::Profile_Flags::Improved_missile_avoidance);
|
||||
|
||||
set_flag(profile, "$friendly ships use AI profile countermeasure chance:", AI::Profile_Flags::Friendlies_use_countermeasure_firechance);
|
||||
|
||||
set_flag(profile, "$improved subsystem attack pathing:", AI::Profile_Flags::Improved_subsystem_attack_pathing);
|
||||
|
||||
set_flag(profile, "$fixed ship-weapon collisions:", AI::Profile_Flags::Fixed_ship_weapon_collision);
|
||||
|
||||
//Intention is to expand this feature to include a preference for close or long range weapons
|
||||
//hence using something besides a simple flag.
|
||||
if (optional_string("$AI secondary range awareness:"))
|
||||
{
|
||||
stuff_string(buf, F_NAME, NAME_LENGTH);
|
||||
int j = ai_secondary_range_select_mode_match(buf);
|
||||
if (j >= 0) {
|
||||
profile->ai_range_aware_secondary_select_mode = j;
|
||||
}
|
||||
else {
|
||||
Warning(LOCATION, "Invalid ai secondary range awareness mode '%s' specified", buf);
|
||||
}
|
||||
}
|
||||
|
||||
set_flag(profile, "$no shield damage from ship collisions:", AI::Profile_Flags::No_shield_damage_from_ship_collisions);
|
||||
|
||||
set_flag(profile, "$reset last_hit_target_time for player hits:", AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
|
||||
|
||||
// if we've been through once already and are at the same place, force a move
|
||||
if (saved_Mp && (saved_Mp == Mp))
|
||||
{
|
||||
@@ -600,11 +652,12 @@ void ai_profile_t::reset()
|
||||
|
||||
flags.reset();
|
||||
|
||||
ai_path_mode = 0;
|
||||
ai_path_mode = AI_PATH_MODE_NORMAL;
|
||||
subsystem_path_radii = 0;
|
||||
bay_arrive_speed_mult = 0;
|
||||
bay_depart_speed_mult = 0;
|
||||
bay_arrive_speed_mult = 1.0f;
|
||||
bay_depart_speed_mult = 1.0f;
|
||||
second_order_lead_predict_factor = 0;
|
||||
ai_range_aware_secondary_select_mode = AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL;
|
||||
|
||||
for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
|
||||
max_incoming_asteroids[i] = 0;
|
||||
@@ -676,10 +729,17 @@ void ai_profile_t::reset()
|
||||
flags.set(AI::Profile_Flags::Fix_ai_path_order_bug);
|
||||
flags.set(AI::Profile_Flags::Aspect_invulnerability_fix);
|
||||
flags.set(AI::Profile_Flags::Use_actual_primary_range);
|
||||
flags.set(AI::Profile_Flags::nonshielded_ships_can_manage_ets);
|
||||
flags.set(AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets);
|
||||
}
|
||||
// this flag has been enabled ever since 3.7.2
|
||||
if (mod_supports_version(3, 7, 2)) {
|
||||
flags.set(AI::Profile_Flags::Fix_ramming_stationary_targets_bug);
|
||||
}
|
||||
// and this flag has been enabled ever since 3.6.10
|
||||
if (mod_supports_version(3, 6, 10)) {
|
||||
flags.set(AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
|
||||
}
|
||||
if (mod_supports_version(21, 4, 0)) {
|
||||
flags.set(AI::Profile_Flags::Fixed_ship_weapon_collision);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@
|
||||
// AI Path types
|
||||
#define AI_PATH_MODE_NORMAL 0
|
||||
#define AI_PATH_MODE_ALT1 1
|
||||
|
||||
#define AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL 0
|
||||
#define AI_RANGE_AWARE_SEC_SEL_MODE_AWARE 1
|
||||
|
||||
#define MAX_AI_PROFILES 5
|
||||
|
||||
@@ -107,6 +110,9 @@ public:
|
||||
|
||||
// How much 0-1 of a second-order lead prediction factor to add to lead indicators. Affects only the HUD indicator, and autoaim.
|
||||
float second_order_lead_predict_factor;
|
||||
|
||||
//Controls if the AI is dumb enough to keep trying to use out-of-range secondaries.
|
||||
int ai_range_aware_secondary_select_mode;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
+158
-16
@@ -17,12 +17,13 @@
|
||||
#include "io/timer.h"
|
||||
#include "math/staticrand.h"
|
||||
#include "mission/missionparse.h"
|
||||
#include "object/objcollide.h"
|
||||
#include "object/object.h"
|
||||
#include "ship/afterburner.h"
|
||||
#include "ship/ship.h"
|
||||
#include "utils/Random.h"
|
||||
#include "weapon/weapon.h"
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma optimize("", off)
|
||||
#pragma auto_inline(off)
|
||||
@@ -529,6 +530,112 @@ int ai_big_maybe_follow_subsys_path(int do_dot_check)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ai_new_maybe_reposition_attack_subsys() {
|
||||
|
||||
ai_info* aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
||||
|
||||
if (aip->target_objnum < 0 || aip->targeted_subsys == nullptr)
|
||||
return false;
|
||||
|
||||
object* target_objp = &Objects[aip->target_objnum];
|
||||
|
||||
// dont bother with this if you're still far away
|
||||
if (vm_vec_dist(&Pl_objp->pos, &target_objp->pos) > target_objp->radius * 2.0f + 2000.0f)
|
||||
return false;
|
||||
|
||||
vec3d geye, gsubpos;
|
||||
eye* ep;
|
||||
polymodel* pm = model_get(Ship_info[Ships[Pl_objp->instance].ship_info_index].model_num);
|
||||
|
||||
// get world pos of eye (stored in geye)
|
||||
ep = &(pm->view_positions[Ships[Pl_objp->instance].current_viewpoint]);
|
||||
model_find_world_point(&geye, &ep->pnt, pm->id, 0, &Pl_objp->orient, &Pl_objp->pos);
|
||||
|
||||
// get world pos of subsystem
|
||||
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
|
||||
vm_vec_add2(&gsubpos, &En_objp->pos);
|
||||
|
||||
// you're in sight! shoot it!
|
||||
if (ship_subsystem_in_sight(En_objp, aip->targeted_subsys, &geye, &gsubpos, 0))
|
||||
return false;
|
||||
|
||||
// not in sight, gotta get there
|
||||
vec3d tgt2pl = Pl_objp->pos - target_objp->pos;
|
||||
vec3d tgt2subsys = gsubpos - target_objp->pos;
|
||||
float pl_dist = vm_vec_mag(&tgt2pl);
|
||||
float subsys_dist = vm_vec_mag(&tgt2subsys) * 1.3f; // add a bit to this, we don't want to go directly at the subsys, we want to go above it
|
||||
|
||||
float angle2subsys = vm_vec_delta_ang(&tgt2pl, &tgt2subsys, nullptr);
|
||||
float angle2goal = angle2subsys;
|
||||
if (angle2goal > 1.2f) // let's not try to go more than about 70 degrees around the ship
|
||||
angle2goal = 1.2f;
|
||||
|
||||
// we'll start by travelling along the sphere at our current distance around the ship
|
||||
// remember that goal_pos will be relative to our target_ship (so we can scale in and out easily)
|
||||
vec3d goal_pos, cross;
|
||||
vm_vec_cross(&cross, &tgt2pl, &tgt2subsys);
|
||||
vm_vec_normalize(&cross);
|
||||
vm_vec_negate(&cross);
|
||||
vm_rot_point_around_line(&goal_pos, &tgt2pl, angle2goal, &vmd_zero_vector, &cross);
|
||||
|
||||
// then scale in (or out) towards our subsys
|
||||
vec3d world_goal_pos;
|
||||
float new_radius = ((subsys_dist - pl_dist) * (angle2goal / angle2subsys)) + pl_dist;
|
||||
vm_vec_copy_scale(&world_goal_pos, &goal_pos, new_radius / pl_dist);
|
||||
world_goal_pos += target_objp->pos;
|
||||
|
||||
vec3d* good_pos = nullptr;
|
||||
if (pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f)) {
|
||||
// ok best case won't work
|
||||
if (pl_dist > subsys_dist) {
|
||||
//we're farther away than the subsys, let's try at our current distance
|
||||
world_goal_pos = goal_pos + target_objp->pos;
|
||||
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
|
||||
good_pos = &world_goal_pos;
|
||||
}
|
||||
|
||||
// ok, let's start moving it away and see if that's better
|
||||
for (float i = 1.0f; i < 4.0f; i += 1.0f) {
|
||||
if (good_pos)
|
||||
break;
|
||||
|
||||
vm_vec_copy_scale(&world_goal_pos, &goal_pos, (new_radius / pl_dist) * powf(1.3f,i));
|
||||
world_goal_pos += target_objp->pos;
|
||||
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
|
||||
good_pos = &world_goal_pos;
|
||||
}
|
||||
|
||||
// welp this sucks, just run away i guess
|
||||
if (!good_pos) {
|
||||
vm_vec_copy_normalize(&world_goal_pos, &tgt2pl);
|
||||
world_goal_pos *= target_objp->radius + pl_dist;
|
||||
world_goal_pos += target_objp->pos;
|
||||
good_pos = &world_goal_pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
good_pos = &world_goal_pos;
|
||||
|
||||
ai_turn_towards_vector(good_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
|
||||
float subsys_distance = vm_vec_dist(&Pl_objp->pos, &gsubpos);
|
||||
|
||||
// full speed until you're close, then slow down a bit
|
||||
accelerate_ship(aip, subsys_distance > 200.0f ? 1.0f : subsys_distance / 200.0f);
|
||||
|
||||
if (!(Ships[Pl_objp->instance].flags[Ship::Ship_Flags::Afterburner_locked])) {
|
||||
if (subsys_distance > 300.0f) {
|
||||
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
||||
afterburners_start(Pl_objp);
|
||||
aip->afterburner_stop_time = Missiontime + 2 * F1_0;
|
||||
}
|
||||
}
|
||||
else
|
||||
afterburners_stop(Pl_objp);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// This function is only called from ai_big_chase_attack() when a ship is flying very slowly and
|
||||
// attacking a big ship. The ship should scan for enemy fighter/bombers... if any are close, then
|
||||
// return 1, otherwise return 0;
|
||||
@@ -637,7 +744,10 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
|
||||
}
|
||||
|
||||
// see if Pl_objp needs to reposition to get a good shot at subsystem which is being attacked
|
||||
if ( ai_big_maybe_follow_subsys_path() ) {
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
|
||||
if (ai_new_maybe_reposition_attack_subsys())
|
||||
return;
|
||||
} else if ( ai_big_maybe_follow_subsys_path() ) {
|
||||
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
||||
afterburners_stop(Pl_objp);
|
||||
}
|
||||
@@ -667,11 +777,25 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
|
||||
|
||||
ai_turn_towards_vector(enemy_pos, Pl_objp, nullptr, rel_pos, 0.0f, 0, rvec);
|
||||
|
||||
// set an optimal range if applicable
|
||||
float optimal_range = 0.0f;
|
||||
ship_weapon* weapons = &Ships[Pl_objp->instance].weapons;
|
||||
weapon_info* wip = nullptr;
|
||||
|
||||
if (weapons->num_primary_banks >= 1 && weapons->current_primary_bank >= 0) {
|
||||
wip = &Weapon_info[weapons->primary_bank_weapons[weapons->current_primary_bank]];
|
||||
} else if (weapons->num_secondary_banks >= 1 && weapons->current_secondary_bank >= 0) {
|
||||
wip = &Weapon_info[weapons->secondary_bank_weapons[weapons->current_secondary_bank]];
|
||||
}
|
||||
|
||||
if (wip != nullptr && wip->optimum_range > 0)
|
||||
optimal_range = wip->optimum_range;
|
||||
|
||||
// calc range of primary weapon
|
||||
weapon_travel_dist = ai_get_weapon_dist(&Ships[Pl_objp->instance].weapons);
|
||||
|
||||
if ( aip->targeted_subsys != NULL ) {
|
||||
if (dist_to_enemy > (weapon_travel_dist-20)) {
|
||||
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist-20))) {
|
||||
accelerate_ship(aip, 1.0f);
|
||||
|
||||
ship *shipp = &Ships[Pl_objp->instance];
|
||||
@@ -699,7 +823,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
|
||||
} else if (dot_to_enemy < 0.75f) {
|
||||
accel = 0.75f;
|
||||
} else {
|
||||
if (dist_to_enemy > weapon_travel_dist/2.0f) {
|
||||
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist/2.0f))) {
|
||||
accel = 1.0f;
|
||||
} else {
|
||||
accel = 1.0f - dot_to_enemy;
|
||||
@@ -757,7 +881,14 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
||||
swp = &Ships[Pl_objp->instance].weapons;
|
||||
|
||||
if (dot_to_enemy > 0.95f - 0.5f * En_objp->radius/MAX(1.0f, En_objp->radius + dist_to_enemy)) {
|
||||
float target_radius = En_objp->radius;
|
||||
|
||||
// normal subsystem attacking uses whether or not its on a path and skips this function entirely to stop "over-eager" firing
|
||||
// since the improved method doesnt use paths, refine the target radius to stop over-eager shooting
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing] && aip->targeted_subsys != nullptr)
|
||||
target_radius = aip->targeted_subsys->system_info->radius;
|
||||
|
||||
if (dot_to_enemy > 0.95f - 0.5f * target_radius/MAX(1.0f, target_radius + dist_to_enemy)) {
|
||||
aip->time_enemy_in_range += flFrametime;
|
||||
|
||||
// Chance of hitting ship is based on dot product of firing ship's forward vector with vector to ship
|
||||
@@ -797,7 +928,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank])) {
|
||||
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank]) && weapon_target_satisfies_lock_restrictions(swip, En_objp)) {
|
||||
float firing_range;
|
||||
if (swip->wi_flags[Weapon::Info_Flags::Local_ssm])
|
||||
firing_range=swip->lssm_lock_range;
|
||||
@@ -827,10 +958,11 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
} else {
|
||||
t = swip->fire_wait;
|
||||
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
|
||||
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
|
||||
} else {
|
||||
swp->burst_counter[current_bank_adjusted] = 0;
|
||||
}
|
||||
swp->burst_seed[current_bank_adjusted] = Random::next();
|
||||
}
|
||||
} else {
|
||||
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
|
||||
@@ -839,10 +971,11 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
} else {
|
||||
t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
|
||||
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
|
||||
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
|
||||
} else {
|
||||
swp->burst_counter[current_bank_adjusted] = 0;
|
||||
}
|
||||
swp->burst_seed[current_bank_adjusted] = Random::next();
|
||||
}
|
||||
}
|
||||
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
|
||||
@@ -1039,7 +1172,10 @@ void ai_big_chase()
|
||||
float dist_normal_to_enemy;
|
||||
|
||||
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
else // I'm fairly sure this retail code VVV meant to use / instead of *
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
} else {
|
||||
// don;t have normal so use a conservative value here
|
||||
dist_normal_to_enemy = 0.3f * dist_to_enemy;
|
||||
@@ -1316,13 +1452,17 @@ void ai_big_strafe_attack()
|
||||
ai_info *aip;
|
||||
vec3d target_pos;
|
||||
vec3d rand_vec;
|
||||
float target_dist, target_dot, accel, t;
|
||||
float accel, t;
|
||||
float target_dist = 0.0f;
|
||||
float target_dot = 0.0f;
|
||||
|
||||
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
||||
|
||||
if ( ai_big_maybe_follow_subsys_path(0) ) {
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
|
||||
if (ai_new_maybe_reposition_attack_subsys())
|
||||
return;
|
||||
} else if (ai_big_maybe_follow_subsys_path(0))
|
||||
return;
|
||||
}
|
||||
|
||||
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
||||
if ( ai_big_strafe_maybe_retreat(&target_pos) )
|
||||
@@ -1345,7 +1485,7 @@ void ai_big_strafe_attack()
|
||||
if ( t > 0.0f && t < 1.5f ) {
|
||||
// set up goal_point for avoid path to turn towards
|
||||
aip->goal_point = Pl_objp->pos;
|
||||
switch(rand()%4) {
|
||||
switch(Random::next(4)) {
|
||||
case 0:
|
||||
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, 2.0f);
|
||||
break;
|
||||
@@ -1439,9 +1579,11 @@ void ai_big_strafe_glide_attack()
|
||||
flight_dot_to_enemy = vm_vec_dot(&target_objp->pos, &norm_vel_vec); //Angle between flight path and target ship
|
||||
|
||||
//If we're following a path, then we shouldn't be doing glide strafe
|
||||
if ( ai_big_maybe_follow_subsys_path(0) ) {
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
|
||||
if (ai_new_maybe_reposition_attack_subsys())
|
||||
return;
|
||||
} else if (ai_big_maybe_follow_subsys_path(0))
|
||||
return;
|
||||
}
|
||||
|
||||
//Gets a point on the target ship to attack, as well as distance and the angle between the nose of the attacker and that point.
|
||||
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
||||
@@ -1575,7 +1717,7 @@ void ai_big_strafe_retreat1()
|
||||
if (Missiontime - aip->submode_start_time > fl2f(1.50f)) {
|
||||
// set up goal_point for avoid path to turn towards
|
||||
aip->prev_goal_point = Pl_objp->pos;
|
||||
switch(rand()%4) {
|
||||
switch(Random::next(4)) {
|
||||
case 0:
|
||||
vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec);
|
||||
break;
|
||||
|
||||
+396
-165
File diff suppressed because it is too large.
Load diff
+10
-8
@@ -293,9 +293,11 @@ void ai_clear_ship_goals( ai_info *aip )
|
||||
}
|
||||
|
||||
// add scripting hook for 'On Goals Cleared' --wookieejedi
|
||||
Script_system.SetHookObject("Ship", &Objects[Ships[aip->shipnum].objnum]);
|
||||
Script_system.RunCondition(CHA_ONGOALSCLEARED);
|
||||
Script_system.RemHookVars({"Ship"});
|
||||
if (Script_system.IsActiveAction(CHA_ONGOALSCLEARED)) {
|
||||
Script_system.SetHookObject("Ship", &Objects[Ships[aip->shipnum].objnum]);
|
||||
Script_system.RunCondition(CHA_ONGOALSCLEARED);
|
||||
Script_system.RemHookVars({"Ship"});
|
||||
}
|
||||
}
|
||||
|
||||
void ai_clear_wing_goals( wing *wingp )
|
||||
@@ -2147,14 +2149,14 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
|
||||
// Mike -- debug code!
|
||||
// If a ship has a subobject on it, attack that instead of the main ship!
|
||||
ai_attack_object( objp, other_obj, NULL);
|
||||
ai_attack_object( objp, other_obj);
|
||||
break;
|
||||
|
||||
case AI_GOAL_CHASE_WEAPON:
|
||||
Assert( Weapons[current_goal->target_instance].objnum >= 0 );
|
||||
other_obj = &Objects[Weapons[current_goal->target_instance].objnum];
|
||||
Assert( other_obj->signature == current_goal->target_signature );
|
||||
ai_attack_object( objp, other_obj, NULL );
|
||||
ai_attack_object( objp, other_obj);
|
||||
break;
|
||||
|
||||
case AI_GOAL_GUARD:
|
||||
@@ -2279,7 +2281,7 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert( shipnum >= 0 );
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
ai_attack_object( objp, other_obj, NULL);
|
||||
ai_attack_object( objp, other_obj);
|
||||
ai_set_attack_subsystem( objp, current_goal->ai_submode ); // submode stored the subsystem type
|
||||
if (current_goal->ai_mode != AI_GOAL_DESTROY_SUBSYSTEM) {
|
||||
if (aip->target_objnum != -1) {
|
||||
@@ -2301,14 +2303,14 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
break;
|
||||
|
||||
case AI_GOAL_CHASE_ANY:
|
||||
ai_attack_object( objp, NULL, NULL );
|
||||
ai_attack_object( objp, nullptr);
|
||||
break;
|
||||
|
||||
// chase-ship-class is chase-any but restricted to a subset of ships
|
||||
case AI_GOAL_CHASE_SHIP_CLASS:
|
||||
shipnum = ship_info_lookup( current_goal->target_name );
|
||||
Assertion( shipnum >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!" );
|
||||
ai_attack_object( objp, NULL, NULL, shipnum );
|
||||
ai_attack_object( objp, nullptr, shipnum );
|
||||
break;
|
||||
|
||||
case AI_GOAL_WARP: {
|
||||
|
||||
@@ -32,6 +32,6 @@ int check_ok_to_fire(int objnum, int target_objnum, weapon_info *wip, int second
|
||||
bool check_los(int objnum, int target_objnum, float threshold, int primary_bank, int secondary_bank, ship_subsys* turret);
|
||||
|
||||
//Does all the stuff needed to aim and fire a turret.
|
||||
void ai_fire_from_turret(ship *shipp, ship_subsys *ss);
|
||||
void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss);
|
||||
|
||||
#endif
|
||||
+173
-192
@@ -18,6 +18,7 @@
|
||||
#include "render/3d.h"
|
||||
#include "ship/ship.h"
|
||||
#include "ship/shipfx.h"
|
||||
#include "utils/Random.h"
|
||||
#include "weapon/beam.h"
|
||||
#include "weapon/flak.h"
|
||||
#include "weapon/muzzleflash.h"
|
||||
@@ -87,6 +88,18 @@ typedef struct eval_enemy_obj_struct {
|
||||
int nearest_objnum;
|
||||
} eval_enemy_obj_struct;
|
||||
|
||||
// the current world orientation of the turret matrix, corresponding to its fvec and uvec defined in the model
|
||||
// is NOT affected by the turret's current aiming
|
||||
void turret_instance_find_world_orient(matrix* out_mat, int model_instance_num, int submodel_num, const matrix* objorient)
|
||||
{
|
||||
auto pmi = model_get_instance(model_instance_num);
|
||||
auto pm = model_get(pmi->model_num);
|
||||
vec3d fvec, uvec;
|
||||
model_instance_find_world_dir(&fvec, &pm->submodel[submodel_num].frame_of_reference.vec.fvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
|
||||
model_instance_find_world_dir(&uvec, &pm->submodel[submodel_num].frame_of_reference.vec.uvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
|
||||
vm_vector_2_matrix_norm(out_mat, &fvec, &uvec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is object in turret field of view?
|
||||
*
|
||||
@@ -98,117 +111,53 @@ typedef struct eval_enemy_obj_struct {
|
||||
*
|
||||
* @return 1 if objp is in fov of the specified turret. Otherwise return 0.
|
||||
*/
|
||||
int object_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, float dist)
|
||||
bool object_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, float dist)
|
||||
{
|
||||
vec3d v2e;
|
||||
float size_mod;
|
||||
bool in_fov;
|
||||
bool in_fov = false;
|
||||
|
||||
if (ss->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
|
||||
int model_num;
|
||||
switch (objp->type) {
|
||||
case OBJ_SHIP:
|
||||
model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
|
||||
break;
|
||||
case OBJ_ASTEROID:
|
||||
model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
|
||||
break;
|
||||
default:
|
||||
vm_vec_normalized_dir(&v2e, &objp->pos, tpos);
|
||||
size_mod = objp->radius / (dist + objp->radius);
|
||||
|
||||
in_fov = turret_fov_test(ss, tvec, &v2e, size_mod);
|
||||
|
||||
return in_fov;
|
||||
}
|
||||
|
||||
auto pm = model_get(model_num);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
vec3d bbox_point;
|
||||
vm_vec_unrotate(&bbox_point, &pm->bounding_box[i], &objp->orient);
|
||||
bbox_point += objp->pos;
|
||||
|
||||
vm_vec_normalized_dir(&v2e, &bbox_point, tpos);
|
||||
in_fov = turret_fov_test(ss, tvec, &v2e, -0.2f);
|
||||
|
||||
if (in_fov)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// if the bbox points method didn't work (or fov_edge_checks isn't on)
|
||||
// try the normal method
|
||||
|
||||
vm_vec_normalized_dir(&v2e, &objp->pos, tpos);
|
||||
size_mod = objp->radius / (dist + objp->radius);
|
||||
|
||||
in_fov = turret_fov_test(ss, tvec, &v2e, size_mod);
|
||||
|
||||
if ( in_fov ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_object_radius_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, vec3d *v2e, vec3d *predicted_pos, float distance)
|
||||
{
|
||||
float target_dist = distance;
|
||||
if (distance == 0.0f)
|
||||
target_dist = vm_vec_dist(predicted_pos,tpos);
|
||||
|
||||
if (object_in_turret_fov(objp, ss, tvec, tpos, target_dist + objp->radius)) {
|
||||
// so the targeted spot in not in fov but the enemy + radius is
|
||||
// lets align the darn gun and try shooting there
|
||||
vec3d temp_vec;
|
||||
float multiplier = 0;
|
||||
model_subsystem *tp = ss->system_info;
|
||||
|
||||
// project v2e_from_turret to turret normal
|
||||
// substract resultant vector from the temp_vec (v2e_from_turret)
|
||||
// adjust z component as necessary
|
||||
// calculate multiplier for the resultant vector
|
||||
// use multiplier and the z component and compose a new vector
|
||||
float dot = vm_vec_dot(v2e, tvec);
|
||||
|
||||
vm_vec_scale_add(&temp_vec, v2e, tvec, -dot);
|
||||
|
||||
if (IS_VEC_NULL_SQ_SAFE(&temp_vec)) {
|
||||
// return false, target is perfectly aligned over or below the turret
|
||||
// safe bet is to allow turret to reacquire better target
|
||||
return false;
|
||||
}
|
||||
|
||||
// normalize the vec, it needs to be done regardless
|
||||
vm_vec_normalize(&temp_vec);
|
||||
bool fix_elevation = false;
|
||||
bool fix_base_rot = false;
|
||||
|
||||
if (dot < tp->turret_fov) {
|
||||
dot = tp->turret_fov;
|
||||
fix_elevation = true;
|
||||
}
|
||||
if (dot > tp->turret_max_fov) {
|
||||
dot = tp->turret_max_fov;
|
||||
fix_elevation = true;
|
||||
}
|
||||
|
||||
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
|
||||
vec3d temp_vec2;
|
||||
vm_vec_rotate(&temp_vec2, &temp_vec, &ss->world_to_turret_matrix);
|
||||
|
||||
// now in turrets frame of reference
|
||||
// check if math is actually possible
|
||||
if (!((temp_vec2.xyz.x == 0) && (temp_vec2.xyz.y == 0))) {
|
||||
float temp_z = temp_vec2.xyz.z;
|
||||
temp_vec2.xyz.z = 0.0f;
|
||||
// make sure null vecs wont happen
|
||||
if (!IS_VEC_NULL_SQ_SAFE(&temp_vec2)) {
|
||||
vm_vec_normalize(&temp_vec2);
|
||||
// only do this if it actually is required
|
||||
if (-temp_vec2.xyz.y < tp->turret_y_fov) {
|
||||
float check_pos = 1;
|
||||
|
||||
fix_base_rot = true;
|
||||
temp_vec2.xyz.y = -tp->turret_y_fov;
|
||||
if (temp_vec2.xyz.x < 0)
|
||||
check_pos = -1;
|
||||
temp_vec2.xyz.x = check_pos * sqrtf(1 - (temp_vec2.xyz.y*temp_vec2.xyz.y));
|
||||
|
||||
// restore z component
|
||||
float scaler = sqrtf(1 - (temp_z*temp_z));
|
||||
vm_vec_scale(&temp_vec2,scaler);
|
||||
temp_vec2.xyz.z = temp_z;
|
||||
// back to world frame
|
||||
vm_vec_unrotate(&temp_vec, &temp_vec2, &ss->world_to_turret_matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fix_elevation || fix_base_rot) {
|
||||
if (fix_elevation) {
|
||||
multiplier = sqrtf(1 - (dot*dot));
|
||||
// keep the temp_vec scaled with the tweaked vector
|
||||
vm_vec_scale(&temp_vec, multiplier);
|
||||
}
|
||||
vm_vec_scale_add(v2e, &temp_vec, tvec, dot);
|
||||
// and we are done with v2e...
|
||||
vm_vec_scale_add(predicted_pos, tpos, v2e, target_dist);
|
||||
// and we are done with predicted position
|
||||
return true;
|
||||
} else {
|
||||
mprintf(("Warning: Function 'is_object_radius_in_turret_fov' was called\nwithout need to fix turret alignments\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// outside of the expanded radii, unable to align, return false
|
||||
return false;
|
||||
return in_fov;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -555,6 +504,7 @@ void evaluate_obj_as_target(object *objp, eval_enemy_obj_struct *eeo)
|
||||
|
||||
if ( objp->type == OBJ_SHIP ) {
|
||||
shipp = &Ships[objp->instance];
|
||||
ship_info* sip = &Ship_info[shipp->ship_info_index];
|
||||
|
||||
// check on enemy team
|
||||
if ( !iff_matches_mask(shipp->team, eeo->enemy_team_mask) ) {
|
||||
@@ -594,16 +544,18 @@ void evaluate_obj_as_target(object *objp, eval_enemy_obj_struct *eeo)
|
||||
}
|
||||
}
|
||||
|
||||
// don't shoot at small ships if we shouldn't
|
||||
// don't shoot at big ships with huge weapons unless they have the flag
|
||||
if (eeo->eeo_flags & EEOF_BIG_ONLY) {
|
||||
if (!(Ship_info[shipp->ship_info_index].is_big_or_huge())) {
|
||||
if (sip->class_type == -1 || !(Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ^ Note the difference between these checks
|
||||
// V "small only" EXCLUDES only big ships, "huge" INCLUDES only big ships
|
||||
|
||||
// don't shoot at big ships if we shouldn't
|
||||
// don't shoot at ships ignored by small weapons if this is a small weapon
|
||||
if (eeo->eeo_flags & EEOF_SMALL_ONLY) {
|
||||
if ((Ship_info[shipp->ship_info_index].is_big_or_huge())) {
|
||||
if (sip->class_type >= 0 && (Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1179,23 +1131,18 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
|
||||
|
||||
if (!skip) {
|
||||
if ( Objects[aip->target_objnum].type == OBJ_SHIP ) {
|
||||
ship_info* esip = &Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index];
|
||||
// check for huge weapon and huge ship
|
||||
if ( !big_only_flag || (Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index].is_big_or_huge()) ) {
|
||||
if ( !big_only_flag || (esip->class_type >= 0 && Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only]) ) {
|
||||
// check for tagged only and tagged ship
|
||||
if ( tagged_only_flag && ship_is_tagged(&Objects[aip->target_objnum]) ) {
|
||||
// select new target if aip->target_objnum is out of field of view
|
||||
vec3d v2e;
|
||||
float dist;
|
||||
bool in_fov;
|
||||
dist = vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, tpos);
|
||||
vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, tpos);
|
||||
|
||||
in_fov = turret_fov_test(turret_subsys, tvec, &v2e);
|
||||
|
||||
if (turret_subsys->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
|
||||
if (in_fov == false)
|
||||
if (object_in_turret_fov(&Objects[aip->target_objnum], turret_subsys, tvec, tpos, dist + Objects[aip->target_objnum].radius))
|
||||
in_fov = true;
|
||||
}
|
||||
// MODIFY FOR ATTACKING BIG SHIP
|
||||
// dot += (0.5f * Objects[aip->target_objnum].radius / dist);
|
||||
if (in_fov) {
|
||||
@@ -1230,20 +1177,22 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
|
||||
* of the turret.
|
||||
*
|
||||
* @param objp Pointer to object
|
||||
* @param tp Turrent model system on that object
|
||||
* @param tp Turret model system on that object
|
||||
* @param gpos [Output] Global absolute position of gun firing point
|
||||
* @param gvec [Output] Global vector
|
||||
*
|
||||
* @note The gun normal is the unrotated gun normal, (the center of the FOV cone), not
|
||||
* the actual gun normal given using the current turret heading. But it _is_ rotated into the model's orientation
|
||||
* in global space.
|
||||
* @note2 Because of this, both single-part and multi-part turrets are treated the same way; no need to find the multi-part's gun submodel.
|
||||
*/
|
||||
void ship_get_global_turret_info(object *objp, model_subsystem *tp, vec3d *gpos, vec3d *gvec)
|
||||
void ship_get_global_turret_info(const object *objp, const model_subsystem *tp, vec3d *gpos, vec3d *gvec)
|
||||
{
|
||||
// vm_vec_unrotate(gpos, &tp->turret_avg_firing_point, &objp->orient);
|
||||
vm_vec_unrotate(gpos, &tp->pnt, &objp->orient);
|
||||
vm_vec_add2(gpos, &objp->pos);
|
||||
vm_vec_unrotate(gvec, &tp->turret_norm, &objp->orient);
|
||||
auto model_instance_num = Ships[objp->instance].model_instance_num;
|
||||
if (gpos)
|
||||
model_instance_find_world_point(gpos, &vmd_zero_vector, model_instance_num, tp->subobj_num, &objp->orient, &objp->pos);
|
||||
if (gvec)
|
||||
model_instance_find_world_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient, true);
|
||||
}
|
||||
|
||||
void turret_ai_update_aim(ai_info *aip, object *En_Objp, ship_subsys *ss);
|
||||
@@ -1303,7 +1252,8 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
|
||||
vm_vec_add2(&enemy_point, &ssp->last_aim_enemy_pos);
|
||||
} else {
|
||||
if ((lep->type == OBJ_SHIP) && (Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge())) {
|
||||
vm_vec_unrotate(&turret_norm, &tp->turret_norm, &objp->orient);
|
||||
// the turret norm here is from the perspective of the base submodel, not the gun submodel
|
||||
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, pm, pmi, pm->submodel[tp->subobj_num].parent, &objp->orient);
|
||||
ai_big_pick_attack_point_turret(lep, ssp, &tmp_pos, &turret_norm, &enemy_point, MIN(wip->max_speed * wip->lifetime, wip->weapon_range), tp->turret_fov);
|
||||
}
|
||||
else {
|
||||
@@ -1434,18 +1384,13 @@ int aifft_rotate_turret(object *objp, ship *shipp, ship_subsys *ss, object *lep,
|
||||
|
||||
in_fov = turret_fov_test(ss, gvec, &v2e);
|
||||
|
||||
if (ss->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
|
||||
if (in_fov == false)
|
||||
in_fov = is_object_radius_in_turret_fov(&Objects[ss->turret_enemy_objnum], ss, gvec, &gun_pos, &v2e, predicted_enemy_pos, 0.0f);
|
||||
}
|
||||
|
||||
if (in_fov) {
|
||||
ret_val = model_rotate_gun(objp, pm, pmi, tp, predicted_enemy_pos);
|
||||
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos);
|
||||
} else if ((tp->flags[Model::Subsystem_Flags::Turret_reset_idle]) &&(timestamp_elapsed(ss->rotation_timestamp))) {
|
||||
ret_val = model_rotate_gun(objp, pm, pmi, tp, predicted_enemy_pos, true);
|
||||
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos, true);
|
||||
}
|
||||
} else if ((ss->system_info->flags[Model::Subsystem_Flags::Turret_reset_idle]) && (timestamp_elapsed(ss->rotation_timestamp))) {
|
||||
ret_val = model_rotate_gun(objp, pm, pmi, ss->system_info, predicted_enemy_pos, true);
|
||||
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos, true);
|
||||
}
|
||||
|
||||
// by default "ret_val" should be set to 1 for multi-part turrets, and 0 for single-part turrets
|
||||
@@ -1477,7 +1422,7 @@ float aifft_compute_turret_dot(object *objp, object *enemy_objp, vec3d *abs_gunp
|
||||
if (ship_subsystem_in_sight(enemy_objp, enemy_subsysp, abs_gunposp, &subobj_pos, 1, &dot_out, &vector_out)) {
|
||||
vec3d turret_norm;
|
||||
|
||||
vm_vec_unrotate(&turret_norm, &turret_subsysp->system_info->turret_norm, &objp->orient);
|
||||
model_instance_find_world_dir(&turret_norm, &turret_subsysp->system_info->turret_norm, Ships[objp->instance].model_instance_num, turret_subsysp->system_info->subobj_num, &objp->orient, true);
|
||||
float dot_return = vm_vec_dot(&turret_norm, &vector_out);
|
||||
|
||||
if (Ai_info[Ships[objp->instance].ai_index].ai_profile_flags[AI::Profile_Flags::Smart_subsystem_targeting_for_turrets]) {
|
||||
@@ -1547,8 +1492,7 @@ ship_subsys *aifft_find_turret_subsys(object *objp, ship_subsys *ssp, object *en
|
||||
|
||||
// Compute absolute gun position.
|
||||
vec3d abs_gun_pos;
|
||||
vm_vec_unrotate(&abs_gun_pos, &ssp->system_info->pnt, &objp->orient);
|
||||
vm_vec_add2(&abs_gun_pos, &objp->pos);
|
||||
ship_get_global_turret_info(objp, ssp->system_info, &abs_gun_pos, nullptr);
|
||||
|
||||
// Only pick a turret to attack on large ships.
|
||||
if (!esip->is_big_or_huge())
|
||||
@@ -1661,8 +1605,10 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
|
||||
{
|
||||
Assert(weapon_num < MAX_SHIP_WEAPONS);
|
||||
float wait = 1000.0f;
|
||||
// should subtract 1 from firepoints to match behavior of wip->burst_shots
|
||||
int burst_shots = (wip->burst_flags[Weapon::Burst_Flags::Num_firepoints_burst_shots] ? turret->system_info->turret_num_firing_points - 1 : wip->burst_shots);
|
||||
|
||||
if (wip->burst_shots > turret->weapons.burst_counter[weapon_num]) {
|
||||
if (burst_shots > turret->weapons.burst_counter[weapon_num]) {
|
||||
wait *= wip->burst_delay;
|
||||
turret->weapons.burst_counter[weapon_num]++;
|
||||
} else {
|
||||
@@ -1672,10 +1618,12 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
|
||||
} else {
|
||||
wait *= wip->fire_wait;
|
||||
}
|
||||
if ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
turret->weapons.burst_counter[weapon_num] = (myrand() % wip->burst_shots);
|
||||
if ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
turret->weapons.burst_counter[weapon_num] = Random::next(burst_shots);
|
||||
turret->weapons.burst_seed[weapon_num] = Random::next();
|
||||
} else {
|
||||
turret->weapons.burst_counter[weapon_num] = 0;
|
||||
turret->weapons.burst_seed[weapon_num] = Random::next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1686,7 +1634,7 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
|
||||
fs_dest = &turret->weapons.next_secondary_fire_stamp[weapon_num - MAX_SHIP_PRIMARY_BANKS];
|
||||
|
||||
//Check for the new cooldown flag
|
||||
if(!((wip->wi_flags[Weapon::Info_Flags::Same_turret_cooldown]) || ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Fast_firing]))))
|
||||
if(!((wip->wi_flags[Weapon::Info_Flags::Same_turret_cooldown]) || ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Fast_firing]))))
|
||||
{
|
||||
|
||||
// make side even for team vs. team
|
||||
@@ -1769,7 +1717,7 @@ void turret_update_enemy_in_range(ship_subsys *turret, float seconds)
|
||||
/**
|
||||
* Fire a weapon from a turret
|
||||
*/
|
||||
bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, vec3d *turret_pos, vec3d *turret_fvec, vec3d *predicted_pos = NULL, float flak_range_override = 100.0f)
|
||||
bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, vec3d *turret_pos, vec3d *firing_vec, vec3d *predicted_pos = nullptr, float flak_range_override = 100.0f)
|
||||
{
|
||||
matrix turret_orient;
|
||||
int weapon_objnum;
|
||||
@@ -1834,8 +1782,25 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
#endif
|
||||
|
||||
if (check_ok_to_fire(parent_objnum, turret->turret_enemy_objnum, wip, -1, turret)) {
|
||||
vm_vector_2_matrix(&turret_orient, turret_fvec, NULL, NULL);
|
||||
turret->turret_last_fire_direction = *turret_fvec;
|
||||
ship_weapon* swp = &turret->weapons;
|
||||
turret->turret_last_fire_direction = *firing_vec;
|
||||
|
||||
if (turret->turret_inaccuracy > 0.0f)
|
||||
vm_vec_random_cone(firing_vec, firing_vec, turret->turret_inaccuracy);
|
||||
|
||||
vm_vector_2_matrix(&turret_orient, firing_vec, nullptr, nullptr);
|
||||
|
||||
// grab and set some burst data before turret_set_next_fire_timestamp wipes it
|
||||
int old_burst_seed = swp->burst_seed[weapon_num];
|
||||
int old_burst_counter = swp->burst_counter[weapon_num];
|
||||
// only used by type 5 beams
|
||||
if (turret->weapons.burst_counter[weapon_num] == 0) {
|
||||
swp->per_burst_rot += wip->b_info.t5info.per_burst_rot;
|
||||
if (swp->per_burst_rot > PI2)
|
||||
swp->per_burst_rot -= PI2;
|
||||
else if (swp->per_burst_rot < -PI2)
|
||||
swp->per_burst_rot += PI2;
|
||||
}
|
||||
|
||||
// set next fire timestamp for the turret
|
||||
if (last_shot_in_salvo)
|
||||
@@ -1861,7 +1826,10 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
else
|
||||
fire_info.target_subsys = NULL;
|
||||
fire_info.turret = turret;
|
||||
fire_info.burst_seed = old_burst_seed;
|
||||
fire_info.fire_method = BFM_TURRET_FIRED;
|
||||
fire_info.per_burst_rotation = swp->per_burst_rot;
|
||||
fire_info.burst_index = old_burst_counter;
|
||||
|
||||
// fire a beam weapon
|
||||
weapon_objnum = beam_fire(&fire_info);
|
||||
@@ -1872,9 +1840,11 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
parent_ship->last_fired_turret = turret;
|
||||
turret->last_fired_weapon_info_index = turret_weapon_class;
|
||||
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", nullptr, "Beam", objp, "Target", &Objects[turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
|
||||
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
|
||||
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", nullptr, "Beam", objp, "Target", &Objects[turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
|
||||
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
|
||||
}
|
||||
}
|
||||
|
||||
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike
|
||||
@@ -1882,7 +1852,6 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
}
|
||||
// don't fire swam, but set up swarm info instead
|
||||
else if ((wip->wi_flags[Weapon::Info_Flags::Swarm]) || (wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
|
||||
ship_weapon *swp = &turret->weapons;
|
||||
if (swp->current_secondary_bank < 0) {
|
||||
swp->current_secondary_bank = 0;
|
||||
}
|
||||
@@ -1908,8 +1877,6 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
else {
|
||||
for (int i = 0; i < wip->shots; i++) {
|
||||
if (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) {
|
||||
ship_weapon *swp;
|
||||
swp = &turret->weapons;
|
||||
int bank_to_fire, num_slots = turret->system_info->turret_num_firing_points;
|
||||
if (wip->subtype == WP_LASER) {
|
||||
int points;
|
||||
@@ -1989,7 +1956,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
}
|
||||
// zookeeper - Firepoints should cycle normally between shots,
|
||||
// so we need to get the position info separately for each shot
|
||||
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, turret_pos, turret_fvec, 1, NULL);
|
||||
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, turret_pos, firing_vec, 1, nullptr);
|
||||
|
||||
weapon_objnum = weapon_create( turret_pos, &turret_orient, turret_weapon_class, parent_objnum, -1, 1, 0,0.0f, turret);
|
||||
weapon_set_tracking_info(weapon_objnum, parent_objnum, turret->turret_enemy_objnum, 1, turret->targeted_subsys);
|
||||
@@ -2007,14 +1974,14 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
// AL 1-6-97: Store pointer to turret subsystem
|
||||
wp->turret_subsys = turret;
|
||||
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", objp, "Beam", nullptr, "Target", &Objects[turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
|
||||
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
|
||||
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", objp, "Beam", nullptr, "Target", &Objects[turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
|
||||
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
|
||||
}
|
||||
|
||||
// if the gun is a flak gun
|
||||
if (wip->wi_flags[Weapon::Info_Flags::Flak]) {
|
||||
// show a muzzle flash
|
||||
flak_muzzle_flash(turret_pos, turret_fvec, &Objects[parent_ship->objnum].phys_info, turret_weapon_class);
|
||||
if (wip->wi_flags[Weapon::Info_Flags::Flak]) {
|
||||
|
||||
if(predicted_pos != NULL)
|
||||
{
|
||||
@@ -2029,9 +1996,10 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
flak_set_range(objp, flak_range_override);
|
||||
}
|
||||
}
|
||||
// otherwise just do mflash if the weapon has it
|
||||
else if (wip->muzzle_flash >= 0) {
|
||||
mflash_create(turret_pos, turret_fvec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
|
||||
|
||||
// do mflash if the weapon has it
|
||||
if (wip->muzzle_flash >= 0) {
|
||||
mflash_create(turret_pos, firing_vec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
|
||||
}
|
||||
|
||||
// in multiplayer (and the master), then send a turret fired packet.
|
||||
@@ -2124,9 +2092,11 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
|
||||
Ships[Objects[tsi->parent_objnum].instance].last_fired_turret = tsi->turret;
|
||||
tsi->turret->last_fired_weapon_info_index = tsi->weapon_class;
|
||||
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[tsi->parent_objnum], "Weapon", &Objects[weapon_objnum], "Beam", nullptr, "Target", &Objects[tsi->turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[tsi->parent_objnum]);
|
||||
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
|
||||
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[tsi->parent_objnum], "Weapon", &Objects[weapon_objnum], "Beam", nullptr, "Target", &Objects[tsi->turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[tsi->parent_objnum]);
|
||||
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
|
||||
}
|
||||
|
||||
// muzzle flash?
|
||||
if (Weapon_info[tsi->weapon_class].muzzle_flash >= 0) {
|
||||
@@ -2157,13 +2127,15 @@ int Num_find_turret_enemy = 0;
|
||||
int Num_turrets_fired = 0;
|
||||
|
||||
/**
|
||||
* Given a turret tp and its parent parent_objnum, fire from the turret at its enemy.
|
||||
* Previously called ai_fire_from_turret()
|
||||
* Given a ship and a turret subsystem, handle all its behavior, mostly targeting and shooting but also
|
||||
* all turret movement and resetting when idle
|
||||
*/
|
||||
extern int Nebula_sec_range;
|
||||
void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
|
||||
{
|
||||
float weapon_firing_range;
|
||||
float WeaponMinRange; // *Weapon minimum firing range -Et1
|
||||
float weapon_min_range; // *Weapon minimum firing range -Et1
|
||||
vec3d v2e;
|
||||
object *lep; // Last enemy pointer
|
||||
model_subsystem *tp = ss->system_info;
|
||||
@@ -2235,6 +2207,8 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
{
|
||||
ss->turret_enemy_objnum = -1;
|
||||
lep = nullptr;
|
||||
ss->flags.remove(Ship::Subsystem_Flags::Forced_target);
|
||||
ss->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
|
||||
}
|
||||
|
||||
Assert((shipp->objnum >= 0) && (shipp->objnum < MAX_OBJECTS));
|
||||
@@ -2242,25 +2216,10 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
objp = &Objects[shipp->objnum];
|
||||
Assert(objp->type == OBJ_SHIP);
|
||||
|
||||
// Wanderer - make sure turrets already have all the data
|
||||
if ( !(tp->flags[Model::Subsystem_Flags::Turret_matrix]) )
|
||||
{
|
||||
if (!(tp->turret_gun_sobj == tp->subobj_num))
|
||||
{
|
||||
auto pmi = model_get_instance(shipp->model_instance_num);
|
||||
auto pm = model_get(pmi->model_num);
|
||||
model_make_turret_matrix(pm, pmi, tp);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the turret info for all guns, not one gun in particular.
|
||||
vec3d gvec, gpos;
|
||||
ship_get_global_turret_info(objp, tp, &gpos, &gvec);
|
||||
|
||||
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
|
||||
vm_matrix_x_matrix( &ss->world_to_turret_matrix, &objp->orient, &tp->turret_matrix );
|
||||
}
|
||||
|
||||
// Rotate the turret even if time hasn't elapsed, since it needs to turn to face its target.
|
||||
int use_angles = aifft_rotate_turret(objp, shipp, ss, lep, &predicted_enemy_pos, &gvec);
|
||||
|
||||
@@ -2351,12 +2310,12 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
|
||||
{
|
||||
weapon_firing_range = wip->lssm_lock_range;
|
||||
WeaponMinRange = 0.0f;
|
||||
weapon_min_range = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
weapon_firing_range = MIN(wip->lifetime * wip->max_speed, wip->weapon_range);
|
||||
WeaponMinRange = wip->WeaponMinRange;
|
||||
weapon_min_range = wip->weapon_min_range;
|
||||
}
|
||||
|
||||
// if beam weapon in nebula and target not tagged, decrease firing range
|
||||
@@ -2369,19 +2328,21 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
}
|
||||
|
||||
// Don't try to fire beyond weapon_limit_range (or within min range)
|
||||
if (dist_to_enemy < WeaponMinRange || dist_to_enemy > weapon_firing_range) {
|
||||
if (dist_to_enemy < weapon_min_range || dist_to_enemy > weapon_firing_range) {
|
||||
// it's possible another weapon is in range, but if not,
|
||||
// we will end up selecting a new target
|
||||
continue;
|
||||
}
|
||||
|
||||
ship_info* esip = lep->type != OBJ_SHIP ? nullptr : &Ship_info[Ships[lep->instance].ship_info_index];
|
||||
|
||||
// If targeted a small ship and have a huge weapon, don't fire. But this shouldn't happen, as a small ship should not get selected.
|
||||
if ( wip->wi_flags[Weapon::Info_Flags::Huge] ) {
|
||||
if ( lep->type != OBJ_SHIP ) {
|
||||
tentative_return = true;
|
||||
continue;
|
||||
}
|
||||
if ( !(Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge()) ) {
|
||||
if ( esip->class_type >= 0 && !(Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
|
||||
tentative_return = true;
|
||||
continue;
|
||||
}
|
||||
@@ -2389,7 +2350,8 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
|
||||
// Similar check for small weapons
|
||||
if ( wip->wi_flags[Weapon::Info_Flags::Small_only] ) {
|
||||
if ( (lep->type == OBJ_SHIP) && Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge() ) {
|
||||
if ( (lep->type == OBJ_SHIP) && esip->class_type >= 0 &&
|
||||
(Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
|
||||
tentative_return = true;
|
||||
continue;
|
||||
}
|
||||
@@ -2433,6 +2395,11 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
tentative_return = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!weapon_target_satisfies_lock_restrictions(wip, lep)) {
|
||||
tentative_return = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// check tagged-only for bombs only if the flag is set; see Mantis #3114
|
||||
@@ -2453,8 +2420,10 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
|
||||
//none of our guns can hit the enemy, so find a new enemy
|
||||
if (num_valid == 0) {
|
||||
ss->turret_enemy_objnum = -1;
|
||||
ss->turret_time_enemy_in_range = 0.0f;
|
||||
if (!ss->flags[Ship::Subsystem_Flags::Forced_target]) {
|
||||
ss->turret_enemy_objnum = -1;
|
||||
ss->turret_time_enemy_in_range = 0.0f;
|
||||
}
|
||||
|
||||
// in the original code, we returned where the "tentative return" variable was set,
|
||||
// so return for real now, since we don't have a reason to remain in the function
|
||||
@@ -2462,8 +2431,16 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
return;
|
||||
}
|
||||
|
||||
// if we've got Forced_subsys_target targeted_subsys *really* shouldnt be null but just in case...
|
||||
if (ss->flags[Ship::Subsystem_Flags::Forced_subsys_target] && ss->targeted_subsys != nullptr) {
|
||||
if (ss->targeted_subsys->current_hits <= 0.0f) {
|
||||
ss->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
|
||||
ss->flags.remove(Ship::Subsystem_Flags::Forced_target);
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe pick a new enemy, unless targeting has been taken over by scripting
|
||||
if ( turret_should_pick_new_target(ss) && !ss->scripting_target_override ) {
|
||||
if ( turret_should_pick_new_target(ss) && !ss->scripting_target_override && !(ss->flags[Ship::Subsystem_Flags::Forced_target])) {
|
||||
Num_find_turret_enemy++;
|
||||
int objnum = find_turret_enemy(ss, parent_objnum, &gpos, &gvec, ss->turret_enemy_objnum);
|
||||
|
||||
@@ -2736,7 +2713,7 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
{
|
||||
// If nothing is OK to fire (lost track of the target?)
|
||||
// reset the target (so we don't continue to track what we can't hit)
|
||||
if (tp->flags[Model::Subsystem_Flags::Turret_only_target_if_can_fire])
|
||||
if (tp->flags[Model::Subsystem_Flags::Turret_only_target_if_can_fire] && !(ss->flags[Ship::Subsystem_Flags::Forced_target]))
|
||||
{
|
||||
ss->turret_enemy_objnum = -1; // Reset enemy objnum, find a new one next frame.
|
||||
ss->turret_time_enemy_in_range = 0.0f;
|
||||
@@ -2771,7 +2748,7 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!(ss->flags[Ship::Subsystem_Flags::Forced_target]))
|
||||
{
|
||||
// Lost him!
|
||||
ss->turret_enemy_objnum = -1; // Reset enemy objnum, find a new one next frame.
|
||||
@@ -2794,16 +2771,20 @@ bool turret_adv_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mo
|
||||
model_subsystem *tp = ss->system_info;
|
||||
float dot = vm_vec_dot(v2e, gvec);
|
||||
if (((dot + size_mod) >= tp->turret_fov) && ((dot - size_mod) <= tp->turret_max_fov)) {
|
||||
object* objp = &Objects[ss->parent_objnum];
|
||||
matrix turret_matrix;
|
||||
turret_instance_find_world_orient(&turret_matrix, Ships[objp->instance].model_instance_num, tp->subobj_num, &objp->orient);
|
||||
|
||||
vec3d of_dst;
|
||||
vm_vec_rotate( &of_dst, v2e, &ss->world_to_turret_matrix );
|
||||
if ((of_dst.xyz.x == 0) && (of_dst.xyz.y == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
of_dst.xyz.z = 0;
|
||||
vm_vec_rotate(&of_dst, v2e, &turret_matrix);
|
||||
if ((of_dst.xyz.x == 0) && (of_dst.xyz.z == 0))
|
||||
return true;
|
||||
else {
|
||||
of_dst.xyz.y = 0;
|
||||
if (!IS_VEC_NULL_SQ_SAFE(&of_dst)) {
|
||||
vm_vec_normalize(&of_dst);
|
||||
// now we have 2d vector with lenght of 1 that points at the targets direction after being rotated to turrets FOR
|
||||
if ((-of_dst.xyz.y + size_mod) >= tp->turret_y_fov)
|
||||
if ((of_dst.xyz.z + size_mod) >= tp->turret_base_fov)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2814,7 +2795,7 @@ bool turret_adv_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mo
|
||||
bool turret_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mod)
|
||||
{
|
||||
bool in_fov = false;
|
||||
if (ss->system_info->flags[Model::Subsystem_Flags::Turret_alt_math])
|
||||
if (ss->system_info->flags[Model::Subsystem_Flags::Turret_restricted_fov])
|
||||
in_fov = turret_adv_fov_test(ss, gvec, v2e, size_mod);
|
||||
else
|
||||
in_fov = turret_std_fov_test(ss, gvec, v2e, size_mod);
|
||||
|
||||
+131
-138
@@ -38,7 +38,6 @@
|
||||
#include "ship/ship.h"
|
||||
#include "ship/shipfx.h"
|
||||
#include "ship/shiphit.h"
|
||||
#include "species_defs/species_defs.h"
|
||||
#include "stats/scoring.h"
|
||||
#include "weapon/weapon.h"
|
||||
|
||||
@@ -50,9 +49,9 @@ asteroid_obj Asteroid_objs[MAX_ASTEROID_OBJS]; // array used to store asteroid o
|
||||
asteroid_obj Asteroid_obj_list; // head of linked list of asteroid_obj structs
|
||||
|
||||
// used for randomly generating debris type when there are multiple sizes.
|
||||
#define SMALL_DEBRIS_WEIGHT 8
|
||||
#define MEDIUM_DEBRIS_WEIGHT 4
|
||||
#define LARGE_DEBRIS_WEIGHT 1
|
||||
const float SMALL_DEBRIS_WEIGHT = 8.0f;
|
||||
const float MEDIUM_DEBRIS_WEIGHT = 4.0f;
|
||||
const float LARGE_DEBRIS_WEIGHT = 1.0f;
|
||||
|
||||
int Asteroids_enabled = 1;
|
||||
int Num_asteroids = 0;
|
||||
@@ -257,7 +256,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if((asteroid_type < 0) || (asteroid_type >= (((int)Species_info.size() + 1) * NUM_DEBRIS_SIZES))) {
|
||||
if((asteroid_type < 0) || (asteroid_type >= (int)Asteroid_info.size())) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -325,7 +324,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
|
||||
asteroid_default_flagset += Object::Object_Flags::Physics;
|
||||
asteroid_default_flagset += Object::Object_Flags::Collides;
|
||||
|
||||
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset);
|
||||
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset, false);
|
||||
|
||||
if ( (objnum == -1) || (objnum >= MAX_OBJECTS) ) {
|
||||
mprintf(("Couldn't create asteroid -- out of object slots\n"));
|
||||
@@ -488,31 +487,6 @@ static void asteroid_load(int asteroid_info_index, int asteroid_subtype)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a weight that depends on asteroid size.
|
||||
*
|
||||
* The weight is then used to determine the frequencty of different sizes of ship debris
|
||||
*/
|
||||
static int get_debris_weight(int ship_debris_index)
|
||||
{
|
||||
int size = ship_debris_index % NUM_DEBRIS_SIZES;
|
||||
switch (size)
|
||||
{
|
||||
case ASTEROID_TYPE_SMALL:
|
||||
return SMALL_DEBRIS_WEIGHT;
|
||||
|
||||
case ASTEROID_TYPE_MEDIUM:
|
||||
return MEDIUM_DEBRIS_WEIGHT;
|
||||
|
||||
case ASTEROID_TYPE_LARGE:
|
||||
return LARGE_DEBRIS_WEIGHT;
|
||||
|
||||
default:
|
||||
Int3();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all the asteroids for the mission
|
||||
*/
|
||||
@@ -522,14 +496,14 @@ void asteroid_create_all()
|
||||
|
||||
// ship_debris_odds_table keeps track of debris type of the next debris piece
|
||||
// each different type (size) of debris piece has a diffenent weight, smaller weighted more heavily than larger.
|
||||
// choose next type from table ship_debris_odds_table by rand()%max_weighted_range,
|
||||
// choose next type from table ship_debris_odds_table by Random::next()%max_weighted_range,
|
||||
// the threshold *below* which the debris type is selected.
|
||||
struct {
|
||||
int random_threshold;
|
||||
float random_threshold;
|
||||
int debris_type;
|
||||
} ship_debris_odds_table[MAX_ACTIVE_DEBRIS_TYPES];
|
||||
|
||||
int max_weighted_range = 0;
|
||||
float max_weighted_range = 0.0f;
|
||||
|
||||
if (!Asteroids_enabled)
|
||||
return;
|
||||
@@ -552,7 +526,7 @@ void asteroid_create_all()
|
||||
|
||||
// Calculate the odds table
|
||||
for (idx=0; idx<num_debris_types; idx++) {
|
||||
int debris_weight = get_debris_weight(Asteroid_field.field_debris_type[idx]);
|
||||
float debris_weight = Asteroid_info[Asteroid_field.field_debris_type[idx]].spawn_weight;
|
||||
ship_debris_odds_table[idx].random_threshold = max_weighted_range + debris_weight;
|
||||
ship_debris_odds_table[idx].debris_type = Asteroid_field.field_debris_type[idx];
|
||||
max_weighted_range += debris_weight;
|
||||
@@ -590,7 +564,7 @@ void asteroid_create_all()
|
||||
// For asteroid, load only large asteroids
|
||||
|
||||
// get a valid subtype
|
||||
int subtype = rand() % NUM_DEBRIS_POFS;
|
||||
int subtype = Random::next(NUM_DEBRIS_POFS);
|
||||
while (Asteroid_field.field_debris_type[subtype] == -1) {
|
||||
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
|
||||
}
|
||||
@@ -599,7 +573,7 @@ void asteroid_create_all()
|
||||
} else {
|
||||
Assert(num_debris_types > 0);
|
||||
|
||||
int rand_choice = rand() % max_weighted_range;
|
||||
float rand_choice = frand() * max_weighted_range;
|
||||
|
||||
for (idx=0; idx<MAX_ACTIVE_DEBRIS_TYPES; idx++) {
|
||||
// for ship debris, choose type according to odds table
|
||||
@@ -784,7 +758,7 @@ static void maybe_throw_asteroid(int count)
|
||||
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
||||
object *A = &Objects[so->objnum];
|
||||
if (so->objnum == Asteroid_throw_objnum) {
|
||||
int subtype = rand() % NUM_DEBRIS_POFS;
|
||||
int subtype = Random::next(NUM_DEBRIS_POFS);
|
||||
while (Asteroid_field.field_debris_type[subtype] == -1) {
|
||||
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
|
||||
}
|
||||
@@ -1045,13 +1019,13 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
smi->collision_checked = false;
|
||||
|
||||
// set angles for last frame (need to set to prev to get p0)
|
||||
angles copy_angles = smi->angs;
|
||||
matrix copy_matrix = smi->canonical_orient;
|
||||
|
||||
// find the start and end positions of the sphere in submodel RF
|
||||
smi->angs = smi->prev_angs;
|
||||
smi->canonical_orient = smi->canonical_prev_orient;
|
||||
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
|
||||
|
||||
smi->angs = copy_angles;
|
||||
smi->canonical_orient = copy_matrix;
|
||||
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
||||
|
||||
mc.p0 = &p0;
|
||||
@@ -1069,7 +1043,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
|
||||
// set up asteroid_hit_info for rotating submodel
|
||||
if (asteroid_hit_info->edge_hit == 0) {
|
||||
model_instance_find_obj_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
@@ -1101,7 +1075,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
|
||||
// get collision normal if not edge hit
|
||||
if (asteroid_hit_info->edge_hit == 0) {
|
||||
model_instance_find_obj_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
@@ -1350,7 +1324,7 @@ static void asteroid_do_area_effect(object *asteroid_objp)
|
||||
if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
|
||||
continue;
|
||||
|
||||
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage );
|
||||
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
|
||||
weapon_area_apply_blast(NULL, ship_objp, &asteroid_objp->pos, blast, 0);
|
||||
} // end for
|
||||
}
|
||||
@@ -1458,9 +1432,14 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
|
||||
|
||||
if ( timestamp_elapsed(asp->final_death_time) ) {
|
||||
vec3d relvec, vfh, tvec;
|
||||
bool skip = false;
|
||||
bool hooked = Script_system.IsActiveAction(CHA_DEATH);
|
||||
|
||||
Script_system.SetHookObject("Self", pasteroid_obj);
|
||||
if(!Script_system.IsConditionOverride(CHA_DEATH, pasteroid_obj))
|
||||
if (hooked) {
|
||||
Script_system.SetHookObject("Self", pasteroid_obj);
|
||||
skip = Script_system.IsConditionOverride(CHA_DEATH, pasteroid_obj);
|
||||
}
|
||||
if (!skip)
|
||||
{
|
||||
pasteroid_obj->flags.set(Object::Object_Flags::Should_be_dead);
|
||||
|
||||
@@ -1510,7 +1489,7 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
|
||||
int num_roids_var = split->max - split->min;
|
||||
|
||||
if (num_roids_var > 0)
|
||||
num_roids += rand() % num_roids_var;
|
||||
num_roids += Random::next(num_roids_var);
|
||||
|
||||
if (num_roids > 0)
|
||||
for (int i=0; i<num_roids; i++)
|
||||
@@ -1565,8 +1544,10 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
|
||||
}
|
||||
asp->final_death_time = timestamp(-1);
|
||||
}
|
||||
Script_system.RunCondition(CHA_DEATH, pasteroid_obj);
|
||||
Script_system.RemHookVar("Self");
|
||||
if (hooked) {
|
||||
Script_system.RunCondition(CHA_DEATH, pasteroid_obj);
|
||||
Script_system.RemHookVar("Self");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1788,7 +1769,7 @@ static void asteroid_parse_section(asteroid_info *asip)
|
||||
}
|
||||
|
||||
required_string("$Detail distance:");
|
||||
asip->num_detail_levels = stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
|
||||
asip->num_detail_levels = (int)stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
|
||||
|
||||
required_string("$Max Speed:");
|
||||
stuff_float(&asip->max_speed);
|
||||
@@ -1802,7 +1783,7 @@ static void asteroid_parse_section(asteroid_info *asip)
|
||||
|
||||
if(optional_string("$Explosion Animations:")){
|
||||
int temp[MAX_FIREBALL_TYPES];
|
||||
int parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
|
||||
auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
|
||||
asip->explosion_bitmap_anims.clear();
|
||||
asip->explosion_bitmap_anims.insert(asip->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
|
||||
}
|
||||
@@ -1849,20 +1830,81 @@ static void asteroid_parse_section(asteroid_info *asip)
|
||||
} else
|
||||
Warning(LOCATION, "Invalid asteroid reference %i used for $Split in asteroids table, ignoring.", split_type);
|
||||
}
|
||||
|
||||
if (optional_string("$Spawn Weight:")) {
|
||||
stuff_float(&asip->spawn_weight);
|
||||
if (asip->spawn_weight <= 0.0f) {
|
||||
Warning(LOCATION, "Spawn weight for asteroid '%s' must be greater than 0", asip->name);
|
||||
asip->spawn_weight = 1.0f;
|
||||
}
|
||||
} else {
|
||||
switch (Asteroid_info.size() % NUM_DEBRIS_SIZES)
|
||||
{
|
||||
case ASTEROID_TYPE_SMALL:
|
||||
asip->spawn_weight = SMALL_DEBRIS_WEIGHT;
|
||||
break;
|
||||
case ASTEROID_TYPE_MEDIUM:
|
||||
asip->spawn_weight = MEDIUM_DEBRIS_WEIGHT;
|
||||
break;
|
||||
case ASTEROID_TYPE_LARGE:
|
||||
asip->spawn_weight = LARGE_DEBRIS_WEIGHT;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE("Here be dragons");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// changes the name to "[species] Debris" if it had a name like "[species] debris #"
|
||||
void maybe_change_asteroid_name(asteroid_info* asip) {
|
||||
|
||||
SCP_string name = asip->name;
|
||||
size_t split = std::string::npos;
|
||||
|
||||
for (species_info species : Species_info) {
|
||||
if (name.compare(0, strlen(species.species_name), species.species_name) == 0) {
|
||||
split = strlen(species.species_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (split == std::string::npos)
|
||||
return;
|
||||
|
||||
SCP_string remaining_name = name.substr(split + 1, name.length());
|
||||
|
||||
split = remaining_name.find(' ');
|
||||
|
||||
if (split == std::string::npos)
|
||||
return;
|
||||
|
||||
SCP_string debris = remaining_name.substr(0, split);
|
||||
SCP_string num = remaining_name.substr(split + 1, remaining_name.length());
|
||||
|
||||
if (stricmp(debris.c_str(), "Debris") != 0)
|
||||
return;
|
||||
|
||||
if (num.empty() || std::find_if(num.begin(), num.end(), [](char c) {
|
||||
return !std::isdigit(c, SCP_default_locale);
|
||||
}) != num.end())
|
||||
return;
|
||||
|
||||
// make sure this asteroid would correspond the 'species section' of the old style retail asteroids
|
||||
if (Asteroid_info.size() < NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES && Asteroid_info.size() >= NUM_DEBRIS_SIZES) {
|
||||
int idx = (int)(Asteroid_info.size()) / NUM_DEBRIS_SIZES - 1;
|
||||
strcpy_s(asip->name, Species_info[idx].species_name);
|
||||
strcat(asip->name, " ");
|
||||
strcat(asip->name, XSTR("debris", 348));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Read in data from asteroid.tbl into Asteroid_info[] array.
|
||||
|
||||
After this function is completes, the Asteroid_info[] array will
|
||||
contain exactly three asteroids per species plus exactly three
|
||||
generic asteroids. These three asteroids are a set
|
||||
of small, medium, and large asteroids in that exact order.
|
||||
|
||||
If asteroid.tbl contains too many or too few asteroids as per
|
||||
the rules above this function will abort the engine with an
|
||||
error message stating the number expected and it will write
|
||||
to the debug.log how everything was parsed.
|
||||
After this function is complete, the Asteroid_info[] array
|
||||
will have at least 3 generic asteroids plus whatever else
|
||||
special ones were added in the table. The generic asteroids are
|
||||
a set of small, medium, and large asteroids in that exact order.
|
||||
|
||||
Note that by saying "asteroid" this code is talking about
|
||||
the asteroids and the debris that make up asteroid fields
|
||||
@@ -1874,12 +1916,6 @@ that is being protected.
|
||||
static void asteroid_parse_tbl()
|
||||
{
|
||||
char impact_ani_file[MAX_FILENAME_LEN];
|
||||
|
||||
// How did we get here without having any species defined?
|
||||
Assertion(!Species_info.empty(),
|
||||
"Cannot parse asteroids/debris if there "
|
||||
"are no species for them to belong to."
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1889,8 +1925,6 @@ static void asteroid_parse_tbl()
|
||||
required_string("#Asteroid Types");
|
||||
|
||||
size_t tally = 0;
|
||||
size_t max_asteroids =
|
||||
NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES;
|
||||
|
||||
#ifndef NDEBUG
|
||||
SCP_vector<SCP_string> parsed_asteroids;
|
||||
@@ -1903,82 +1937,41 @@ static void asteroid_parse_tbl()
|
||||
|
||||
asteroid_parse_section(&new_asteroid);
|
||||
|
||||
size_t species = tally / NUM_DEBRIS_SIZES;
|
||||
if (tally >= max_asteroids)
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
// Bump the warning count in release so they get something
|
||||
// even if the message never gets displayed.
|
||||
Warning(LOCATION, "Ignoring extra asteroid/debris");
|
||||
#else
|
||||
SCP_string msg("Ignoring extra asteroid/debris '");
|
||||
msg.append(new_asteroid.name);
|
||||
msg.append("'\n");
|
||||
Warning(LOCATION, "%s", msg.c_str());
|
||||
parsed_asteroids.push_back(msg);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
maybe_change_asteroid_name(&new_asteroid);
|
||||
|
||||
#ifndef NDEBUG
|
||||
SCP_string msg;
|
||||
msg.append("Parsing asteroid: '");
|
||||
msg.append(new_asteroid.name);
|
||||
msg.append("' as a '");
|
||||
msg.append((species == 0) ? "generic" : Species_info[species - 1].species_name);
|
||||
msg.append("'");
|
||||
switch (tally % NUM_DEBRIS_SIZES) {
|
||||
case ASTEROID_TYPE_SMALL:
|
||||
msg.append(" small\n");
|
||||
break;
|
||||
case ASTEROID_TYPE_MEDIUM:
|
||||
msg.append(" medium\n");
|
||||
break;
|
||||
case ASTEROID_TYPE_LARGE:
|
||||
msg.append(" large\n");
|
||||
break;
|
||||
default:
|
||||
Error(LOCATION, "Get a coder! Math has broken!\n"
|
||||
"Important numbers:\n"
|
||||
"\ttally: " SIZE_T_ARG "\n"
|
||||
"\tNUM_DEBRIS_SIZES: %d\n",
|
||||
tally, NUM_DEBRIS_SIZES
|
||||
);
|
||||
msg.append(" unknown\n");
|
||||
}
|
||||
parsed_asteroids.push_back(msg);
|
||||
#endif
|
||||
Asteroid_info.push_back(new_asteroid);
|
||||
SCP_string msg;
|
||||
msg.append("Parsing asteroid: '");
|
||||
msg.append(new_asteroid.name);
|
||||
msg.append("' as a '");
|
||||
msg.append((tally >= NUM_DEBRIS_SIZES) ? "generic" : "special");
|
||||
msg.append("'");
|
||||
switch (tally % NUM_DEBRIS_SIZES) {
|
||||
case ASTEROID_TYPE_SMALL:
|
||||
msg.append(" small\n");
|
||||
break;
|
||||
case ASTEROID_TYPE_MEDIUM:
|
||||
msg.append(" medium\n");
|
||||
break;
|
||||
case ASTEROID_TYPE_LARGE:
|
||||
msg.append(" large\n");
|
||||
break;
|
||||
default:
|
||||
Error(LOCATION, "Get a coder! Math has broken!\n"
|
||||
"Important numbers:\n"
|
||||
"\ttally: " SIZE_T_ARG "\n"
|
||||
"\tNUM_DEBRIS_SIZES: %d\n",
|
||||
tally, NUM_DEBRIS_SIZES
|
||||
);
|
||||
msg.append(" unknown\n");
|
||||
}
|
||||
parsed_asteroids.push_back(msg);
|
||||
#endif
|
||||
Asteroid_info.push_back(new_asteroid);
|
||||
tally++;
|
||||
}
|
||||
required_string("#End");
|
||||
|
||||
if (tally != max_asteroids)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
mprintf(("Asteroid.tbl as parsed:\n"));
|
||||
for (SCP_vector<SCP_string>::iterator iter = parsed_asteroids.begin();
|
||||
iter != parsed_asteroids.end(); ++iter)
|
||||
{
|
||||
mprintf(("%s\n", iter->c_str()));
|
||||
}
|
||||
#endif
|
||||
Error(LOCATION,
|
||||
"Found " SIZE_T_ARG " asteroids/debris when " SIZE_T_ARG " expected\n\n"
|
||||
"<Number expected> = <Number of species> * %d + %d generic asteroids\n"
|
||||
SIZE_T_ARG " = " SIZE_T_ARG "*%d + %d\n\n"
|
||||
#ifdef NDEBUG
|
||||
"Run a debug build to see a list of all parsed asteroids\n",
|
||||
#else
|
||||
"See the debug.log for a listing of all parsed asteroids\n",
|
||||
#endif
|
||||
tally, max_asteroids,
|
||||
NUM_DEBRIS_SIZES, NUM_DEBRIS_SIZES,
|
||||
max_asteroids, Species_info.size(), NUM_DEBRIS_SIZES, NUM_DEBRIS_SIZES
|
||||
);
|
||||
}
|
||||
|
||||
Asteroid_impact_explosion_ani = -1;
|
||||
required_string("$Impact Explosion:");
|
||||
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
|
||||
|
||||
@@ -73,6 +73,8 @@ public:
|
||||
int model_num[NUM_DEBRIS_POFS];
|
||||
SCP_vector<int> explosion_bitmap_anims;
|
||||
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
|
||||
SCP_string display_name; // only used for hud targeting display and for 'ship' asteroids
|
||||
float spawn_weight; // ship asteroids only, relative proportion to spawn compared to other types in its asteroid field
|
||||
|
||||
asteroid_info( )
|
||||
: num_detail_levels( 0 ), max_speed( 0 ), damage_type_idx( 0 ),
|
||||
|
||||
@@ -716,7 +716,7 @@ bool StartAutopilot()
|
||||
vm_vec_scale(&pos, 5*speed_cap*tc_factor); // pos is now scaled by 5 times the speed (5 seconds ahead)
|
||||
vm_vec_add(&pos, &pos, &Autopilot_flight_leader->pos); // pos is now 5*speed cap in front of player ship
|
||||
|
||||
switch (myrand()%24)
|
||||
switch (Random::next(24))
|
||||
// 8 different ways of getting perp points
|
||||
// 4 of which will not be used when capships are present (anything below, or straight above)
|
||||
{
|
||||
@@ -795,7 +795,8 @@ bool StartAutopilot()
|
||||
vm_vec_scale(&perp, Autopilot_flight_leader->radius+radius);
|
||||
|
||||
// randomly scale up/down by up to 20%
|
||||
j = 20-myrand()%40; // [-20,20]
|
||||
// jg18 - original comment said [-20,20] but preserving existing behavior
|
||||
j = Random::next(-19, 20);
|
||||
|
||||
vm_vec_scale(&perp, 1.0f+(float(j)/100.0f));
|
||||
vm_vec_add(&cameraPos, &pos, &perp);
|
||||
|
||||
@@ -287,7 +287,7 @@ static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
|
||||
const char *colon = strchr(new_dir, ':');
|
||||
|
||||
if (colon) {
|
||||
if (!cfile_chdrive(tolower(*(colon - 1)) - 'a' + 1, 1))
|
||||
if (!cfile_chdrive(SCP_tolower(*(colon - 1)) - 'a' + 1, 1))
|
||||
return 1;
|
||||
|
||||
path = colon + 1;
|
||||
@@ -305,7 +305,7 @@ static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
|
||||
status = _chdir(path);
|
||||
if (status != 0) {
|
||||
#ifdef _WIN32
|
||||
cfile_chdrive(tolower(cur_dir[0]) - 'a' + 1, 1);
|
||||
cfile_chdrive(SCP_tolower(cur_dir[0]) - 'a' + 1, 1);
|
||||
#endif /* _WIN32 */
|
||||
return 2;
|
||||
}
|
||||
@@ -866,6 +866,7 @@ static int cfget_cfile_block()
|
||||
cfile->data = nullptr;
|
||||
cfile->fp = nullptr;
|
||||
cfile->type = CFILE_BLOCK_USED;
|
||||
cf_clear_compression_info(cfile);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -921,7 +922,7 @@ int cfclose( CFILE * cfile )
|
||||
} else {
|
||||
// VP do nothing
|
||||
}
|
||||
|
||||
cf_clear_compression_info(cfile);
|
||||
cfile->type = CFILE_BLOCK_UNUSED;
|
||||
return result;
|
||||
}
|
||||
@@ -973,7 +974,7 @@ static CFILE *cf_open_fill_cfblock(const char* source, int line, const char* ori
|
||||
if(pos == -1L)
|
||||
pos = 0;
|
||||
cf_init_lowlevel_read_code(cfp,0,filelength(fileno(fp)), 0 );
|
||||
|
||||
cf_check_compression(cfp);
|
||||
return cfp;
|
||||
}
|
||||
}
|
||||
@@ -1008,7 +1009,7 @@ static CFILE *cf_open_packed_cfblock(const char* source, int line, const char* o
|
||||
cfp->line_num = line;
|
||||
|
||||
cf_init_lowlevel_read_code(cfp,offset, size, 0 );
|
||||
|
||||
cf_check_compression(cfp);
|
||||
return cfp;
|
||||
}
|
||||
|
||||
@@ -1052,6 +1053,7 @@ static CFILE *cf_open_mapped_fill_cfblock(const char* source, int line, const ch
|
||||
cfp->line_num = line;
|
||||
|
||||
cf_init_lowlevel_read_code(cfp, 0, 0, 0 );
|
||||
|
||||
#if defined _WIN32
|
||||
cfp->hMapFile = CreateFileMapping(cfp->hInFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (cfp->hMapFile == NULL) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "cfile/cfile.h"
|
||||
#include "cfile/cfilearchive.h"
|
||||
#include "cfile/cfilecompression.h"
|
||||
#include "luaconf.h"
|
||||
|
||||
#include <sstream>
|
||||
@@ -33,7 +34,6 @@
|
||||
#define CHECK_POSITION
|
||||
|
||||
// Called once to setup the low-level reading code.
|
||||
|
||||
void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos )
|
||||
{
|
||||
Assert(cfile != nullptr);
|
||||
@@ -54,6 +54,33 @@ void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size,
|
||||
}
|
||||
}
|
||||
|
||||
//This function checks the file header to see if the file is compressed, if so it fills the correct compression info.
|
||||
void cf_check_compression(CFILE* cfile)
|
||||
{
|
||||
if (cfile->size <= 16)
|
||||
return;
|
||||
int header=cfread_int(cfile);
|
||||
cfseek(cfile, 0, SEEK_SET);
|
||||
if (comp_check_header(header) == COMP_HEADER_MATCH)
|
||||
comp_create_ci(cfile, header);
|
||||
}
|
||||
|
||||
//This function is called to cleanup compression info when the cfile handle is reused.
|
||||
void cf_clear_compression_info(CFILE* cfile)
|
||||
{
|
||||
if (cfile->compression_info.header != 0)
|
||||
{
|
||||
free(cfile->compression_info.offsets);
|
||||
free(cfile->compression_info.decoder_buffer);
|
||||
cfile->compression_info.offsets = nullptr;
|
||||
cfile->compression_info.decoder_buffer = nullptr;
|
||||
cfile->compression_info.header = 0;
|
||||
cfile->compression_info.block_size = 0;
|
||||
cfile->compression_info.last_decoded_block_pos = 0;
|
||||
cfile->compression_info.last_decoded_block_bytes = 0;
|
||||
cfile->compression_info.num_offsets = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// cfeof() Tests for end-of-file on a stream
|
||||
@@ -61,11 +88,11 @@ void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size,
|
||||
// returns a nonzero value after the first read operation that attempts to read
|
||||
// past the end of the file. It returns 0 if the current position is not end of file.
|
||||
// There is no error return.
|
||||
|
||||
int cfeof(CFILE *cfile)
|
||||
{
|
||||
Assert(cfile != NULL);
|
||||
|
||||
if (cfile->compression_info.header != 0)
|
||||
return comp_feof(cfile);
|
||||
int result = 0;
|
||||
|
||||
#if defined(CHECK_POSITION) && !defined(NDEBUG)
|
||||
@@ -92,6 +119,8 @@ int cfeof(CFILE *cfile)
|
||||
int cftell( CFILE * cfile )
|
||||
{
|
||||
Assert(cfile != NULL);
|
||||
if (cfile->compression_info.header != 0)
|
||||
return (int)comp_ftell(cfile);
|
||||
|
||||
#if defined(CHECK_POSITION) && !defined(NDEBUG)
|
||||
if (cfile->fp) {
|
||||
@@ -117,6 +146,9 @@ int cfseek( CFILE *cfile, int offset, int where )
|
||||
|
||||
Assert(cfile != NULL);
|
||||
|
||||
if (cfile->compression_info.header != 0)
|
||||
return comp_fseek(cfile, offset, where);
|
||||
|
||||
// TODO: seek to offset in memory mapped file
|
||||
Assert( !cfile->mem_mapped );
|
||||
|
||||
@@ -202,23 +234,31 @@ int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
|
||||
// This is a file from memory
|
||||
bytes_read = size;
|
||||
memcpy(buf, reinterpret_cast<const char*>(cfile->data) + cfile->raw_position, size);
|
||||
} else if (cfile->compression_info.header != 0) {
|
||||
bytes_read = comp_fread(cfile, reinterpret_cast<char*>(buf),size);
|
||||
if (bytes_read != size)
|
||||
{
|
||||
mprintf(("\nFile: %s decompression error. Result was: %d expected: %d\n", cfile->original_filename.c_str(), (int)bytes_read, (int)size));
|
||||
Assertion(bytes_read == size, "File decompression error!");
|
||||
}
|
||||
} else {
|
||||
bytes_read = fread( buf, 1, size, cfile->fp );
|
||||
bytes_read = fread(buf, 1, size, cfile->fp);
|
||||
}
|
||||
|
||||
if ( bytes_read > 0 ) {
|
||||
cfile->raw_position += bytes_read;
|
||||
Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
|
||||
}
|
||||
|
||||
#if defined(CHECK_POSITION) && !defined(NDEBUG)
|
||||
if (cfile->fp) {
|
||||
//Raw position is not the fp position with compressed files
|
||||
if (cfile->fp && cfile->compression_info.header == 0) {
|
||||
auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset;
|
||||
Assert(tmp_offset==cfile->raw_position);
|
||||
Assert(tmp_offset == cfile->raw_position);
|
||||
}
|
||||
#endif
|
||||
|
||||
return (int)(bytes_read / elsize);
|
||||
|
||||
}
|
||||
|
||||
int cfread_lua_number(double *buf, CFILE *cfile)
|
||||
@@ -266,4 +306,3 @@ int cfread_lua_number(double *buf, CFILE *cfile)
|
||||
return items_read;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _CFILEARCHIVE_H
|
||||
#define _CFILEARCHIVE_H
|
||||
|
||||
@@ -23,6 +22,17 @@
|
||||
#define CFILE_BLOCK_UNUSED 0
|
||||
#define CFILE_BLOCK_USED 1
|
||||
|
||||
struct COMPRESSION_INFO {
|
||||
int header = 0;
|
||||
size_t compressed_size = 0;
|
||||
int block_size = 0;
|
||||
int num_offsets = 0;
|
||||
int* offsets = nullptr;
|
||||
char* decoder_buffer = nullptr;
|
||||
int last_decoded_block_pos = 0;
|
||||
int last_decoded_block_bytes = 0;
|
||||
};
|
||||
|
||||
struct CFILE {
|
||||
int type = CFILE_BLOCK_UNUSED; // CFILE_BLOCK_UNUSED, CFILE_BLOCK_USED
|
||||
int dir_type; // directory location
|
||||
@@ -44,12 +54,19 @@ struct CFILE {
|
||||
SCP_string original_filename;
|
||||
const char* source_file;
|
||||
int line_num;
|
||||
COMPRESSION_INFO compression_info;
|
||||
};
|
||||
|
||||
#define MAX_CFILE_BLOCKS 64
|
||||
extern std::array<CFILE, MAX_CFILE_BLOCKS> Cfile_block_list;
|
||||
|
||||
// Called once to setup the low-level reading code.
|
||||
void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos );
|
||||
void cf_init_lowlevel_read_code(CFILE* cfile, size_t lib_offset, size_t size, size_t pos);
|
||||
|
||||
// This checks if the file is compressed or not, and creates the proper compression info if so.
|
||||
void cf_check_compression(CFILE* cfile);
|
||||
|
||||
// Used to clear compression info data and free dynamic memory used by compression
|
||||
void cf_clear_compression_info(CFILE* cfile);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
#define _CFILE_INTERNAL
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#include <winbase.h>
|
||||
#endif
|
||||
|
||||
#include "lz4.h"
|
||||
#include "cfilecompression.h"
|
||||
#include "cfilearchive.h"
|
||||
|
||||
/*INTERNAL FUNCTIONS*/
|
||||
/*LZ41*/
|
||||
void lz41_load_offsets(CFILE* cf);
|
||||
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length);
|
||||
void lz41_create_ci(CFILE* cf, int header);
|
||||
/*MISC*/
|
||||
int fso_fseek(CFILE* cfile, int offset, int where);
|
||||
/*END OF INTERNAL FUNCTIONS*/
|
||||
|
||||
int comp_check_header(int header)
|
||||
{
|
||||
if (LZ41_FILE_HEADER == header)
|
||||
return COMP_HEADER_MATCH;
|
||||
|
||||
return COMP_HEADER_MISMATCH;
|
||||
}
|
||||
|
||||
void comp_create_ci(CFILE* cf, int header)
|
||||
{
|
||||
//mprintf(("(CI)Compressed File Opened: %s \n", cf->original_filename.c_str()));
|
||||
|
||||
if (LZ41_FILE_HEADER == header)
|
||||
lz41_create_ci(cf, header);
|
||||
|
||||
//mprintf(("(CI)Uncompressed FileSize: %d \n", cf->size));
|
||||
//mprintf(("(CI)Compressed FileSize: %d \n", cf->compression_info.compressed_size));
|
||||
//mprintf(("(CI)Block Size: %d \n", cf->compression_info.block_size));
|
||||
}
|
||||
|
||||
size_t comp_fread(CFILE* cf, char* buffer, size_t length)
|
||||
{
|
||||
/* Check the request to be at least 1 byte */
|
||||
Assertion(length > 0, "Invalid length requested.");
|
||||
|
||||
/* Check that we are not requesting to read beyond end of file */
|
||||
Assertion(cf->raw_position + length <= cf->size, "Invalid length requested.");
|
||||
|
||||
if (LZ41_FILE_HEADER == cf->compression_info.header)
|
||||
return lz41_stream_random_access(cf, buffer, cf->raw_position, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t comp_ftell(CFILE* cf)
|
||||
{
|
||||
return cf->raw_position;
|
||||
}
|
||||
|
||||
int comp_feof(CFILE* cf)
|
||||
{
|
||||
if (cf->raw_position >= cf->size)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int comp_fseek(CFILE* cf, int offset, int where)
|
||||
{
|
||||
size_t goal_position;
|
||||
switch (where) {
|
||||
case SEEK_SET: goal_position = offset; break;
|
||||
case SEEK_CUR: goal_position = cf->raw_position + offset; break;
|
||||
case SEEK_END: goal_position = cf->size + offset; break;
|
||||
default: return 1;
|
||||
}
|
||||
|
||||
// Make sure we don't seek beyond the end of the file
|
||||
CAP(goal_position,(size_t)0, cf->size);
|
||||
|
||||
cf->raw_position = goal_position;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Special fseek for compressed files handled by FSO, only SEEK_SET and SEEK_END is supported.
|
||||
int fso_fseek(CFILE* cfile, int offset, int where)
|
||||
{
|
||||
size_t goal_position;
|
||||
|
||||
switch (where) {
|
||||
case SEEK_SET: goal_position = offset + cfile->lib_offset; break;
|
||||
case SEEK_END: goal_position = cfile->compression_info.compressed_size + offset + cfile->lib_offset; break;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
// Make sure we don't seek beyond the end of the file
|
||||
CAP(goal_position, cfile->lib_offset, cfile->lib_offset + cfile->compression_info.compressed_size);
|
||||
|
||||
int result = 0;
|
||||
if (cfile->fp)
|
||||
{
|
||||
result = fseek(cfile->fp, (long)goal_position, SEEK_SET);
|
||||
Assertion(goal_position >= cfile->lib_offset, "Invalid offset values detected while seeking! Goal was " SIZE_T_ARG ", lib_offset is " SIZE_T_ARG ".", goal_position, cfile->lib_offset);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void lz41_create_ci(CFILE* cf, int header)
|
||||
{
|
||||
cf->compression_info.header = header;
|
||||
cf->compression_info.compressed_size = cf->size;
|
||||
fso_fseek(cf, (int)sizeof(int)*-3, SEEK_END);
|
||||
auto fNumoffsets = fread(&cf->compression_info.num_offsets, sizeof(int), 1, cf->fp);
|
||||
auto fSize = fread(&cf->size, sizeof(int), 1, cf->fp);
|
||||
auto fBsize = fread(&cf->compression_info.block_size, sizeof(int), 1, cf->fp);
|
||||
|
||||
Assertion(cf->compression_info.num_offsets > 0, "Invalid number of offsets, compressed file is possibly in the wrong format or corrupted.");
|
||||
#if !defined(NDEBUG)
|
||||
Assertion(cf->size > 4, "Invalid filesize, compressed file is possibly in the wrong format or corrupted.");
|
||||
Assertion(cf->compression_info.block_size > 16, "Invalid block size, compressed file is possibly in the wrong format or corrupted.");
|
||||
Assertion(fNumoffsets == 1, "Error while reading the number of offsets, compressed file is possibly in the wrong format or corrupted.");
|
||||
Assertion(fSize == 1, "Error while reading original filesize, compressed file is possibly in the wrong format or corrupted.");
|
||||
Assertion(fBsize == 1, "Error while reading block size, compressed file is possibly in the wrong format or corrupted.");
|
||||
#endif
|
||||
|
||||
cf->compression_info.decoder_buffer = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
|
||||
cf->compression_info.last_decoded_block_pos = 0;
|
||||
cf->compression_info.last_decoded_block_bytes = 0;
|
||||
lz41_load_offsets(cf);
|
||||
}
|
||||
|
||||
void lz41_load_offsets(CFILE* cf)
|
||||
{
|
||||
cf->compression_info.offsets = (int*)malloc(cf->compression_info.num_offsets * sizeof(int));
|
||||
int block;
|
||||
int* offsets_ptr = cf->compression_info.offsets;
|
||||
|
||||
/* Seek to the first offset position, remember to consider the trailing ints */
|
||||
fso_fseek(cf, ( ( sizeof(int) * cf->compression_info.num_offsets ) * -1 ) - (sizeof(int)*3 ), SEEK_END);
|
||||
for (block = 0; block < cf->compression_info.num_offsets; ++block)
|
||||
{
|
||||
auto bytes_read = fread(offsets_ptr++, sizeof(int), 1, cf->fp);
|
||||
Assertion(bytes_read == 1, "Error reading offset list.");
|
||||
}
|
||||
}
|
||||
|
||||
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length)
|
||||
{
|
||||
LZ4_streamDecode_t lz4_stream_decode_body;
|
||||
LZ4_streamDecode_t* lz4_stream_decode = &lz4_stream_decode_body;
|
||||
/* The blocks (current_block to end_block) contain the data we want */
|
||||
size_t current_block = offset / cf->compression_info.block_size;
|
||||
size_t end_block = ((offset + length - 1) / cf->compression_info.block_size) + 1;
|
||||
size_t written_bytes = 0;
|
||||
|
||||
if (cf->compression_info.num_offsets <= (int)end_block)
|
||||
return (size_t)LZ41_OFFSETS_MISMATCH;
|
||||
|
||||
/* Seek to the first block to read, if it matches the cached block, search the next one instead */
|
||||
if (cf->compression_info.last_decoded_block_pos == cf->compression_info.offsets[current_block] && current_block + 1 <= end_block)
|
||||
fso_fseek(cf, cf->compression_info.offsets[current_block+1], SEEK_SET);
|
||||
else
|
||||
fso_fseek(cf, cf->compression_info.offsets[current_block], SEEK_SET);
|
||||
|
||||
offset = offset % cf->compression_info.block_size;
|
||||
char* cmp_buf = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
|
||||
|
||||
/* Start decoding */
|
||||
for (; current_block < end_block; ++current_block)
|
||||
{
|
||||
/* Only read and decode if the requested block is not the cached one */
|
||||
if (cf->compression_info.last_decoded_block_pos != cf->compression_info.offsets[current_block])
|
||||
{
|
||||
cf->compression_info.last_decoded_block_pos = cf->compression_info.offsets[current_block];
|
||||
|
||||
/* The difference in offsets is the size of the block */
|
||||
int cmp_bytes = cf->compression_info.offsets[current_block + 1] - cf->compression_info.offsets[current_block];
|
||||
auto bytes_read = fread(cmp_buf, cmp_bytes, 1, cf->fp);
|
||||
Assertion(bytes_read == 1, "Error reading from compressed file.");
|
||||
|
||||
cf->compression_info.last_decoded_block_bytes = LZ4_decompress_safe_continue(lz4_stream_decode, cmp_buf, cf->compression_info.decoder_buffer, cmp_bytes, cf->compression_info.block_size);
|
||||
if (cf->compression_info.last_decoded_block_bytes <= 0)
|
||||
{
|
||||
free(cmp_buf);
|
||||
return (size_t)LZ41_DECOMPRESSION_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write out the part of the data we care about from buffer */
|
||||
size_t block_length = (length < (cf->compression_info.last_decoded_block_bytes - offset) ? length : (cf->compression_info.last_decoded_block_bytes - offset));
|
||||
memcpy(bytes_out + written_bytes, cf->compression_info.decoder_buffer + offset, (size_t)block_length);
|
||||
written_bytes += block_length;
|
||||
offset = 0;
|
||||
length -= block_length;
|
||||
}
|
||||
|
||||
free(cmp_buf);
|
||||
return written_bytes;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
ShivanSpS - Compressed files support for FSO. Many thanks to ngld, taylor and everyone elsewho helped me in getting this done.
|
||||
|
||||
-The file header is a version, this is used to tell FSO how to decompress that file, always use 4 chars to mantain alignment.
|
||||
-The header ID can be used to add diferent revisions to LZ41 decompression system or to add other compression format supports whiout breaking compatibility.
|
||||
-The system uses a offset list to record the position of every block in file, this list, along with the number of offsets, original filesize,
|
||||
and block size, must be written by the compressor app.
|
||||
-A decoder cache is used to store the last decoded block, this ensures each block is read and decoded only once in sequential reads. This cache
|
||||
is a little bit bigger than the block size, a higher block size means less overhead added to the file, but it also means a little more ram will
|
||||
be used during decompression.
|
||||
-All this dynamic memory is assigned at cfopen() and it is cleared on cfclose().
|
||||
|
||||
................................char[4]..........(n ints)...(int)..........(int)..........(int)
|
||||
-COMPRESSED FILE DATA STUCTURE: HEADER|N BLOCKS|N OFFSETS|NUM_OFFSETS|ORIGINAL_FILESIZE|BLOCK_SIZE
|
||||
*/
|
||||
|
||||
#ifndef _CFILECOMPRESSION_H
|
||||
#define _CFILECOMPRESSION_H
|
||||
struct CFILE;
|
||||
|
||||
/*LZ41*/
|
||||
#define LZ41_FILE_HEADER 0x31345A4C // "14ZL" -> "LZ41", inverted
|
||||
#define LZ41_DECOMPRESSION_ERROR -1
|
||||
#define LZ41_MAX_BLOCKS_OVERFLOW -2
|
||||
#define LZ41_HEADER_MISMATCH -3
|
||||
#define LZ41_OFFSETS_MISMATCH -4
|
||||
/******/
|
||||
|
||||
#define COMP_HEADER_MATCH 1
|
||||
#define COMP_HEADER_MISMATCH 0
|
||||
|
||||
/*
|
||||
Returns COMP_HEADER_MATCH if header is a valid compressed file header,
|
||||
returns COMP_HEADER_MISMATCH if it dosent.
|
||||
*/
|
||||
int comp_check_header(int header);
|
||||
|
||||
/*
|
||||
This is called to generate the correct compression_info data
|
||||
after the file has been indentified as a compressed file.
|
||||
This must be done before calling any other function.
|
||||
*/
|
||||
void comp_create_ci(CFILE* cf, int header);
|
||||
|
||||
/*
|
||||
Read X bytes from the uncompressed file starting from X offset.
|
||||
Returns the amount of bytes read, and 0 or lower to indicate errors.
|
||||
*/
|
||||
size_t comp_fread(CFILE* cf, char* buffer, size_t length);
|
||||
|
||||
/*
|
||||
Returns the current uncompressed file position.
|
||||
*/
|
||||
size_t comp_ftell(CFILE* cf);
|
||||
|
||||
/*
|
||||
Returns 1 of the uncompressed file has been completely read, otherwise it returns a 0.
|
||||
*/
|
||||
int comp_feof(CFILE* cf);
|
||||
|
||||
/*
|
||||
Used to move the uncompressed file current position.
|
||||
*/
|
||||
int comp_fseek(CFILE* cf, int offset, int where);
|
||||
|
||||
#endif
|
||||
+14
-14
@@ -246,7 +246,7 @@ Flag exe_params[] =
|
||||
{ "-noninteractive", "Disables interactive dialogs", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-noninteractive", },
|
||||
{ "-no_unfocused_pause","Don't pause if the window isn't focused", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-no_unfocused_pause", },
|
||||
{ "-benchmark_mode", "Puts the game into benchmark mode", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-benchmark_mode", },
|
||||
{ "-profile_frame_time","Profile frame time", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-profile_frame_time", },
|
||||
{ "-profile_frame_time","Profile frame time", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-profile_frame_time", },
|
||||
{ "-profile_write_file", "Write profiling information to file", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-profile_write_file", },
|
||||
{ "-json_profiling", "Generate JSON profiling output", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-json_profiling", },
|
||||
{ "-debug_window", "Enable the debug window", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-debug_window", },
|
||||
@@ -274,7 +274,6 @@ cmdline_parm gameclosed_arg("-closed", NULL, AT_NONE); // Cmdline_closed_game
|
||||
cmdline_parm gamerestricted_arg("-restricted", NULL, AT_NONE); // Cmdline_restricted_game
|
||||
cmdline_parm port_arg("-port", "Multiplayer network port", AT_INT);
|
||||
cmdline_parm multilog_arg("-multilog", NULL, AT_NONE); // Cmdline_multi_log
|
||||
cmdline_parm client_dodamage("-clientdamage", NULL, AT_NONE); // Cmdline_client_dodamage
|
||||
cmdline_parm pof_spew("-pofspew", NULL, AT_NONE); // Cmdline_spew_pof_info
|
||||
cmdline_parm weapon_spew("-weaponspew", nullptr, AT_STRING); // Cmdline_spew_weapon_stats
|
||||
cmdline_parm mouse_coords("-coords", NULL, AT_NONE); // Cmdline_mouse_coords
|
||||
@@ -287,7 +286,6 @@ char *Cmdline_game_password = NULL;
|
||||
char *Cmdline_rank_above = NULL;
|
||||
char *Cmdline_rank_below = NULL;
|
||||
int Cmdline_cd_check = 1;
|
||||
int Cmdline_client_dodamage = 0;
|
||||
int Cmdline_closed_game = 0;
|
||||
int Cmdline_freespace_no_music = 0;
|
||||
int Cmdline_freespace_no_sound = 0;
|
||||
@@ -481,6 +479,7 @@ cmdline_parm reparse_mainhall_arg("-reparse_mainhall", NULL, AT_NONE); //Cmdline
|
||||
cmdline_parm frame_profile_write_file("-profile_write_file", NULL, AT_NONE); // Cmdline_profile_write_file
|
||||
cmdline_parm no_unfocused_pause_arg("-no_unfocused_pause", NULL, AT_NONE); //Cmdline_no_unfocus_pause
|
||||
cmdline_parm benchmark_mode_arg("-benchmark_mode", NULL, AT_NONE); //Cmdline_benchmark_mode
|
||||
cmdline_parm pilot_arg("-pilot", nullptr, AT_STRING); //Cmdline_pilot
|
||||
cmdline_parm noninteractive_arg("-noninteractive", NULL, AT_NONE); //Cmdline_noninteractive
|
||||
cmdline_parm json_profiling("-json_profiling", NULL, AT_NONE); //Cmdline_json_profiling
|
||||
cmdline_parm show_video_info("-show_video_info", NULL, AT_NONE); //Cmdline_show_video_info
|
||||
@@ -512,6 +511,7 @@ int Cmdline_reparse_mainhall = 0;
|
||||
bool Cmdline_profile_write_file = false;
|
||||
bool Cmdline_no_unfocus_pause = false;
|
||||
bool Cmdline_benchmark_mode = false;
|
||||
const char *Cmdline_pilot = nullptr;
|
||||
bool Cmdline_noninteractive = false;
|
||||
bool Cmdline_json_profiling = false;
|
||||
bool Cmdline_frame_profile = false;
|
||||
@@ -812,7 +812,7 @@ void os_validate_parms(int argc, char *argv[])
|
||||
if (!stricmp(token, "-help") || !stricmp(token, "--help") || !stricmp(token, "-h") || !stricmp(token, "-?")) {
|
||||
printf("FreeSpace 2 Open, version %s\n", FS_VERSION_FULL);
|
||||
printf("Website: http://scp.indiegames.us\n");
|
||||
printf("Mantis (bug reporting): http://scp.indiegames.us/mantis/\n\n");
|
||||
printf("Github (bug reporting): https://github.com/scp-fs2open/fs2open.github.com/issues\n\n");
|
||||
printf("Usage: fs2_open [options]\n");
|
||||
|
||||
// not the prettiest thing but the job gets done
|
||||
@@ -1619,7 +1619,7 @@ bool SetCmdlineParams()
|
||||
Cmdline_game_name = gamename_arg.str();
|
||||
|
||||
// be sure that this string fits in our limits
|
||||
if ( strlen(Cmdline_game_name) > MAX_GAMENAME_LEN ) {
|
||||
if ( strlen(Cmdline_game_name) >= MAX_GAMENAME_LEN ) {
|
||||
Cmdline_game_name[MAX_GAMENAME_LEN-1] = '\0';
|
||||
}
|
||||
}
|
||||
@@ -1629,8 +1629,9 @@ bool SetCmdlineParams()
|
||||
Cmdline_game_password = gamepassword_arg.str();
|
||||
|
||||
// be sure that this string fits in our limits
|
||||
if ( strlen(Cmdline_game_name) > MAX_PASSWD_LEN ) {
|
||||
Cmdline_game_name[MAX_PASSWD_LEN-1] = '\0';
|
||||
if ( strlen(Cmdline_game_password) >= MAX_PASSWD_LEN ) {
|
||||
ReleaseWarning(LOCATION, "Multi game password is longer than max of %d charaters and will be trimmed to fit!", MAX_PASSWD_LEN-1);
|
||||
Cmdline_game_password[MAX_PASSWD_LEN-1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1663,12 +1664,6 @@ bool SetCmdlineParams()
|
||||
Cmdline_multi_log = 1;
|
||||
}
|
||||
|
||||
|
||||
// maybe use old-school client damage
|
||||
if(client_dodamage.found()){
|
||||
Cmdline_client_dodamage = 1;
|
||||
}
|
||||
|
||||
// spew pof info
|
||||
if(pof_spew.found()){
|
||||
Cmdline_spew_pof_info = 1;
|
||||
@@ -1721,7 +1716,7 @@ bool SetCmdlineParams()
|
||||
sprintf(override, "{\"width\":%d,\"height\":%d}", width, height);
|
||||
options::OptionsManager::instance()->setOverride("Graphics.Resolution", override);
|
||||
} else {
|
||||
mprintf(("Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".", Cmdline_res));
|
||||
mprintf(("Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".\n", Cmdline_res));
|
||||
}
|
||||
}
|
||||
if(center_res_arg.found()){
|
||||
@@ -2131,6 +2126,11 @@ bool SetCmdlineParams()
|
||||
Cmdline_benchmark_mode = true;
|
||||
}
|
||||
|
||||
if (pilot_arg.found())
|
||||
{
|
||||
Cmdline_pilot = pilot_arg.str();
|
||||
}
|
||||
|
||||
if (noninteractive_arg.found())
|
||||
{
|
||||
Cmdline_noninteractive = true;
|
||||
|
||||
@@ -30,7 +30,6 @@ extern char *Cmdline_game_password;
|
||||
extern char *Cmdline_rank_above;
|
||||
extern char *Cmdline_rank_below;
|
||||
extern int Cmdline_cd_check;
|
||||
extern int Cmdline_client_dodamage;
|
||||
extern int Cmdline_closed_game;
|
||||
extern int Cmdline_freespace_no_music;
|
||||
extern int Cmdline_freespace_no_sound;
|
||||
@@ -144,6 +143,7 @@ extern int Cmdline_reparse_mainhall;
|
||||
extern bool Cmdline_profile_write_file;
|
||||
extern bool Cmdline_no_unfocus_pause;
|
||||
extern bool Cmdline_benchmark_mode;
|
||||
extern const char *Cmdline_pilot;
|
||||
extern bool Cmdline_noninteractive;
|
||||
extern bool Cmdline_json_profiling;
|
||||
extern bool Cmdline_frame_profile;
|
||||
|
||||
+1044
-827
File diff suppressed because it is too large.
Load diff
@@ -28,12 +28,27 @@ enum Joy_axis_index {
|
||||
JOY_RZ_AXIS
|
||||
};
|
||||
|
||||
/*!
|
||||
* Controller index enumeration
|
||||
* @details For use with hardcoded bindings and scripting API to allow human-readable translation
|
||||
* @note These enums are hardcoded so that an int value of 0 would be JOY0
|
||||
*/
|
||||
enum CID : short {
|
||||
CID_NONE = -3, // No device bound
|
||||
CID_KEYBOARD = -2, // button belongs to keyboard
|
||||
CID_MOUSE = -1, // to mouse
|
||||
CID_JOY0 = 0, // to Joy0
|
||||
CID_JOY1 = 1, // to Joy1 (Throttle?)
|
||||
CID_JOY2 = 2, // to Joy2 (Pedals?)
|
||||
CID_JOY3 = 3 // to Joy3 (Head tracker?)
|
||||
// Any additional joystick is an int >3
|
||||
};
|
||||
|
||||
/*!
|
||||
* These are used to index a corresponding (analog) action, namely controlling the orientation angles and throttle.
|
||||
*/
|
||||
enum Joy_axis_action_index {
|
||||
JOY_HEADING_AXIS =0,
|
||||
JOY_HEADING_AXIS =0, //
|
||||
JOY_PITCH_AXIS,
|
||||
JOY_BANK_AXIS,
|
||||
JOY_ABS_THROTTLE_AXIS,
|
||||
@@ -45,7 +60,16 @@ enum Joy_axis_action_index {
|
||||
NUM_JOY_AXIS_ACTIONS //!< The total number of actions an axis may map to
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* Joystick action modes
|
||||
* note: should this be in joy.h?
|
||||
*/
|
||||
enum Joy_axis_action_mode {
|
||||
JAAM_ABS, //!< Absolute mode. Axis position = output value
|
||||
JAAM_REL, //!< Relative mode. Axis postion away from its center adds or subtracts an output value register.
|
||||
JAAM_BTN_NEG, //!< Button mode, negative side. Axis position in the negative side will trigger a button action
|
||||
JAAM_BTN_POS, //!< Button mode, positive side. Axis position in the positive side will trigger a button action
|
||||
};
|
||||
|
||||
/*!
|
||||
* Control Configuration Types. Namely differ in how the control is activated
|
||||
@@ -55,27 +79,12 @@ enum CC_type {
|
||||
CC_TYPE_CONTINUOUS //!< A continous control that is activated as long as the key or button is held down
|
||||
};
|
||||
|
||||
/*!
|
||||
* Control configuration item type.
|
||||
*/
|
||||
typedef struct config_item {
|
||||
short key_default; //!< default key bound to action
|
||||
short joy_default; //!< default joystick button bound to action
|
||||
char tab; //!< what tab (category) it belongs in
|
||||
int indexXSTR; //!< what string index we should use to translate this with an XSTR
|
||||
const char *text; //!< describes the action in the config screen
|
||||
char type; //!< manner control should be checked in
|
||||
short key_id; //!< actual key bound to action
|
||||
short joy_id; //!< joystick button bound to action
|
||||
int used; //!< has control been used yet in mission? If so, this is the timestamp
|
||||
bool disabled; //!< whether this action should be available at all
|
||||
bool continuous_ongoing;//!< whether this action is a continuous one and is currently ongoing
|
||||
} config_item;
|
||||
|
||||
/*!
|
||||
* All available actions
|
||||
* This is the value of the id field in config_item
|
||||
* The first group of items are ship targeting.
|
||||
*
|
||||
* Note: Do not adjust the order or numeric value
|
||||
*/
|
||||
enum IoActionId {
|
||||
TARGET_NEXT =0, //!< target next
|
||||
@@ -252,7 +261,7 @@ enum IoActionId {
|
||||
DOWN_SLIDE_THRUST =111, //!< DOWN_SLIDE_THRUST
|
||||
HUD_TARGETBOX_TOGGLE_WIREFRAME =112, //!< HUD_TARGETBOX_TOGGLE_WIREFRAME
|
||||
VIEW_TOPDOWN =113, //!< VIEW_TOPDOWN
|
||||
VIEW_TRACK_TARGET =114, //!< VIEW_TRACK_TARGET
|
||||
VIEW_TRACK_TARGET =114, //!< VIEW_TRACK_TARGET (Switfty) Toggle for VM_Track
|
||||
|
||||
//!< @n
|
||||
//!< AutoPilot - Kazan
|
||||
@@ -285,10 +294,200 @@ enum IoActionId {
|
||||
CCFG_MAX //!< The total number of defined control actions (or last define + 1)
|
||||
};
|
||||
|
||||
class CCB;
|
||||
|
||||
/*!
|
||||
* A singular button binding
|
||||
*/
|
||||
class CC_bind {
|
||||
public:
|
||||
CID cid = CID_NONE; //!< Which controller this belongs to
|
||||
short btn = -1; //!< Which button to index; If cid == CID_KEYBOARD, this is a key hash.
|
||||
|
||||
public:
|
||||
CC_bind() = default;
|
||||
CC_bind(const CC_bind &A) = default;
|
||||
|
||||
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) {};
|
||||
|
||||
CC_bind& operator=(const CC_bind &A);
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to the given CC_bind
|
||||
*/
|
||||
bool operator==(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to either in the pair
|
||||
*/
|
||||
bool operator==(const CCB &pair) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is not equal to the given CC_bind
|
||||
*/
|
||||
bool operator!=(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is not equal to either in the pair
|
||||
*/
|
||||
bool operator!=(const CCB &pair) const;
|
||||
|
||||
/*!
|
||||
* Clears the binding
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/*!
|
||||
* True if not bound
|
||||
*/
|
||||
bool empty() const;
|
||||
|
||||
/*!
|
||||
* Returns a human-readable string of this binding
|
||||
*/
|
||||
SCP_string textify() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* A pair of bindings.
|
||||
* @note Please don't set the bindings directly, use ::take() instead.
|
||||
*/
|
||||
class CCB {
|
||||
public:
|
||||
CC_bind first; // The primary binding
|
||||
CC_bind second; // The secondary binding
|
||||
|
||||
public:
|
||||
CCB() = default;
|
||||
CCB(const CCB& A) = default;
|
||||
|
||||
/*!
|
||||
* Returns true if nothing is bound
|
||||
*/
|
||||
bool empty() const;
|
||||
|
||||
/*!
|
||||
* Takes a given binding
|
||||
*
|
||||
* @param[in] A The bind to take
|
||||
* @param[in] order 0 is primary, 1 is secondary, -1 is overwrite existing bind (if any)
|
||||
*
|
||||
* @note Should there be a binding to the same controller as the given binding, it is cleared
|
||||
*/
|
||||
void take(CC_bind A, int order);
|
||||
|
||||
/*!
|
||||
* Clears both bindings
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/*!
|
||||
* Gets the value of btn if either CC_bind has the given CID
|
||||
*
|
||||
* @returns the btn of the bound controller, or
|
||||
* @returns -1 if that controller is not bound
|
||||
*/
|
||||
short get_btn(CID) const;
|
||||
|
||||
/*!
|
||||
* Assigns the contents of the given CCB to this CCB
|
||||
*/
|
||||
CCB& operator=(const CCB&);
|
||||
|
||||
/*!
|
||||
* Returns True if this CCB's first isn't empty and the given CCB has a binding equal to it
|
||||
*/
|
||||
bool has_first(const CCB&) const;
|
||||
|
||||
/*!
|
||||
* Returns True if this CCB's second isn't empty and the given CCB has a binding equal to it
|
||||
*/
|
||||
bool has_second(const CCB&) const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* A preset, a collection of bindings for use in Control_config with an associated name
|
||||
*/
|
||||
class CC_preset {
|
||||
public:
|
||||
SCP_vector<CCB> bindings;
|
||||
SCP_string name;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Control configuration item type.
|
||||
* @detail Contains binding info, documentation, behavior, etc. for a single control
|
||||
*/
|
||||
class CCI : public CCB {
|
||||
public:
|
||||
// Inherited from CCB
|
||||
// CC_bind first;
|
||||
// CC_bind second;
|
||||
|
||||
// Items Set in menu
|
||||
char tab; //!< what tab (category) it belongs in
|
||||
int indexXSTR; //!< what string index we should use to translate this with an XSTR 0 = None, 1= Use item index + CONTROL_CONFIG_XSTR, 2 <= use CCI::indexXSTR directly
|
||||
SCP_string text; //!< describes the action in the config screen
|
||||
|
||||
CC_type type; //!< manner control should be checked in
|
||||
|
||||
// Items used during gameplay
|
||||
int used; //!< has control been used yet in mission? If so, this is the timestamp
|
||||
bool disabled = true; //!< whether this action should be available at all
|
||||
bool continuous_ongoing; //!< whether this action is a continuous one and is currently ongoing
|
||||
|
||||
public:
|
||||
CCI() = default;
|
||||
CCI(const CCI& A) = default;
|
||||
|
||||
/*!
|
||||
* Assigns the contents of the given CCI to this CCI
|
||||
*/
|
||||
CCI& operator=(const CCI&);
|
||||
|
||||
/*!
|
||||
* @brief Takes the bindings of the given CCB, but leaves all other members alone
|
||||
*/
|
||||
CCI& operator=(const CCB&);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Builder predicate to populate a ControlConfig vector with hardcoded default bindings.
|
||||
*/
|
||||
class CCI_builder {
|
||||
public:
|
||||
/*!
|
||||
* Initilizes the given ControlConfig vector
|
||||
*/
|
||||
CCI_builder(SCP_vector<CCI>& _ControlConfig);
|
||||
|
||||
/*!
|
||||
* Start a chain of factory methods. If there are any pre-init work to be done, that's done here.
|
||||
*/
|
||||
CCI_builder& start();
|
||||
|
||||
/*!
|
||||
* End a chain of factory methods. If there any post-init work to be done, that's done here.
|
||||
*/
|
||||
void end();
|
||||
|
||||
/*!
|
||||
* Assigns the hardcoded binding to the given action
|
||||
* @note This differs from the original hardcode from :V: in the hopes of it being more intuitive to future IoAction additions
|
||||
*/
|
||||
CCI_builder& operator()(IoActionId action_id, short key_default, short joy_default, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
|
||||
|
||||
private:
|
||||
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
|
||||
SCP_vector<CCI>& ControlConfig;
|
||||
};
|
||||
|
||||
|
||||
extern int Failed_key_index;
|
||||
|
||||
extern int Axis_map_to[];
|
||||
extern int Invert_axis[];
|
||||
extern int Axis_map_to[]; // Array to map an axis action to a joy axis. size() = NUM_JOY_AXIS_ACTIONS
|
||||
extern int Axis_map_to_defaults[];
|
||||
extern int Invert_axis[]; // Array to hold inversion bools for a joy axis. size() = JOY_NUM_AXES
|
||||
extern int Invert_axis_defaults[];
|
||||
|
||||
extern int Joy_dead_zone_size;
|
||||
@@ -296,23 +495,76 @@ extern int Joy_sensitivity;
|
||||
|
||||
extern int Control_config_overlay_id;
|
||||
|
||||
extern config_item Control_config[]; //!< Stores the keyboard configuration
|
||||
extern SCP_vector<config_item*> Control_config_presets; // tabled control presets; pointers to config_item arrays
|
||||
extern SCP_vector<SCP_string> Control_config_preset_names; // names for Control_config_presets (identical order of items)
|
||||
extern SCP_vector<CCI> Control_config; //!< Stores the keyboard configuration
|
||||
extern SCP_vector<CC_preset> Control_config_presets; // tabled control presets; pointers to config_item arrays
|
||||
extern const char **Scan_code_text;
|
||||
extern const char **Joy_button_text;
|
||||
|
||||
void control_config_common_init(); //!< initialize common control config stuff - call at game startup after localization has been initialized
|
||||
void control_config_common_close(); //!< close common control config stuff - call at game shutdown
|
||||
/*!
|
||||
* @brief Checks if either binding in the CCB has the given cid
|
||||
*
|
||||
* @returns 0 if its the first element, or
|
||||
* @returns 1 if its the second element, or
|
||||
* @returns -1 if neither
|
||||
*/
|
||||
int is_cid_either(CID, const CCB);
|
||||
|
||||
/*!
|
||||
* @brief initialize common control config stuff - call at game startup after localization has been initialized
|
||||
*/
|
||||
void control_config_common_init();
|
||||
|
||||
/*!
|
||||
* @brief close common control config stuff - call at game shutdown
|
||||
*/
|
||||
void control_config_common_close();
|
||||
|
||||
/*!
|
||||
* @brief init config menu
|
||||
*/
|
||||
void control_config_init();
|
||||
|
||||
/*!
|
||||
* @brief do a frame of the config menu
|
||||
*/
|
||||
void control_config_do_frame(float frametime);
|
||||
|
||||
/*!
|
||||
* @brief close config menu
|
||||
*/
|
||||
void control_config_close();
|
||||
|
||||
/*!
|
||||
* @brief Cancel configuration of controls, revert any changes, return to previous menu/game state
|
||||
*/
|
||||
void control_config_cancel_exit();
|
||||
|
||||
void control_config_reset_defaults(int presetnum=-1);
|
||||
/*!
|
||||
* @brief Resets all controls to values within the currently selected preset
|
||||
*/
|
||||
void control_config_reset_defaults();
|
||||
|
||||
/*!
|
||||
* Returns the IoActionId of a control bound to the given key
|
||||
*
|
||||
* @param[in] key The key combo to look for
|
||||
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
|
||||
* @details If find_override is set to true, then this returns the index of the action
|
||||
*/
|
||||
int translate_key_to_index(const char *key, bool find_override=true);
|
||||
|
||||
|
||||
/*!
|
||||
* @brief Given the system default key 'key', return the current key that is bound to that function.
|
||||
*
|
||||
* @param[in] key The default key combo (as string) to a certain control
|
||||
*
|
||||
* @returns The key combo (as string) currently bound to the control
|
||||
*
|
||||
* @details Both the 'key' and the return value are descriptive strings that can be displayed
|
||||
* directly to the user. If 'key' isn't a real key, is not normally bound to anything,
|
||||
* or there is no key currently bound to the function, NULL is returned.
|
||||
*/
|
||||
char *translate_key(char *key);
|
||||
|
||||
/**
|
||||
@@ -334,14 +586,70 @@ const char *textify_scancode(int code);
|
||||
*/
|
||||
const char *textify_scancode_universal(int code);
|
||||
|
||||
/*!
|
||||
* @brief Checks how long a control has been active
|
||||
*
|
||||
* @param[in] id The IoActionId of the control to check
|
||||
*
|
||||
* @returns Time, in milliseconds, that the control has been active.
|
||||
*
|
||||
* @note This function is only to be used on CC_CONTINOUS type controls, or it will trigger an assertion
|
||||
*/
|
||||
float check_control_timef(int id);
|
||||
|
||||
/**
|
||||
* @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
|
||||
*
|
||||
* @brief id The IoActionId of the control to check
|
||||
* @brief key The key combo to check against the control.
|
||||
*
|
||||
* @returns 0 If the control wasn't used, or
|
||||
* @returns 1 If the control was used
|
||||
*/
|
||||
int check_control(int id, int key = -1);
|
||||
|
||||
/**
|
||||
* @brief Gets the scaled reading for all control axes.
|
||||
*
|
||||
* @param[out] h heading
|
||||
* @param[out] p pitch
|
||||
* @param[out] b bank
|
||||
* @param[out] ta Throttle - Absolute
|
||||
* @param[out] tr Throttle - Relative
|
||||
*
|
||||
* @note None of the input pointers may be nullptr
|
||||
*/
|
||||
void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr);
|
||||
|
||||
/**
|
||||
* @brief Markes the given control (by IoActionId) as used
|
||||
*
|
||||
* @details Updates the ::used timestamp, triggers a script hook, and marks ::continous_ongoing as true
|
||||
*/
|
||||
void control_used(int id);
|
||||
|
||||
/**
|
||||
* @brief Clears the bindings of all controls
|
||||
*/
|
||||
void control_config_clear();
|
||||
|
||||
/**
|
||||
* @brief debug function used to indicate number of controls checked
|
||||
*/
|
||||
void control_check_indicate();
|
||||
|
||||
/**
|
||||
* @brief Clears the used timestamp of all controls
|
||||
*/
|
||||
void control_config_clear_used_status();
|
||||
|
||||
/**
|
||||
* @brief Applies sensitivity multiplier and deadzone histerisis to the raw axis value
|
||||
*
|
||||
* @param[in] raw The raw axis value to transform
|
||||
*
|
||||
* @return The transformed value
|
||||
*/
|
||||
int joy_get_scaled_reading(int raw);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -93,13 +93,12 @@ class Decoder {
|
||||
bool m_decoding;
|
||||
size_t m_queueSize = 0;
|
||||
|
||||
Decoder(const Decoder&) SCP_DELETED_FUNCTION;
|
||||
|
||||
Decoder& operator=(const Decoder&) SCP_DELETED_FUNCTION;
|
||||
protected:
|
||||
Decoder();
|
||||
|
||||
public:
|
||||
Decoder(const Decoder&) = delete;
|
||||
Decoder& operator=(const Decoder&) = delete;
|
||||
|
||||
virtual ~Decoder();
|
||||
|
||||
|
||||
@@ -613,7 +613,7 @@ void cutscenes_screen_do_frame()
|
||||
if (src)
|
||||
{
|
||||
Text_size = split_str(src, Cutscene_desc_coords[gr_screen.res][2], Text_line_size, Text_lines,
|
||||
Cutscene_max_text_lines[gr_screen.res]);
|
||||
Cutscene_max_text_lines[gr_screen.res], MAX_TEXT_LINE_LEN);
|
||||
Assert(Text_size >= 0 && Text_size < Cutscene_max_text_lines[gr_screen.res]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
|
||||
if (status->subtitleStream == nullptr) {
|
||||
// We don't have an external subtitle stream or loading from that file has failed
|
||||
auto& current_language = Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang];
|
||||
auto& current_language = Lcl_languages[lcl_get_current_lang_index()];
|
||||
for (uint32_t i = 0; i < ctx->nb_streams; ++i) {
|
||||
auto test_stream = ctx->streams[i];
|
||||
|
||||
@@ -428,7 +428,7 @@ std::unique_ptr<InputStream> openInputStream(const SCP_string& name) {
|
||||
|
||||
std::unique_ptr<InputStream> openSubtitleStream(const SCP_string& name)
|
||||
{
|
||||
auto& current_language = Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang];
|
||||
auto& current_language = Lcl_languages[lcl_get_current_lang_index()];
|
||||
|
||||
for (auto ext : CHECKED_SUBT_EXTENSIONS) {
|
||||
SCP_string fileName;
|
||||
@@ -453,7 +453,7 @@ bool FFMPEGDecoder::initialize(const SCP_string& fileName, const PlaybackPropert
|
||||
{
|
||||
SCP_string movieName = fileName;
|
||||
// First make the file name lower case
|
||||
std::transform(movieName.begin(), movieName.end(), movieName.begin(), [](char c) { return (char)::tolower(c); });
|
||||
SCP_tolower(movieName);
|
||||
|
||||
// Then remove the extension
|
||||
size_t dotPos = movieName.find('.');
|
||||
|
||||
@@ -45,10 +45,8 @@ struct DecoderStatus {
|
||||
|
||||
~DecoderStatus();
|
||||
|
||||
private:
|
||||
DecoderStatus(const DecoderStatus&) SCP_DELETED_FUNCTION;
|
||||
|
||||
DecoderStatus& operator=(const DecoderStatus&) SCP_DELETED_FUNCTION;
|
||||
DecoderStatus(const DecoderStatus&) = delete;
|
||||
DecoderStatus& operator=(const DecoderStatus&) = delete;
|
||||
};
|
||||
|
||||
template<typename Frame>
|
||||
@@ -67,8 +65,8 @@ class FFMPEGStreamDecoder {
|
||||
explicit FFMPEGStreamDecoder(DecoderStatus* status);
|
||||
virtual ~FFMPEGStreamDecoder();
|
||||
|
||||
FFMPEGStreamDecoder(const FFMPEGStreamDecoder&) SCP_DELETED_FUNCTION;
|
||||
FFMPEGStreamDecoder& operator=(const FFMPEGStreamDecoder&) SCP_DELETED_FUNCTION;
|
||||
FFMPEGStreamDecoder(const FFMPEGStreamDecoder&) = delete;
|
||||
FFMPEGStreamDecoder& operator=(const FFMPEGStreamDecoder&) = delete;
|
||||
|
||||
virtual void decodePacket(AVPacket* packet) = 0;
|
||||
|
||||
|
||||
+85
-89
@@ -30,8 +30,9 @@
|
||||
#include "ship/ship.h"
|
||||
#include "ship/shipfx.h"
|
||||
#include "species_defs/species_defs.h"
|
||||
#include "weapon/weapon.h"
|
||||
#include "tracing/Monitor.h"
|
||||
#include "utils/Random.h"
|
||||
#include "weapon/weapon.h"
|
||||
|
||||
#define MIN_RADIUS_FOR_PERSISTANT_DEBRIS 50 // ship radius at which debris from it becomes persistant
|
||||
#define DEBRIS_SOUND_DELAY 2000 // time to start debris sound after created
|
||||
@@ -51,8 +52,6 @@ int Debris_model = -1;
|
||||
int Debris_vaporize_model = -1;
|
||||
int Debris_num_submodels = 0;
|
||||
|
||||
#define MAX_DEBRIS_DIST 10000.0f // Debris goes away if it's this far away.
|
||||
#define DEBRIS_DISTANCE_CHECK_TIME (10*1000) // Check every 10 seconds.
|
||||
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
|
||||
|
||||
const auto OnDebrisCreatedHook = scripting::Hook::Factory(
|
||||
@@ -75,7 +74,7 @@ static void debris_start_death_roll(object *debris_obj, debris *debris_p)
|
||||
|
||||
int fireball_type = fireball_ship_explosion_type(&Ship_info[debris_p->ship_info_index]);
|
||||
if(fireball_type < 0) {
|
||||
fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
|
||||
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
|
||||
}
|
||||
fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
|
||||
|
||||
@@ -196,34 +195,6 @@ void debris_delete( object * obj )
|
||||
db->objnum = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* If debris piece *db is far away from all players, make it go away very soon.
|
||||
* In single player game, delete if MAX_DEBRIS_DIST from player.
|
||||
* In multiplayer game, delete if MAX_DEBRIS_DIST from all players.
|
||||
*/
|
||||
void maybe_delete_debris(debris *db)
|
||||
{
|
||||
object *objp;
|
||||
|
||||
if (Player_obj != nullptr && timestamp_elapsed(db->next_distance_check) && timestamp_elapsed(db->must_survive_until)) {
|
||||
if (!(Game_mode & GM_MULTIPLAYER)) { // In single player game, just check against player.
|
||||
if (vm_vec_dist_quick(&Player_obj->pos, &Objects[db->objnum].pos) > MAX_DEBRIS_DIST)
|
||||
db->lifeleft = 0.1f;
|
||||
else
|
||||
db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
|
||||
} else {
|
||||
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
||||
if (objp->flags[Object::Object_Flags::Player_ship]) {
|
||||
if (vm_vec_dist_quick(&objp->pos, &Objects[db->objnum].pos) < MAX_DEBRIS_DIST) {
|
||||
db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
db->lifeleft = 0.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do various updates to debris: check if time to die, start fireballs
|
||||
@@ -246,7 +217,7 @@ void debris_process_post(object * obj, float frame_time)
|
||||
radar_plot_object( obj );
|
||||
|
||||
if ( timestamp_elapsed(db->sound_delay) ) {
|
||||
obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector, 0);
|
||||
obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector);
|
||||
db->sound_delay = 0;
|
||||
}
|
||||
}
|
||||
@@ -260,8 +231,6 @@ void debris_process_post(object * obj, float frame_time)
|
||||
}
|
||||
}
|
||||
|
||||
maybe_delete_debris(db); // Make this debris go away if it's very far away.
|
||||
|
||||
// ================== DO THE ELECTRIC ARCING STUFF =====================
|
||||
if ( db->arc_frequency <= 0 ) {
|
||||
return; // If arc_frequency <= 0, this piece has no arcs on it
|
||||
@@ -274,7 +243,7 @@ void debris_process_post(object * obj, float frame_time)
|
||||
|
||||
if (db->is_hull) {
|
||||
|
||||
int n, n_arcs = ((rand()>>5) % 3)+1; // Create 1-3 sparks
|
||||
int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks
|
||||
|
||||
vec3d v1, v2, v3, v4;
|
||||
|
||||
@@ -283,8 +252,7 @@ void debris_process_post(object * obj, float frame_time)
|
||||
|
||||
n = 0;
|
||||
|
||||
int a = 100, b = 1000;
|
||||
int lifetime = (myrand()%((b)-(a)+1))+(a);
|
||||
int lifetime = Random::next(100, 1000);
|
||||
|
||||
// Create the spark effects
|
||||
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
|
||||
@@ -351,8 +319,8 @@ void debris_process_post(object * obj, float frame_time)
|
||||
db->arc_timestamp[i] = timestamp(-1);
|
||||
} else {
|
||||
// Maybe move a vertex.... 20% of the time maybe?
|
||||
int mr = myrand();
|
||||
if ( mr < RAND_MAX/5 ) {
|
||||
int mr = Random::next();
|
||||
if ( mr < Random::MAX_VALUE/5 ) {
|
||||
vec3d v1, v2;
|
||||
|
||||
submodel_get_two_random_points_better(db->model_num, db->submodel_num, &v1, &v2);
|
||||
@@ -424,17 +392,6 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
sip = &Ship_info[shipp->ship_info_index];
|
||||
vaporize = (shipp->flags[Ship::Ship_Flags::Vaporize]);
|
||||
|
||||
if ( !hull_flag ) {
|
||||
// Make vaporize debris seen from farther away
|
||||
float dist = vm_vec_dist_quick( pos, &Eye_position );
|
||||
if (vaporize) {
|
||||
dist /= 2.0f;
|
||||
}
|
||||
if ( dist > 200.0f ) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// try to maintain our soft limit
|
||||
if (hull_flag && (Num_hull_pieces >= SOFT_LIMIT_DEBRIS_PIECES)) {
|
||||
// cause oldest hull debris chunk to blow up
|
||||
@@ -457,8 +414,40 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
|
||||
db = &Debris[n];
|
||||
|
||||
//WMC - We must survive until now, at least.
|
||||
db->must_survive_until = timestamp();
|
||||
if (!hull_flag) {
|
||||
if (model_num >= 0) {
|
||||
db->model_num = model_num;
|
||||
db->submodel_num = Random::next(sip->generic_debris_num_submodels);
|
||||
}
|
||||
else if (vaporize) {
|
||||
db->model_num = Debris_vaporize_model;
|
||||
db->submodel_num = Random::next(Debris_num_submodels);
|
||||
}
|
||||
else {
|
||||
db->model_num = Debris_model;
|
||||
db->submodel_num = Random::next(Debris_num_submodels);
|
||||
}
|
||||
}
|
||||
else {
|
||||
db->model_num = model_num;
|
||||
db->submodel_num = submodel_num;
|
||||
}
|
||||
|
||||
db->model_instance_num = model_create_instance(false, db->model_num);
|
||||
|
||||
float radius = submodel_get_radius(db->model_num, db->submodel_num);
|
||||
|
||||
// if its generic, maybe cull it if its too small and far
|
||||
if (!hull_flag) {
|
||||
float dist = vm_vec_dist_quick(pos, &Eye_position);
|
||||
// Make vaporize debris seen from farther away
|
||||
if (vaporize) {
|
||||
dist /= 2.0f;
|
||||
}
|
||||
if (dist > radius * 200.0f) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if(hull_flag && sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
|
||||
{
|
||||
@@ -468,14 +457,16 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
{
|
||||
// Create Debris piece n!
|
||||
if ( hull_flag ) {
|
||||
if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
|
||||
db->lifeleft = 2.0f * (myrand() * RAND_MAX_1f) + 0.5f;
|
||||
if (Random::next() < (Random::MAX_VALUE/6)) // Make some pieces blow up shortly after explosion.
|
||||
db->lifeleft = 2.0f * (frand()) + 0.5f;
|
||||
else {
|
||||
db->flags.set(Debris_Flags::DoNotExpire);
|
||||
db->lifeleft = -1.0f; // large hull pieces stay around forever
|
||||
}
|
||||
} else {
|
||||
db->lifeleft = (myrand() * RAND_MAX_1f) * 2.0f + 0.1f;
|
||||
} else {
|
||||
// small non-hull pieces should stick around longer the larger they are
|
||||
// sqrtf should make sure its never too crazy long
|
||||
db->lifeleft = (frand() * 2.0f + 0.1f) * sqrtf(radius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,12 +475,10 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
{
|
||||
if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
|
||||
{
|
||||
db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
|
||||
db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
|
||||
}
|
||||
else if(sip->debris_min_lifetime >= 0.0f)
|
||||
{
|
||||
db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
|
||||
if(db->lifeleft < sip->debris_min_lifetime)
|
||||
db->lifeleft = sip->debris_min_lifetime;
|
||||
}
|
||||
@@ -514,7 +503,6 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
db->fire_timeout = 0; // if not changed, timestamp_elapsed() will return false
|
||||
db->time_started = Missiontime;
|
||||
db->species = Ship_info[shipp->ship_info_index].species;
|
||||
db->next_distance_check = (myrand() % 2000) + 4*DEBRIS_DISTANCE_CHECK_TIME;
|
||||
db->parent_alt_name = shipp->alt_type_index;
|
||||
db->damage_mult = 1.0f;
|
||||
|
||||
@@ -533,25 +521,9 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
db->arc_frequency = 0;
|
||||
}
|
||||
|
||||
if ( model_num < 0 ) {
|
||||
if (vaporize) {
|
||||
db->model_num = Debris_vaporize_model;
|
||||
} else {
|
||||
db->model_num = Debris_model;
|
||||
}
|
||||
db->submodel_num = (myrand()>>4) % Debris_num_submodels;
|
||||
} else {
|
||||
db->model_num = model_num;
|
||||
db->submodel_num = submodel_num;
|
||||
}
|
||||
|
||||
db->model_instance_num = model_create_instance(false, db->model_num);
|
||||
|
||||
float radius = submodel_get_radius( db->model_num, db->submodel_num );
|
||||
|
||||
db->next_fireball = timestamp_rand(500,2000); //start one 1/2 - 2 secs later
|
||||
|
||||
if ( pos == NULL )
|
||||
if ( pos == nullptr )
|
||||
pos = &source_obj->pos;
|
||||
|
||||
flagset<Object::Object_Flags> default_flags;
|
||||
@@ -559,10 +531,18 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
default_flags.set(Object::Object_Flags::Physics);
|
||||
default_flags.set(Object::Object_Flags::Collides, hull_flag != 0);
|
||||
|
||||
objnum = obj_create( OBJ_DEBRIS, parent_objnum, n, &source_obj->orient, pos, radius, default_flags);
|
||||
matrix orient = source_obj->orient;
|
||||
if (!db->is_hull) {
|
||||
// non-hull debris has no relation to its parent orientation
|
||||
vec3d rand;
|
||||
vm_vec_rand_vec(&rand);
|
||||
vm_vector_2_matrix(&orient, &rand);
|
||||
}
|
||||
|
||||
objnum = obj_create(OBJ_DEBRIS, parent_objnum, n, &orient, pos, radius, default_flags, false);
|
||||
if ( objnum == -1 ) {
|
||||
mprintf(("Couldn't create debris object -- out of object slots\n"));
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
db->objnum = objnum;
|
||||
@@ -612,7 +592,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
|
||||
if ( hull_flag ) {
|
||||
float t;
|
||||
scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from location of blast center
|
||||
scale = exp_force * i2fl(Random::next(10, 29)); // for radial_vel away from location of blast center
|
||||
db->sound_delay = timestamp(DEBRIS_SOUND_DELAY);
|
||||
|
||||
// set up physics mass and I_inv for hull debris pieces
|
||||
@@ -624,7 +604,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
|
||||
// limit the amount of time that fireballs appear
|
||||
// let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds.
|
||||
t = 1000*Objects[db->source_objnum].radius/3 + myrand()%(fl2i(1000*3*Objects[db->source_objnum].radius));
|
||||
t = 1000*Objects[db->source_objnum].radius/3 + Random::next(fl2i(1000*3*Objects[db->source_objnum].radius));
|
||||
db->fire_timeout = timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
|
||||
|
||||
if ( Objects[db->source_objnum].radius < MIN_RADIUS_FOR_PERSISTANT_DEBRIS ) {
|
||||
@@ -635,7 +615,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
}
|
||||
}
|
||||
else {
|
||||
scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from blast center (non-hull)
|
||||
scale = exp_force * i2fl(Random::next(10, 29)); // for radial_vel away from blast center (non-hull)
|
||||
}
|
||||
|
||||
if ( vm_vec_mag_squared( &to_center ) < 0.1f ) {
|
||||
@@ -662,7 +642,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
radius = 1.0f;
|
||||
}
|
||||
|
||||
scale = ( 6.0f + i2fl(myrand()%4) ) / radius;
|
||||
scale = (6.0f + i2fl(Random::next(4))) / radius;
|
||||
|
||||
vm_vec_rand_vec_quick(&rotvel);
|
||||
vm_vec_scale(&rotvel, scale);
|
||||
@@ -899,13 +879,13 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
smi->collision_checked = false;
|
||||
|
||||
// set angles for last frame (need to set to prev to get p0)
|
||||
angles copy_angles = smi->angs;
|
||||
matrix copy_matrix = smi->canonical_orient;
|
||||
|
||||
// find the start and end positions of the sphere in submodel RF
|
||||
smi->angs = smi->prev_angs;
|
||||
smi->canonical_orient = smi->canonical_prev_orient;
|
||||
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
|
||||
|
||||
smi->angs = copy_angles;
|
||||
smi->canonical_orient = copy_matrix;
|
||||
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
||||
|
||||
mc.p0 = &p0;
|
||||
@@ -924,7 +904,7 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
|
||||
// set up debris_hit_info for rotating submodel
|
||||
if (debris_hit_info->edge_hit == 0) {
|
||||
model_instance_find_obj_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
model_instance_find_world_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
@@ -956,7 +936,7 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
|
||||
// get collision normal if not edge hit
|
||||
if (debris_hit_info->edge_hit == 0) {
|
||||
model_instance_find_obj_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
model_instance_find_world_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
@@ -1127,7 +1107,7 @@ void debris_render(object * obj, model_draw_list *scene)
|
||||
MONITOR_INC(NumHullDebrisRend, 1);
|
||||
} else {
|
||||
MONITOR_INC(NumSmallDebrisRend, 1);
|
||||
render_info.set_flags(MR_NO_LIGHTING);
|
||||
// render_info.set_flags(MR_NO_LIGHTING);
|
||||
}
|
||||
|
||||
submodel_render_queue( &render_info, scene, pm, pmi, db->submodel_num, &obj->orient, &obj->pos );
|
||||
@@ -1146,3 +1126,19 @@ bool debris_is_vaporized(debris *db)
|
||||
{
|
||||
return db->model_num == Debris_vaporize_model;
|
||||
}
|
||||
|
||||
void create_generic_debris(object* ship_objp, vec3d* pos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris) {
|
||||
Assertion(ship_objp->type == OBJ_SHIP, "create_generic_debris called for a non-ship, only ships can spew debris!");
|
||||
if (ship_objp->type != OBJ_SHIP)
|
||||
return;
|
||||
|
||||
float num_debris = frand_range(min_num_debris, max_num_debris);
|
||||
|
||||
num_debris *= (Detail.num_small_debris + 0.5f) / 4.5f;
|
||||
|
||||
vec3d create_pos = *pos;
|
||||
for (int i = 0; i < num_debris; i++) {
|
||||
int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;
|
||||
debris_create(ship_objp, model_num, -1, &create_pos, pos, 0, speed_mult);
|
||||
}
|
||||
}
|
||||
@@ -40,17 +40,15 @@ typedef struct debris {
|
||||
gamesnd_id ambient_sound; // Ambient looping sound
|
||||
int objnum = -1; // What object this is linked to
|
||||
float lifeleft; // When 0 or less object dies
|
||||
int must_survive_until; //WMC - timestamp of earliest point that it can be murthered.
|
||||
int model_num; // What model this uses
|
||||
int model_instance_num; // What model instance this uses - needed for arcs
|
||||
int submodel_num; // What submodel this uses
|
||||
int next_fireball; // When to start a fireball
|
||||
int is_hull; // indicates a large hull chunk of debris
|
||||
int is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment
|
||||
int species; // What species this is from. -1 if don't care.
|
||||
int fire_timeout; // timestamp that holds time for fireballs to stop appearing
|
||||
int sound_delay; // timestamp to signal when sound should start
|
||||
fix time_started; // time when debris was created
|
||||
int next_distance_check; // timestamp to determine whether to delete this piece of debris.
|
||||
|
||||
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
|
||||
int arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. -1 is not used
|
||||
@@ -82,4 +80,8 @@ void debris_remove_from_hull_list(debris *db);
|
||||
bool debris_is_generic(debris *db);
|
||||
bool debris_is_vaporized(debris *db);
|
||||
|
||||
// creates a burst of generic debris at hitpos from ship_objp, with a random number between min and max
|
||||
// use_ship_debris is for whether the ship's generic debris should be used, or simply debris01.pof
|
||||
void create_generic_debris(object* ship_objp, vec3d* hitpos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris);
|
||||
|
||||
#endif // _DEBRIS_H
|
||||
@@ -65,15 +65,23 @@ void GetLightInfo(vec3 position, out vec3 lightDir, out float attenuation)
|
||||
if (lightType == LT_TUBE) { // Tube light
|
||||
float beamLength = length(beamVec);
|
||||
vec3 beamDir = beamVec / beamLength;
|
||||
//The actual 'lighting element' is shorter than the light volume cylinder
|
||||
//To compensate the light is moved forward along the beam one radius and the length shortened
|
||||
//this allows room for clean falloff of the light past the ends of beams.
|
||||
vec3 adjustedLightPos = lightPosition - (beamDir * lightRadius);
|
||||
beamLength = beamLength - (lightRadius * 2.0);
|
||||
//adjustments having been made, lightdir needs recalculating
|
||||
lightDir = adjustedLightPos - position.xyz;
|
||||
// Get nearest point on line
|
||||
float neardist = clamp(dot(lightDir, beamDir), 0.0, beamLength);
|
||||
// Move back from the endpoint of the beam along the beam by the distance we calculated
|
||||
vec3 nearest = lightPosition - beamDir * neardist;
|
||||
vec3 nearest = adjustedLightPos - beamDir * neardist;
|
||||
lightDir = nearest - position.xyz;
|
||||
dist = length(lightDir);
|
||||
if(dist > lightRadius) {
|
||||
discard;
|
||||
}
|
||||
attenuation = 1.0 - clamp(dist / lightRadius, 0.0, 1.0);
|
||||
} else if (lightType == LT_CONE) {
|
||||
float coneDot = dot(normalize(-lightDir), coneDir);
|
||||
if(dualCone) {
|
||||
|
||||
@@ -12,7 +12,7 @@ layout (std140) uniform genericData {
|
||||
vec3 fog_color;
|
||||
float fog_start;
|
||||
|
||||
float fog_scale;
|
||||
float fog_density;
|
||||
float zNear;
|
||||
float zFar;
|
||||
};
|
||||
@@ -23,17 +23,15 @@ void main()
|
||||
|
||||
// If the scene is in fog then we change the color we operate on based on the depth of the pixel
|
||||
float depth_val = texture(depth_tex, fragTexCoord.xy).x;
|
||||
// Transform depth value from [-1, 1] to [0, 1]
|
||||
float depth_normalized = fma(depth_val, 0.5, 0.5);
|
||||
// Transform depth value from [0, 1] to [-1, 1]
|
||||
float depth_normalized = 2 * depth_val - 1;
|
||||
// Now we compute the depth value in projection space using the clipping plane information
|
||||
float view_depth = 2.0 * zNear * zFar / (zFar + zNear - depth_normalized * (zFar - zNear));
|
||||
|
||||
if (isinf(view_depth)) {
|
||||
fragOut0.rgb = color_in.rgb;
|
||||
} else {
|
||||
// This is the same formula that is also used by the main shader
|
||||
float fog_dist = clamp((view_depth - fog_start) * 0.75 * fog_scale, 0.0, 1.0);
|
||||
|
||||
float fog_dist = clamp(1 - pow(fog_density, view_depth - fog_start) , 0.0, 1.0);
|
||||
vec3 finalFogColor = srgb_to_linear(fog_color);
|
||||
|
||||
fragOut0.rgb = mix(color_in.rgb, finalFogColor, fog_dist);
|
||||
|
||||
@@ -429,7 +429,15 @@ void main()
|
||||
fragOut0 = baseColor;
|
||||
#ifdef FLAG_DEFERRED
|
||||
fragOut1 = vec4(vertIn.position.xyz, 1.0);
|
||||
fragOut2 = vec4(normal, glossData);
|
||||
|
||||
// Anti-glint "trick" based on Valve's "Advanced VR Rendering" talk at GDC2015:
|
||||
// http://media.steampowered.com/apps/valve/2015/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf Page 43
|
||||
// basically make surfaces rougher if local normals change too fast in screenspace
|
||||
vec2 normDx = dFdx(normal.xy);
|
||||
vec2 normDy = dFdy(normal.xy);
|
||||
float glossGeo = 1.0f - pow(clamp(max(dot(normDx,normDx), dot(normDy,normDy)),0.0,1.0),0.33);
|
||||
fragOut2 = vec4(normal, min(glossData, glossGeo));
|
||||
|
||||
fragOut3 = vec4(specColor.rgb, fresnelFactor);
|
||||
fragOut4 = emissiveColor;
|
||||
#endif
|
||||
|
||||
@@ -221,6 +221,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Freighter
|
||||
$Counts for Alone: YES
|
||||
@@ -250,6 +251,7 @@ $AI:
|
||||
+Active docks: ( "cargo" )
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: AWACS
|
||||
$Counts for Alone: YES
|
||||
@@ -277,6 +279,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Gas Miner
|
||||
$Counts for Alone: YES
|
||||
@@ -304,6 +307,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Cruiser
|
||||
$Counts for Alone: YES
|
||||
@@ -331,6 +335,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Corvette
|
||||
$Counts for Alone: YES
|
||||
@@ -358,6 +363,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Capital
|
||||
$Counts for Alone: YES
|
||||
@@ -386,6 +392,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Super Cap
|
||||
$Counts for Alone: YES
|
||||
@@ -412,6 +419,7 @@ $AI:
|
||||
+Can Form Wing: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Drydock
|
||||
$Counts for Alone: YES
|
||||
@@ -436,6 +444,7 @@ $AI:
|
||||
+Turrets attack this: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
$Name: Knossos Device
|
||||
$Counts for Alone: YES
|
||||
@@ -458,5 +467,6 @@ $AI:
|
||||
+Turrets attack this: YES
|
||||
+Passive docks: ( "support" )
|
||||
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
|
||||
$Vaporize Percent Chance: 0.0
|
||||
#End
|
||||
+104
-106
@@ -14,6 +14,7 @@
|
||||
#include "graphics/tmapper.h"
|
||||
#include "localization/localize.h"
|
||||
#include "model/model.h"
|
||||
#include "nebula/neb.h"
|
||||
#include "object/object.h"
|
||||
#include "options/Option.h"
|
||||
#include "parse/parselo.h"
|
||||
@@ -31,13 +32,15 @@ int Knossos_warp_ani_used;
|
||||
|
||||
#define MAX_WARP_LOD 0
|
||||
|
||||
fireball Fireballs[MAX_FIREBALLS];
|
||||
constexpr int INTITIAL_FIREBALL_CONTAINTER_SIZE = 256;
|
||||
|
||||
SCP_vector<fireball> Fireballs;
|
||||
SCP_vector<int> Unused_fireball_indices;
|
||||
|
||||
fireball_info Fireball_info[MAX_FIREBALL_TYPES];
|
||||
|
||||
int fireball_used[MAX_FIREBALL_TYPES];
|
||||
|
||||
int Num_fireballs = 0;
|
||||
int Num_fireball_types = 0;
|
||||
|
||||
bool fireballs_inited = false;
|
||||
@@ -284,11 +287,13 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
}
|
||||
}
|
||||
|
||||
bool first_time;
|
||||
// now select our entry accordingly...
|
||||
// are we using a previous entry?
|
||||
if (existing_idx >= 0)
|
||||
{
|
||||
fi = &Fireball_info[existing_idx];
|
||||
first_time = false;
|
||||
}
|
||||
// we are creating a new entry, so set some defaults
|
||||
else
|
||||
@@ -312,6 +317,7 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
fireball_set_default_warp_attributes(Num_fireball_types);
|
||||
|
||||
Num_fireball_types++;
|
||||
first_time = true;
|
||||
}
|
||||
|
||||
// copy over what we already parsed
|
||||
@@ -319,6 +325,7 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
strcpy_s(fi->unique_id, unique_id);
|
||||
strcpy_s(fi->lod[0].filename, fireball_filename);
|
||||
|
||||
|
||||
// Do we have a LOD num?
|
||||
if (optional_string("$LOD:"))
|
||||
{
|
||||
@@ -326,6 +333,9 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
|
||||
if (fi->lod_count > MAX_FIREBALL_LOD)
|
||||
fi->lod_count = MAX_FIREBALL_LOD;
|
||||
} else if (first_time) {
|
||||
//assume a LOD of at least 1
|
||||
fi->lod_count = 1;
|
||||
}
|
||||
|
||||
// check for particular lighting color
|
||||
@@ -435,8 +445,6 @@ void fireball_load_data()
|
||||
// This will get called at the start of each level.
|
||||
void fireball_init()
|
||||
{
|
||||
int i;
|
||||
|
||||
if ( !fireballs_inited ) {
|
||||
// Do all the processing that happens only once
|
||||
fireball_parse_tbl();
|
||||
@@ -446,10 +454,10 @@ void fireball_init()
|
||||
}
|
||||
|
||||
// Reset everything between levels
|
||||
Num_fireballs = 0;
|
||||
for (i=0; i<MAX_FIREBALLS; i++ ) {
|
||||
Fireballs[i].objnum = -1;
|
||||
}
|
||||
Fireballs.clear();
|
||||
Fireballs.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
|
||||
Unused_fireball_indices.clear();
|
||||
Unused_fireball_indices.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
|
||||
|
||||
// Goober5000 - reset Knossos warp flag
|
||||
Knossos_warp_ani_used = 0;
|
||||
@@ -467,13 +475,15 @@ void fireball_delete( object * obj )
|
||||
fireball *fb;
|
||||
|
||||
num = obj->instance;
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
|
||||
fb = &Fireballs[num];
|
||||
|
||||
Assert( fb->objnum == OBJ_INDEX(obj));
|
||||
|
||||
Fireballs[num].objnum = -1;
|
||||
Num_fireballs--;
|
||||
Assert( Num_fireballs >= 0 );
|
||||
Unused_fireball_indices.push_back(num);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,13 +491,9 @@ void fireball_delete( object * obj )
|
||||
*/
|
||||
void fireball_delete_all()
|
||||
{
|
||||
fireball *fb;
|
||||
int i;
|
||||
|
||||
for ( i = 0; i < MAX_FIREBALLS; i++ ) {
|
||||
fb = &Fireballs[i];
|
||||
if ( fb->objnum != -1 ) {
|
||||
obj_delete(fb->objnum);
|
||||
for (auto& current_fireball : Fireballs) {
|
||||
if ( current_fireball.objnum != -1 ) {
|
||||
obj_delete(current_fireball.objnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,6 +505,8 @@ void fireball_set_framenum(int num)
|
||||
fireball_info *fd;
|
||||
fireball_lod *fl;
|
||||
|
||||
Assert(static_cast<int>(Fireballs.size()) > num);
|
||||
|
||||
fb = &Fireballs[num];
|
||||
fd = &Fireball_info[Fireballs[num].fireball_info_index];
|
||||
|
||||
@@ -537,6 +545,9 @@ int fireball_is_perishable(object * obj)
|
||||
|
||||
num = obj->instance;
|
||||
objnum = OBJ_INDEX(obj);
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert((int)Fireballs.size() > obj->instance);
|
||||
Assert( Fireballs[num].objnum == objnum );
|
||||
|
||||
fb = &Fireballs[num];
|
||||
@@ -553,43 +564,6 @@ int fireball_is_perishable(object * obj)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* There are too many fireballs, so delete the oldest small one
|
||||
* to free up a slot.
|
||||
*
|
||||
* @return The fireball slot freed.
|
||||
*/
|
||||
int fireball_free_one()
|
||||
{
|
||||
fireball *fb;
|
||||
int i;
|
||||
|
||||
int oldest_objnum = -1, oldest_slotnum = -1;
|
||||
float lifeleft, oldest_lifeleft = 0.0f;
|
||||
|
||||
for ( i = 0; i < MAX_FIREBALLS; i++ ) {
|
||||
fb = &Fireballs[i];
|
||||
|
||||
// only remove the ones that aren't warp effects
|
||||
if ( (fb->objnum >= 0) && fireball_is_perishable(&Objects[fb->objnum]) ) {
|
||||
|
||||
lifeleft = fb->total_time - fb->time_elapsed;
|
||||
if ( (oldest_objnum < 0) || (lifeleft < oldest_lifeleft) ) {
|
||||
oldest_slotnum = i;
|
||||
oldest_lifeleft = lifeleft;
|
||||
oldest_objnum = fb->objnum;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( oldest_objnum > -1 ) {
|
||||
obj_delete(oldest_objnum);
|
||||
}
|
||||
return oldest_slotnum;
|
||||
}
|
||||
|
||||
int fireball_is_warp(object * obj)
|
||||
{
|
||||
int num, objnum;
|
||||
@@ -597,6 +571,9 @@ int fireball_is_warp(object * obj)
|
||||
|
||||
num = obj->instance;
|
||||
objnum = OBJ_INDEX(obj);
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
|
||||
Assert( Fireballs[num].objnum == objnum );
|
||||
|
||||
fb = &Fireballs[num];
|
||||
@@ -641,6 +618,9 @@ void fireball_process_post(object * obj, float frame_time)
|
||||
|
||||
num = obj->instance;
|
||||
objnum = OBJ_INDEX(obj);
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
|
||||
Assert( Fireballs[num].objnum == objnum );
|
||||
|
||||
fb = &Fireballs[num];
|
||||
@@ -665,6 +645,10 @@ float fireball_lifeleft( object *obj )
|
||||
|
||||
num = obj->instance;
|
||||
objnum = OBJ_INDEX(obj);
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
|
||||
|
||||
Assert( Fireballs[num].objnum == objnum );
|
||||
|
||||
fb = &Fireballs[num];
|
||||
@@ -680,6 +664,10 @@ float fireball_lifeleft_percent( object *obj )
|
||||
int num, objnum;
|
||||
fireball *fb;
|
||||
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
|
||||
|
||||
num = obj->instance;
|
||||
objnum = OBJ_INDEX(obj);
|
||||
Assert( Fireballs[num].objnum == objnum );
|
||||
@@ -781,10 +769,8 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
{
|
||||
int n, objnum, fb_lod;
|
||||
object *obj;
|
||||
fireball *fb;
|
||||
fireball_info *fd;
|
||||
fireball_lod *fl;
|
||||
|
||||
Assert( fireball_type > -1 );
|
||||
Assert( fireball_type < Num_fireball_types );
|
||||
|
||||
@@ -800,23 +786,21 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
}
|
||||
}
|
||||
|
||||
if ( (Num_fireballs >= MAX_FIREBALLS) || (Num_objects >= MAX_OBJECTS) ) {
|
||||
|
||||
// out of slots, so free one up.
|
||||
n = fireball_free_one();
|
||||
if ( n < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
for ( n = 0; n < MAX_FIREBALLS; n++ ) {
|
||||
if ( Fireballs[n].objnum < 0 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert( n != MAX_FIREBALLS );
|
||||
if (Num_objects >= MAX_OBJECTS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fb = &Fireballs[n];
|
||||
|
||||
if (!Unused_fireball_indices.empty()) {
|
||||
n = Unused_fireball_indices.back();
|
||||
Unused_fireball_indices.pop_back();
|
||||
}
|
||||
else {
|
||||
n = static_cast<int>(Fireballs.size());
|
||||
Fireballs.emplace_back();
|
||||
}
|
||||
|
||||
fireball* new_fireball = &Fireballs[n];
|
||||
|
||||
// get an lod to use
|
||||
fb_lod = fireball_get_lod(pos, fd, size);
|
||||
@@ -833,13 +817,13 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
}
|
||||
fl = &fd->lod[fb_lod];
|
||||
|
||||
fb->lod = (char)fb_lod;
|
||||
new_fireball->lod = (char)fb_lod;
|
||||
|
||||
fb->flags = extra_flags;
|
||||
fb->warp_open_sound_index = warp_open_sound;
|
||||
fb->warp_close_sound_index = warp_close_sound;
|
||||
fb->warp_open_duration = (warp_open_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_open_duration;
|
||||
fb->warp_close_duration = (warp_close_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_close_duration;
|
||||
new_fireball->flags = extra_flags;
|
||||
new_fireball->warp_open_sound_index = warp_open_sound;
|
||||
new_fireball->warp_close_sound_index = warp_close_sound;
|
||||
new_fireball->warp_open_duration = (warp_open_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_open_duration;
|
||||
new_fireball->warp_close_duration = (warp_close_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_close_duration;
|
||||
|
||||
matrix orient;
|
||||
if(orient_override != NULL){
|
||||
@@ -854,60 +838,55 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
|
||||
flagset<Object::Object_Flags> default_flags;
|
||||
default_flags.set(Object::Object_Flags::Renders);
|
||||
objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags);
|
||||
|
||||
if (objnum < 0) {
|
||||
Int3(); // Get John, we ran out of objects for fireballs
|
||||
return objnum;
|
||||
}
|
||||
objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags, false);
|
||||
|
||||
obj = &Objects[objnum];
|
||||
|
||||
fb->fireball_info_index = fireball_type;
|
||||
fb->fireball_render_type = render_type;
|
||||
fb->time_elapsed = 0.0f;
|
||||
fb->objnum = objnum;
|
||||
fb->current_bitmap = -1;
|
||||
new_fireball->fireball_info_index = fireball_type;
|
||||
new_fireball->fireball_render_type = render_type;
|
||||
new_fireball->time_elapsed = 0.0f;
|
||||
new_fireball->objnum = objnum;
|
||||
new_fireball->current_bitmap = -1;
|
||||
|
||||
switch( fb->fireball_render_type ) {
|
||||
switch( new_fireball->fireball_render_type ) {
|
||||
|
||||
case FIREBALL_MEDIUM_EXPLOSION:
|
||||
fb->orient = (myrand()>>8) & 7; // 0 - 7
|
||||
new_fireball->orient = Random::next() & 7; // 0 - 7
|
||||
break;
|
||||
|
||||
case FIREBALL_LARGE_EXPLOSION:
|
||||
fb->orient = (myrand()>>8) % 360; // 0 - 359
|
||||
new_fireball->orient = Random::next(360); // 0 - 359
|
||||
break;
|
||||
|
||||
case FIREBALL_WARP_EFFECT:
|
||||
// Play sound effect for warp hole opening up
|
||||
fireball_play_warphole_open_sound(ship_class, fb);
|
||||
fireball_play_warphole_open_sound(ship_class, new_fireball);
|
||||
|
||||
// warp in type
|
||||
if (reverse) {
|
||||
fb->orient = 1;
|
||||
new_fireball->orient = 1;
|
||||
// if warp out, then reverse the orientation
|
||||
vm_vec_scale( &obj->orient.vec.fvec, -1.0f ); // Reverse the forward vector
|
||||
vm_vec_scale( &obj->orient.vec.rvec, -1.0f ); // Reverse the right vector
|
||||
} else {
|
||||
fb->orient = 0;
|
||||
new_fireball->orient = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Int3();
|
||||
UNREACHABLE("Bad type set in fireball_create");
|
||||
break;
|
||||
}
|
||||
|
||||
if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT ) {
|
||||
if ( new_fireball->fireball_render_type == FIREBALL_WARP_EFFECT ) {
|
||||
Assert( warp_lifetime >= 4.0f ); // Warp lifetime must be at least 4 seconds!
|
||||
if ( warp_lifetime < 4.0f )
|
||||
warp_lifetime = 4.0f;
|
||||
fb->total_time = warp_lifetime; // in seconds
|
||||
new_fireball->total_time = warp_lifetime; // in seconds
|
||||
} else {
|
||||
fb->total_time = i2fl(fl->num_frames) / fl->fps; // in seconds
|
||||
new_fireball->total_time = i2fl(fl->num_frames) / fl->fps; // in seconds
|
||||
}
|
||||
|
||||
|
||||
fireball_set_framenum(n);
|
||||
|
||||
if ( velocity ) {
|
||||
@@ -923,7 +902,6 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
vm_vec_zero(&obj->phys_info.max_rotvel);
|
||||
}
|
||||
|
||||
Num_fireballs++;
|
||||
return objnum;
|
||||
}
|
||||
|
||||
@@ -1008,9 +986,9 @@ int fireball_ship_explosion_type(ship_info *sip)
|
||||
}
|
||||
|
||||
if(ship_fireballs > 0){
|
||||
index = sip->explosion_bitmap_anims[rand()%ship_fireballs];
|
||||
index = sip->explosion_bitmap_anims[Random::next(ship_fireballs)];
|
||||
} else if(objecttype_fireballs > 0){
|
||||
index = Ship_types[sip->class_type].explosion_bitmap_anims[rand()%objecttype_fireballs];
|
||||
index = Ship_types[sip->class_type].explosion_bitmap_anims[Random::next(objecttype_fireballs)];
|
||||
}
|
||||
|
||||
return index;
|
||||
@@ -1027,7 +1005,7 @@ int fireball_asteroid_explosion_type(asteroid_info *aip)
|
||||
int roid_fireballs = (int)aip->explosion_bitmap_anims.size();
|
||||
|
||||
if (roid_fireballs > 0) {
|
||||
index = aip->explosion_bitmap_anims[rand()%roid_fireballs];
|
||||
index = aip->explosion_bitmap_anims[Random::next(roid_fireballs)];
|
||||
}
|
||||
|
||||
return index;
|
||||
@@ -1058,24 +1036,33 @@ void fireball_render(object* obj, model_draw_list *scene)
|
||||
|
||||
MONITOR_INC( NumFireballsRend, 1 );
|
||||
|
||||
// Make sure the new system works fine.
|
||||
Assert(obj->instance > -1);
|
||||
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
|
||||
|
||||
num = obj->instance;
|
||||
fb = &Fireballs[num];
|
||||
|
||||
if ( Fireballs[num].current_bitmap < 0 )
|
||||
if ( fb->current_bitmap < 0 )
|
||||
return;
|
||||
|
||||
float alpha = 1.0f;
|
||||
|
||||
if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_fireballs)
|
||||
alpha *= neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_FIREBALL(obj->radius));
|
||||
|
||||
g3_transfer_vertex(&p, &obj->pos);
|
||||
|
||||
switch ( fb->fireball_render_type ) {
|
||||
|
||||
case FIREBALL_MEDIUM_EXPLOSION: {
|
||||
batching_add_volume_bitmap(Fireballs[num].current_bitmap, &p, fb->orient, obj->radius);
|
||||
batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius, alpha);
|
||||
}
|
||||
break;
|
||||
|
||||
case FIREBALL_LARGE_EXPLOSION: {
|
||||
// Make the big explosions rotate with the viewer.
|
||||
batching_add_volume_bitmap_rotated(Fireballs[num].current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius);
|
||||
batching_add_volume_bitmap_rotated(fb->current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius, alpha);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1093,3 +1080,14 @@ void fireball_render(object* obj, model_draw_list *scene)
|
||||
Int3();
|
||||
}
|
||||
}
|
||||
|
||||
// Because fireballs are only added and removed in two places, and Unused_fireball_indices is always updated in those places to contain unused indices,
|
||||
// this very simple code will give you the correct count of currently existing fireballs in use.
|
||||
int fireball_get_count()
|
||||
{
|
||||
int count = static_cast<int>(Fireballs.size()) - static_cast<int>(Unused_fireball_indices.size());
|
||||
|
||||
Assert (count >= 0);
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -95,8 +95,7 @@ typedef struct fireball {
|
||||
} fireball;
|
||||
// end move
|
||||
|
||||
extern fireball Fireballs[MAX_FIREBALLS];
|
||||
extern int Num_fireballs;
|
||||
extern SCP_vector<fireball> Fireballs;
|
||||
|
||||
extern bool fireballs_inited;
|
||||
|
||||
@@ -146,4 +145,7 @@ extern int Knossos_warp_ani_used;
|
||||
|
||||
extern bool Fireball_use_3d_warp;
|
||||
|
||||
// Cyborg - get a count of how many valid fireballs are in the mission.
|
||||
int fireball_get_count();
|
||||
|
||||
#endif /* _FIREBALLS_H */
|
||||
@@ -91,7 +91,7 @@ void warpin_queue_render(model_draw_list *scene, object *obj, matrix *orient, ve
|
||||
|
||||
g3_transfer_vertex( &verts[4], &vecs[4] );
|
||||
|
||||
float alpha = (The_mission.flags[Mission::Mission_Flags::Fullneb]) ? (1.0f - neb2_get_fog_intensity(obj)) : 1.0f;
|
||||
float alpha = (The_mission.flags[Mission::Mission_Flags::Fullneb]) ? neb2_get_fog_visibility(&obj->pos, 1.0f) : 1.0f;
|
||||
|
||||
//batch_add_bitmap(warp_glow_bitmap, TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT, &verts[4], 0, r, alpha);
|
||||
batching_add_bitmap(warp_glow_bitmap, &verts[4], 0, r, alpha);
|
||||
|
||||
@@ -112,35 +112,27 @@ static int Current_help_page;
|
||||
|
||||
void gameplay_help_blit_control_line(int x, int y, int id)
|
||||
{
|
||||
int has_key=0, has_joy=0;
|
||||
char buf[256];
|
||||
config_item *ci;
|
||||
|
||||
ci = &Control_config[id];
|
||||
auto ci = &Control_config[id];
|
||||
|
||||
buf[0] = 0;
|
||||
|
||||
if ( ci->key_id >= 0 ) {
|
||||
strcpy_s(buf, textify_scancode(ci->key_id));
|
||||
has_key=1;
|
||||
strcpy_s(buf, ci->first.textify().c_str());
|
||||
|
||||
if (!ci->second.empty()) {
|
||||
strcat_s(buf, XSTR( ", ", 129));
|
||||
strcat_s(buf, ci->second.textify().c_str());
|
||||
}
|
||||
|
||||
if ( ci->joy_id >= 0 ) {
|
||||
if ( has_key ) {
|
||||
strcat_s(buf, XSTR( ", ", 129));
|
||||
}
|
||||
strcat_s(buf, Joy_button_text[ci->joy_id]);
|
||||
has_joy=1;
|
||||
}
|
||||
|
||||
if ( !has_key && !has_joy ) {
|
||||
if (ci->empty()) {
|
||||
strcpy_s(buf, XSTR( "no binding", 130));
|
||||
}
|
||||
|
||||
gr_string(x,y,buf,GR_RESIZE_MENU);
|
||||
|
||||
// gr_string(x+KEY_DESCRIPTION_OFFSET,y,ci->text,GR_RESIZE_MENU);
|
||||
gr_string(x+KEY_DESCRIPTION_OFFSET, y, XSTR(ci->text, CONTROL_CONFIG_XSTR + id), GR_RESIZE_MENU);
|
||||
gr_string(x+KEY_DESCRIPTION_OFFSET, y, XSTR(ci->text.c_str(), CONTROL_CONFIG_XSTR + id), GR_RESIZE_MENU);
|
||||
}
|
||||
|
||||
void gameplay_help_blit_control_line_raw(int x, int y, const char *control_text, const char *control_description)
|
||||
|
||||
@@ -239,7 +239,10 @@ bool GameState_Stack_Valid()
|
||||
|
||||
// returns one of the GS_STATE_ macros
|
||||
int gameseq_get_state(int depth)
|
||||
{
|
||||
{
|
||||
if (!GameState_Stack_Valid())
|
||||
return GS_STATE_INVALID;
|
||||
|
||||
Assert(depth <= gs_current_stack);
|
||||
|
||||
return gs[gs_current_stack - depth].current_state;
|
||||
|
||||
+49
-36
@@ -10,6 +10,7 @@
|
||||
#include <sstream>
|
||||
#include <climits>
|
||||
|
||||
#include "gamesnd.h"
|
||||
#include "gamesnd/gamesnd.h"
|
||||
#include "localization/localize.h"
|
||||
#include "parse/parselo.h"
|
||||
@@ -303,6 +304,10 @@ gamesnd_id gamesnd_get_by_name(const char* name)
|
||||
{
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
|
||||
// empty name is not valid!
|
||||
if (name == nullptr || name[0] == '\0')
|
||||
return gamesnd_id(-1);
|
||||
|
||||
int index = gamesnd_lookup_name(name, Snds);
|
||||
|
||||
if (index < 0)
|
||||
@@ -421,21 +426,32 @@ bool parse_game_sound(const char* tag, gamesnd_id* idx_dest)
|
||||
{
|
||||
if (optional_string(tag))
|
||||
{
|
||||
SCP_string buf;
|
||||
stuff_string(buf, F_NAME);
|
||||
|
||||
*idx_dest = gamesnd_get_by_name(buf.c_str());
|
||||
|
||||
// The special case "-1" is needed to silence warnings where sounds are intentionally removed
|
||||
if (idx_dest->isValid() || buf == "-1")
|
||||
return true;
|
||||
|
||||
error_display(0, "Could not find game sound with name '%s'!", buf.c_str());
|
||||
*idx_dest = parse_game_sound_inline();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses a game sound that should appear at the current parsing location
|
||||
* @return The game sound id or invalid id when parsing fails
|
||||
*/
|
||||
gamesnd_id parse_game_sound_inline()
|
||||
{
|
||||
SCP_string buf;
|
||||
stuff_string(buf, F_NAME);
|
||||
|
||||
auto id = gamesnd_get_by_name(buf.c_str());
|
||||
|
||||
// The special case "-1" is needed to silence warnings where sounds are intentionally removed
|
||||
if (!id.isValid() && buf != "-1") {
|
||||
error_display(0, "Could not find game sound with name '%s'!", buf.c_str());
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a sound. When using this function for a table entry,
|
||||
* required_string and optional_string aren't needed, as this function deals with
|
||||
@@ -510,7 +526,7 @@ void parse_iface_sound_list(const char* tag, SCP_vector<interface_snd_id>& desti
|
||||
//if we're using the old format, double check the size)
|
||||
if(!scp_list && (destination.size() != (unsigned)check))
|
||||
{
|
||||
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.", tag, object_name, destination.size(), check));
|
||||
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.\n", tag, object_name, destination.size(), check));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,12 +544,12 @@ void gamesnd_preload_common_sounds()
|
||||
return;
|
||||
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
|
||||
if ( gs->preload ) {
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
for (auto& gs: Snds) {
|
||||
if ( gs.preload ) {
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
|
||||
game_busy( NOX("** preloading common game sounds **") ); // Animate loading cursor... does nothing if loading screen not active.
|
||||
entry.id = snd_load(&entry, gs->flags);
|
||||
entry.id = snd_load(&entry, &gs.flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,12 +565,12 @@ void gamesnd_load_gameplay_sounds()
|
||||
return;
|
||||
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
|
||||
if ( !gs->preload ) { // don't try to load anything that's already preloaded
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
for (auto& gs: Snds) {
|
||||
if ( !gs.preload ) { // don't try to load anything that's already preloaded
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
if (entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0) {
|
||||
game_busy(NOX("** preloading gameplay sounds **")); // Animate loading cursor... does nothing if loading screen not active.
|
||||
entry.id = snd_load(&entry, gs->flags);
|
||||
entry.id = snd_load(&entry, &gs.flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,8 +583,8 @@ void gamesnd_load_gameplay_sounds()
|
||||
void gamesnd_unload_gameplay_sounds()
|
||||
{
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
for (auto& gs: Snds) {
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
if (entry.id.isValid()) {
|
||||
snd_unload(entry.id);
|
||||
entry.id = sound_load_id::invalid();
|
||||
@@ -586,10 +602,10 @@ void gamesnd_load_interface_sounds()
|
||||
return;
|
||||
|
||||
Assert( Snds_iface.size() < INT_MAX );
|
||||
for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
|
||||
for (auto& entry : si->sound_entries) {
|
||||
for (auto& gs: Snds) {
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
|
||||
entry.id = snd_load(&entry, si->flags);
|
||||
entry.id = snd_load(&entry, &gs.flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,11 +679,9 @@ void parse_gamesnd_old(game_snd* gs)
|
||||
{
|
||||
int temp_min, temp_max;
|
||||
|
||||
ignore_gray_space();
|
||||
if (stuff_int_optional(&temp_min, true) == 2)
|
||||
if (stuff_int_optional(&temp_min) == 2)
|
||||
{
|
||||
ignore_gray_space();
|
||||
if (stuff_int_optional(&temp_max, true) == 2)
|
||||
if (stuff_int_optional(&temp_max) == 2)
|
||||
{
|
||||
mprintf(("Dutifully converting retail sound %s, '%s' to a 3D sound...\n", gs->name.c_str(), entry.filename));
|
||||
is_3d = 1;
|
||||
@@ -681,8 +695,7 @@ void parse_gamesnd_old(game_snd* gs)
|
||||
}
|
||||
|
||||
// check for extra values per Mantis #2408
|
||||
ignore_gray_space();
|
||||
if (stuff_int_optional(&temp, true) == 2)
|
||||
if (stuff_int_optional(&temp) == 2)
|
||||
{
|
||||
Warning(LOCATION, "Unexpected extra value %d found for sound '%s' (filename '%s')! Check the format of the sounds.tbl (or .tbm) entry.", temp, gs->name.c_str(), entry.filename);
|
||||
}
|
||||
@@ -738,14 +751,14 @@ bool required_string_no_create(const char* token, bool no_create)
|
||||
|
||||
static GameSoundCycleType parse_cycle_type() {
|
||||
if (optional_string("Sequential")) {
|
||||
return GameSoundCycleType::Sequential;
|
||||
return GameSoundCycleType::SequentialCycle;
|
||||
} else if (optional_string("Random")) {
|
||||
return GameSoundCycleType::Random;
|
||||
return GameSoundCycleType::RandomCycle;
|
||||
} else {
|
||||
error_display(0, "Failed to parse sound cycle type. Expected 'sequential' or 'random'. Got [%.32s]", next_tokens());
|
||||
// Ignore everything until the end of the line. That should hopefully skip the bad token.
|
||||
advance_to_eoln(nullptr);
|
||||
return GameSoundCycleType::Sequential;
|
||||
return GameSoundCycleType::SequentialCycle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1336,7 +1349,7 @@ float gamesnd_get_max_duration(game_snd* gs) {
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
if (!entry.id.isValid()) {
|
||||
// Lazily load unloaded sound entries when required
|
||||
entry.id = snd_load(&entry, gs->flags);
|
||||
entry.id = snd_load(&entry, &gs->flags);
|
||||
}
|
||||
|
||||
max_length = std::max(max_length, snd_get_duration(entry.id));
|
||||
@@ -1349,14 +1362,14 @@ game_snd_entry* gamesnd_choose_entry(game_snd* gs) {
|
||||
|
||||
size_t index = 0;
|
||||
switch(gs->cycle_type) {
|
||||
case GameSoundCycleType::Random:
|
||||
case GameSoundCycleType::RandomCycle:
|
||||
if (gs->sound_entries.size() == 1) {
|
||||
index = 0;
|
||||
} else {
|
||||
index = util::UniformRange<size_t>(0, gs->sound_entries.size() - 1).next();
|
||||
}
|
||||
break;
|
||||
case GameSoundCycleType::Sequential:
|
||||
case GameSoundCycleType::SequentialCycle:
|
||||
if (gs->last_entry_index == std::numeric_limits<size_t>::max()) {
|
||||
// If this is the first time we must return the first sound to keep everything consistent
|
||||
index = 0;
|
||||
|
||||
@@ -372,6 +372,8 @@ interface_snd_id gamesnd_get_by_iface_tbl_index(int index);
|
||||
// Does all parsing for a sound. Returns true if a sound was successfully parsed.
|
||||
bool parse_game_sound(const char* tag, gamesnd_id* idx_dest);
|
||||
|
||||
gamesnd_id parse_game_sound_inline();
|
||||
|
||||
void parse_iface_sound(const char* tag, interface_snd_id* idx_dest);
|
||||
void parse_iface_sound_list(const char* tag, SCP_vector<interface_snd_id>& destination, const char* object_name, bool scp_list = false);
|
||||
|
||||
|
||||
Loaded 100 of 569 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user