mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 17:48:46 -10:00
Compare commits
675
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a97db52bf6 | ||
|
|
5a93ecace2 | ||
|
|
1caf89a034 | ||
|
|
995299ffac | ||
|
|
58682b8bbf | ||
|
|
9e4fe90d6d | ||
|
|
5ccd4fb9ee | ||
|
|
f4e7bef89b | ||
|
|
0dab72c27a | ||
|
|
241c46fe3f | ||
|
|
49544a598e | ||
|
|
93a608bd79 | ||
|
|
ce227f72d8 | ||
|
|
e76875f39c | ||
|
|
e43b81b669 | ||
|
|
1a7e8283df | ||
|
|
9d0bbeed53 | ||
|
|
8bfed9e612 | ||
|
|
a68cf42299 | ||
|
|
f8dff7f419 | ||
|
|
da18a2cea4 | ||
|
|
e17b2fd0b6 | ||
|
|
5978f99ad9 | ||
|
|
151a6f6d66 | ||
|
|
c84f0b0b69 | ||
|
|
182f10f94f | ||
|
|
a9a44162d7 | ||
|
|
a3f5b086fe | ||
|
|
4a4d997285 | ||
|
|
157cf0ad52 | ||
|
|
268fcad1a3 | ||
|
|
43a9154dab | ||
|
|
7528756867 | ||
|
|
946c03c825 | ||
|
|
74cea66877 | ||
|
|
5c192bb645 | ||
|
|
3f1163c55c | ||
|
|
143cf3d36d | ||
|
|
05865b8d13 | ||
|
|
a158c829e9 | ||
|
|
9f8602ea4d | ||
|
|
ff802cc75a | ||
|
|
cd9e61396d | ||
|
|
914c66fea9 | ||
|
|
5b54ddfd53 | ||
|
|
931f6118fe | ||
|
|
20d86c6079 | ||
|
|
5eea353952 | ||
|
|
319e573637 | ||
|
|
83f08291af | ||
|
|
2813ceb5e9 | ||
|
|
a2c05009dc | ||
|
|
2af0aefd91 | ||
|
|
d9a6330d9e | ||
|
|
634eece877 | ||
|
|
e5c4c58eba | ||
|
|
e567ae7c81 | ||
|
|
46d3e490bb | ||
|
|
4dfdddf4d2 | ||
|
|
1da50c738f | ||
|
|
a8f4be1d34 | ||
|
|
306f34a574 | ||
|
|
fb52a8861e | ||
|
|
587e6f4c21 | ||
|
|
06d1514a47 | ||
|
|
f483a92329 | ||
|
|
db69cb8395 | ||
|
|
e803d8a0e9 | ||
|
|
761b97529a | ||
|
|
1cd45aa1b4 | ||
|
|
5f110962e3 | ||
|
|
0dea8d2904 | ||
|
|
381eda672b | ||
|
|
cbf2693d33 | ||
|
|
6c96d12133 | ||
|
|
cb3819309b | ||
|
|
8fb0cbe08c | ||
|
|
ce0fe088eb | ||
|
|
86ef459cf6 | ||
|
|
cb19fecc36 | ||
|
|
af7bb154d3 | ||
|
|
937091ee77 | ||
|
|
dd0add946b | ||
|
|
2e4d97ce4e | ||
|
|
88c3cea753 | ||
|
|
4d184d501a | ||
|
|
702e8f430d | ||
|
|
5941d5ed8b | ||
|
|
879b1f43ad | ||
|
|
fff480e101 | ||
|
|
406f8e58ca | ||
|
|
2af19b6605 | ||
|
|
59e8742c84 | ||
|
|
849a4f6d4e | ||
|
|
8c4849aaee | ||
|
|
a06da31c6a | ||
|
|
77c7569f68 | ||
|
|
fb229ccbb1 | ||
|
|
be2045013b | ||
|
|
5659243766 | ||
|
|
6d03ce1c13 | ||
|
|
cb3a17a404 | ||
|
|
f4a3a885dd | ||
|
|
1fe175b523 | ||
|
|
2ceb523954 | ||
|
|
349b353bbe | ||
|
|
c0cc43f558 | ||
|
|
c3980b7aa6 | ||
|
|
5d78e3ee63 | ||
|
|
38acf9ae15 | ||
|
|
10cdf2d367 | ||
|
|
d84600356a | ||
|
|
a0b4ae1a2e | ||
|
|
6a9b6a38e9 | ||
|
|
ea985b6455 | ||
|
|
6b5f9c9be7 | ||
|
|
eca894e732 | ||
|
|
beaeafeb97 | ||
|
|
d77f413f39 | ||
|
|
a243052f4c | ||
|
|
05bf43bfcc | ||
|
|
1b8f920529 | ||
|
|
ea1a3707ad | ||
|
|
cd2ff46541 | ||
|
|
17feb28d29 | ||
|
|
897429e239 | ||
|
|
712491696f | ||
|
|
b8fa52097c | ||
|
|
7eff7900a6 | ||
|
|
2808606b84 | ||
|
|
84d3cdf847 | ||
|
|
b1e4bd7ef8 | ||
|
|
f087c8adbb | ||
|
|
8a8d7222f2 | ||
|
|
9c353e71da | ||
|
|
5a3cfa5f19 | ||
|
|
afcb4871a3 | ||
|
|
2589d8c542 | ||
|
|
2374da528f | ||
|
|
3d8174d0d8 | ||
|
|
dc50aaa86b | ||
|
|
2c04313c59 | ||
|
|
e6e551121f | ||
|
|
aadf1c5f8b | ||
|
|
6cb49fa245 | ||
|
|
d0737926bd | ||
|
|
cfc1088ed0 | ||
|
|
0d4c222a97 | ||
|
|
22b5f8148d | ||
|
|
e3ff08ea46 | ||
|
|
e6da70a0ff | ||
|
|
ac03c1a612 | ||
|
|
5903dc7e9d | ||
|
|
a5dc72c56c | ||
|
|
c1444781e8 | ||
|
|
300cd8a1cf | ||
|
|
60b8876eb0 | ||
|
|
3c939f5cc4 | ||
|
|
db4f9e4370 | ||
|
|
fa250c5ff6 | ||
|
|
0c736b7bca | ||
|
|
440c6fda4c | ||
|
|
aad6c84684 | ||
|
|
7f8674c532 | ||
|
|
40b4294b13 | ||
|
|
54ee4a30d6 | ||
|
|
a3691ed481 | ||
|
|
973bd5570e | ||
|
|
15445408f9 | ||
|
|
337b0402ce | ||
|
|
8ee96781b3 | ||
|
|
58ff297495 | ||
|
|
7875e5f7c7 | ||
|
|
8057b52a04 | ||
|
|
ce3597f5a6 | ||
|
|
d42789decd | ||
|
|
a32c3187de | ||
|
|
1e7431a044 | ||
|
|
b0166dd6a6 | ||
|
|
a023a1a82f | ||
|
|
bf51f8ee91 | ||
|
|
f89cd69ad9 | ||
|
|
9d3ba25606 | ||
|
|
0ee8c4b01a | ||
|
|
fc102056b8 | ||
|
|
877373f257 | ||
|
|
31d73b7ed5 | ||
|
|
e4bba0c7f2 | ||
|
|
0d0574df0f | ||
|
|
f2bc71c13a | ||
|
|
f88e5c475e | ||
|
|
5848df31dd | ||
|
|
2820532ab3 | ||
|
|
32084cfe59 | ||
|
|
7a75aaa172 | ||
|
|
c50cffae17 | ||
|
|
b7aa53dd79 | ||
|
|
90e1f18fa2 | ||
|
|
19a6a1aa97 | ||
|
|
730b294d7b | ||
|
|
42eae002d2 | ||
|
|
b3db62cf25 | ||
|
|
689d184869 | ||
|
|
772920b727 | ||
|
|
2b2320ffe7 | ||
|
|
b9229cd713 | ||
|
|
886b54c298 | ||
|
|
ca4540a821 | ||
|
|
0f73a59155 | ||
|
|
1d1ed1905b | ||
|
|
917b4547ec | ||
|
|
614f510d0a | ||
|
|
aa7fc6dafd | ||
|
|
cb4e132df2 | ||
|
|
fe17997ca3 | ||
|
|
f691966675 | ||
|
|
a531fd9fed | ||
|
|
9c65e1c9b5 | ||
|
|
69dfe8194d | ||
|
|
2d37a24b4d | ||
|
|
b970e22410 | ||
|
|
289de86eb9 | ||
|
|
1d561a1f1e | ||
|
|
1310c38642 | ||
|
|
b090e5d09c | ||
|
|
e9311ec828 | ||
|
|
eaafd8cb13 | ||
|
|
5f899634c2 | ||
|
|
7918797eec | ||
|
|
d279e477b3 | ||
|
|
d2f842494d | ||
|
|
a6f9337d5a | ||
|
|
1a1d3f040c | ||
|
|
109c2b5a81 | ||
|
|
c2b92410aa | ||
|
|
16926a4395 | ||
|
|
76440ddfd1 | ||
|
|
08a73c1ed7 | ||
|
|
9f30d3001c | ||
|
|
6c101fe52a | ||
|
|
bc083dc61b | ||
|
|
9755d44c5f | ||
|
|
9502af6f20 | ||
|
|
5eb80f55bc | ||
|
|
4b9ccfc68f | ||
|
|
9db567ea40 | ||
|
|
7d8b437847 | ||
|
|
e9d4f322d0 | ||
|
|
080aa65ec8 | ||
|
|
1207dfcae9 | ||
|
|
8db0a44d7d | ||
|
|
9ba88ca163 | ||
|
|
c451a82f87 | ||
|
|
eb36b56f1d | ||
|
|
c39d6b290f | ||
|
|
56a3ddd5d3 | ||
|
|
89219df6af | ||
|
|
fa80fc3278 | ||
|
|
84897636cd | ||
|
|
9e618f6632 | ||
|
|
43e85dd8fd | ||
|
|
a12c1de012 | ||
|
|
b1247042ac | ||
|
|
3725f27a5c | ||
|
|
55efbe73b3 | ||
|
|
40b9924892 | ||
|
|
c21cbb3067 | ||
|
|
7b9b237a66 | ||
|
|
f15a6bc3e8 | ||
|
|
3a7269275e | ||
|
|
81656e8dff | ||
|
|
7c2deb6761 | ||
|
|
552817b8e1 | ||
|
|
555a413d7e | ||
|
|
0241c9f4b9 | ||
|
|
c93c91013c | ||
|
|
81d08cd6f3 | ||
|
|
4efcd82940 | ||
|
|
a4014ea985 | ||
|
|
83dee2eab4 | ||
|
|
39f80289c2 | ||
|
|
cbd491e277 | ||
|
|
1a60eff319 | ||
|
|
7713e1b09b | ||
|
|
c85876d0d3 | ||
|
|
6cadc350b1 | ||
|
|
806bb77298 | ||
|
|
e5fccc0b19 | ||
|
|
0bf632ceae | ||
|
|
bd9ea61e9d | ||
|
|
836d7d4f2d | ||
|
|
5ea4a4c014 | ||
|
|
dd71838715 | ||
|
|
b16854796a | ||
|
|
1142e7d9bc | ||
|
|
212c91c72f | ||
|
|
4e75114479 | ||
|
|
e1ae938553 | ||
|
|
af833f059c | ||
|
|
d9b7710f92 | ||
|
|
fc7389f95a | ||
|
|
908f306985 | ||
|
|
21c9b65c99 | ||
|
|
f92bc15fc6 | ||
|
|
6539fc9aeb | ||
|
|
a0730f601c | ||
|
|
59a362ed1d | ||
|
|
dadb91889f | ||
|
|
41dc187456 | ||
|
|
7780769ba5 | ||
|
|
c535108708 | ||
|
|
dde16a54c6 | ||
|
|
09b6888bc6 | ||
|
|
be4d4eb1d5 | ||
|
|
f0ecd88da9 | ||
|
|
6d9e590996 | ||
|
|
fe3dce092a | ||
|
|
0b6150e961 | ||
|
|
ebe2d235da | ||
|
|
9ecc158aee | ||
|
|
4d8dbcb457 | ||
|
|
966ac4d878 | ||
|
|
1ffcaafc89 | ||
|
|
06f576bfa6 | ||
|
|
d0c5fe6bcc | ||
|
|
f903180411 | ||
|
|
726c954e37 | ||
|
|
1a72471543 | ||
|
|
a7ec8d958b | ||
|
|
c1c16c2fcf | ||
|
|
ec3fc3477d | ||
|
|
3524679c9e | ||
|
|
cf4f1b2ac6 | ||
|
|
95fea193ae | ||
|
|
8bf7f66483 | ||
|
|
38f7479500 | ||
|
|
f5b2b1bbd0 | ||
|
|
1a1d4bb057 | ||
|
|
858c116b3e | ||
|
|
b8b24a67e0 | ||
|
|
df0279869c | ||
|
|
85b37e098e | ||
|
|
515beef61a | ||
|
|
7281f71da4 | ||
|
|
7c29656c7c | ||
|
|
3900faac87 | ||
|
|
5cd3b3a308 | ||
|
|
75b61efbd1 | ||
|
|
b8c3c560d2 | ||
|
|
94744e9d69 | ||
|
|
04c2913f07 | ||
|
|
75a7fc5755 | ||
|
|
6132f8bfc1 | ||
|
|
32183f5e71 | ||
|
|
3f0c33046c | ||
|
|
1c482d76df | ||
|
|
e5a5c30fbf | ||
|
|
4ddcaec027 | ||
|
|
a68b22a9d8 | ||
|
|
d7a531d449 | ||
|
|
8d2895d3c2 | ||
|
|
255f3773c1 | ||
|
|
8748109eaf | ||
|
|
6abfddfea5 | ||
|
|
32905ac841 | ||
|
|
671a7586e7 | ||
|
|
9718e7008c | ||
|
|
ab438116d3 | ||
|
|
dce9e1c031 | ||
|
|
b5f3edc019 | ||
|
|
e28b3bb592 | ||
|
|
0a7fac447d | ||
|
|
4c7e4feaf3 | ||
|
|
bf84dbf34f | ||
|
|
b19c3e565b | ||
|
|
ffcf078f03 | ||
|
|
e52fa1edeb | ||
|
|
44952eb2d6 | ||
|
|
95d21a4085 | ||
|
|
8f835fe34b | ||
|
|
c26b0caeb8 | ||
|
|
85ebd316e1 | ||
|
|
abc82bd323 | ||
|
|
f374eadb45 | ||
|
|
3e77e1370d | ||
|
|
2ce4bdd55c | ||
|
|
bbb838a332 | ||
|
|
7a655e44da | ||
|
|
2ed608b303 | ||
|
|
b49dbfeaa4 | ||
|
|
bfacc16a51 | ||
|
|
f96c73faac | ||
|
|
5fd06b40fe | ||
|
|
21e35d0ded | ||
|
|
9dc6eb7248 | ||
|
|
fcb257db59 | ||
|
|
fa5ee72770 | ||
|
|
72ed863700 | ||
|
|
d6cb9892de | ||
|
|
021eb8e49a | ||
|
|
487cac6456 | ||
|
|
f76bf1b8c4 | ||
|
|
755c4c4e29 | ||
|
|
168b3310ab | ||
|
|
365d9c57e3 | ||
|
|
1d5fa5eef1 | ||
|
|
05624b5092 | ||
|
|
8be619db7e | ||
|
|
9b82daeefc | ||
|
|
c53daa325c | ||
|
|
86622e1ce0 | ||
|
|
199c7b79ef | ||
|
|
26e9ecab0e | ||
|
|
d746ae4f1a | ||
|
|
5fbce060e6 | ||
|
|
cb20a05967 | ||
|
|
07db2aabf5 | ||
|
|
be46706081 | ||
|
|
83282b6427 | ||
|
|
a4eeaa706e | ||
|
|
a224736543 | ||
|
|
d055d03e53 | ||
|
|
4c80ad7243 | ||
|
|
53394093a4 | ||
|
|
ef631f9fe0 | ||
|
|
d58d924f3e | ||
|
|
8375b33998 | ||
|
|
3be99b2480 | ||
|
|
7811207ca9 | ||
|
|
dd72784f28 | ||
|
|
22feeddc6f | ||
|
|
6c35dac535 | ||
|
|
8e64845cf1 | ||
|
|
bdde909fa6 | ||
|
|
6921f550c1 | ||
|
|
06eaf94cb3 | ||
|
|
d7fdf7b079 | ||
|
|
b9b64fe49d | ||
|
|
218d8c09be | ||
|
|
bda54ffe83 | ||
|
|
b03301897e | ||
|
|
32fcb8ccba | ||
|
|
86851fe0d2 | ||
|
|
a565c8e76d | ||
|
|
44ed242aee | ||
|
|
145a34eeac | ||
|
|
1e27a6bed3 | ||
|
|
84905f0789 | ||
|
|
a52f31f748 | ||
|
|
62c8260d26 | ||
|
|
82dfdbc962 | ||
|
|
220ff6752b | ||
|
|
bd767ece09 | ||
|
|
f60fe6dfc2 | ||
|
|
342958b99f | ||
|
|
497b834766 | ||
|
|
8989b6e925 | ||
|
|
650b8d4f07 | ||
|
|
672aa7f81f | ||
|
|
d99c4ca172 | ||
|
|
fe1540c8a4 | ||
|
|
df1126923d | ||
|
|
8dc03b0fff | ||
|
|
1b75fea06c | ||
|
|
87021dd1d2 | ||
|
|
3a380a05c9 | ||
|
|
2e7f37ef7a | ||
|
|
dbe0af1a24 | ||
|
|
f95ae0b1bd | ||
|
|
94dac6b3d2 | ||
|
|
11a33fe42e | ||
|
|
821e48d188 | ||
|
|
c68f8d71e4 | ||
|
|
5380d88680 | ||
|
|
3605f66e6d | ||
|
|
329974b4bf | ||
|
|
06227bbc80 | ||
|
|
a918ef0b6f | ||
|
|
9e731279eb | ||
|
|
742dd074ee | ||
|
|
f1a25ab47d | ||
|
|
d3db542640 | ||
|
|
74beab5177 | ||
|
|
2a62f04d72 | ||
|
|
acf367e66a | ||
|
|
c5d5a93766 | ||
|
|
f0ee1e2f7c | ||
|
|
47025a79d7 | ||
|
|
305af2a1ce | ||
|
|
5732183471 | ||
|
|
611de4d060 | ||
|
|
8270a38505 | ||
|
|
73e82d9440 | ||
|
|
6a1ae81bcb | ||
|
|
035334878b | ||
|
|
54c00ce205 | ||
|
|
a9664ff22f | ||
|
|
b5f4942cd3 | ||
|
|
9141f1e9ed | ||
|
|
26477276da | ||
|
|
821d16192f | ||
|
|
5864ec4e7e | ||
|
|
c31c258679 | ||
|
|
c307ef7600 | ||
|
|
2701544822 | ||
|
|
4ad015881d | ||
|
|
43b36fad20 | ||
|
|
3dd2c38cef | ||
|
|
cfd3b716ba | ||
|
|
d76f38320a | ||
|
|
da58556787 | ||
|
|
78d6a4b881 | ||
|
|
1180f8762a | ||
|
|
f342925763 | ||
|
|
2ec1553898 | ||
|
|
bfb8c70831 | ||
|
|
e8bb54c36d | ||
|
|
6115a34d13 | ||
|
|
cc7f067e0f | ||
|
|
939d6a50c2 | ||
|
|
3783cb0d1d | ||
|
|
23254c993e | ||
|
|
29fa8c4f9e | ||
|
|
ab7aa63569 | ||
|
|
77062e2571 | ||
|
|
7229fdc423 | ||
|
|
d8c7a63af7 | ||
|
|
2cf24cb27b | ||
|
|
768f88b135 | ||
|
|
d1829a9f10 | ||
|
|
860e28fbdb | ||
|
|
07a389a5e8 | ||
|
|
87367d6b84 | ||
|
|
ad7e713692 | ||
|
|
e5e2729157 | ||
|
|
039866d526 | ||
|
|
d432f9b1e9 | ||
|
|
c51bc08d1d | ||
|
|
3be4d0b15a | ||
|
|
7da79c174b | ||
|
|
62a43512fd | ||
|
|
492683db2a | ||
|
|
c53e6b1487 | ||
|
|
e7f6fa3c91 | ||
|
|
455d2c9681 | ||
|
|
b053131e71 | ||
|
|
e502cdfe76 | ||
|
|
e855fc81e9 | ||
|
|
df77615473 | ||
|
|
20f992fe54 | ||
|
|
d557a6eff4 | ||
|
|
e66803af09 | ||
|
|
cd3464d331 | ||
|
|
bc5f986d76 | ||
|
|
621030a720 | ||
|
|
9b29e754ce | ||
|
|
e4886db4d2 | ||
|
|
b9b36d2a76 | ||
|
|
87fd99f9f6 | ||
|
|
03083d1a91 | ||
|
|
3120d6dda1 | ||
|
|
8dde6d47cb | ||
|
|
98e78ec086 | ||
|
|
e1b9832faf | ||
|
|
c9e11d4657 | ||
|
|
d7c4c544e6 | ||
|
|
50762246cb | ||
|
|
d04ce617fe | ||
|
|
8340427388 | ||
|
|
eda645dcfc | ||
|
|
72b13631c8 | ||
|
|
824b5bbb37 | ||
|
|
2a8ec9cc45 | ||
|
|
237bd53b6d | ||
|
|
ba263d6889 | ||
|
|
d333694a09 | ||
|
|
cf356ed3df | ||
|
|
4677fb8801 | ||
|
|
5ee211203a | ||
|
|
898f8a9963 | ||
|
|
c5b033235a | ||
|
|
f1dd86477a | ||
|
|
a8cc7d2896 | ||
|
|
e902f6d075 | ||
|
|
0c2d1a7a10 | ||
|
|
c984faeda1 | ||
|
|
dac5fbeb82 | ||
|
|
94dbd95530 | ||
|
|
6e6b4098f3 | ||
|
|
fc11ddcd45 | ||
|
|
ec4e50481b | ||
|
|
d143add82d | ||
|
|
ed3fcc8aca | ||
|
|
c24457ff6d | ||
|
|
4bb8456f69 | ||
|
|
5503aec7b4 | ||
|
|
4324b41109 | ||
|
|
6134a42ef0 | ||
|
|
2a4894d696 | ||
|
|
55380f7e8f | ||
|
|
3a5a2ab43c | ||
|
|
4f0f57e711 | ||
|
|
8801672301 | ||
|
|
a18ac34c03 | ||
|
|
dd44d7d13a | ||
|
|
84cad8c188 | ||
|
|
9dfc6d7c68 | ||
|
|
3c7639a9e3 | ||
|
|
7f1e72ecca | ||
|
|
d147330739 | ||
|
|
beaadbbc16 | ||
|
|
f38dbae310 | ||
|
|
7f0666cc33 | ||
|
|
91723be9c5 | ||
|
|
4e669f9ce4 | ||
|
|
209e905a05 | ||
|
|
a82db02b41 | ||
|
|
22663cb297 | ||
|
|
3698cbe09f | ||
|
|
29754cafc2 | ||
|
|
474527ef83 | ||
|
|
ce32ef3022 | ||
|
|
792cf0a0ed | ||
|
|
314ba92782 | ||
|
|
6264656097 | ||
|
|
6bd3b43cd8 | ||
|
|
3bec6d8cfc | ||
|
|
72271be1f9 | ||
|
|
bb838ea4dd | ||
|
|
ad74c21b82 | ||
|
|
e7b0f61870 | ||
|
|
7c37628e1a | ||
|
|
2a337133cb | ||
|
|
a06cb0c688 | ||
|
|
e4254636dd | ||
|
|
f61649e911 | ||
|
|
83fde5acfc | ||
|
|
7d7b6eeeb8 | ||
|
|
94057c5ef2 | ||
|
|
6fe5ab7dc7 | ||
|
|
a57949c747 | ||
|
|
24310690f6 | ||
|
|
bd75eff4e4 | ||
|
|
364e39cedf | ||
|
|
e1662d8501 | ||
|
|
7938169730 | ||
|
|
3d5c43e45d | ||
|
|
1581d9dc1f | ||
|
|
d4f8706208 | ||
|
|
8d40952bf5 | ||
|
|
4cac920943 | ||
|
|
2ad0571b22 | ||
|
|
6e10ee64f8 | ||
|
|
c9f4982d08 | ||
|
|
bc059bdb86 | ||
|
|
e60cf65036 | ||
|
|
2934e99ddc | ||
|
|
11fac90617 | ||
|
|
9ef56797fe | ||
|
|
e1ed5fdd78 | ||
|
|
a125b8b3ce | ||
|
|
de54dea61a | ||
|
|
d665c618c1 | ||
|
|
fa27b8d133 | ||
|
|
aea39664ba | ||
|
|
5575ab4c95 | ||
|
|
2827e93b2d | ||
|
|
cad4f6980d | ||
|
|
286a96e2c3 | ||
|
|
7c15adec1a | ||
|
|
52cc1f182e | ||
|
|
64b54d1022 | ||
|
|
5d0ee564ac | ||
|
|
e7583ddab8 | ||
|
|
98a5ff4a23 |
No files matched your search
@@ -48,5 +48,7 @@ CheckOptions:
|
||||
value: 'std::vector;std::deque;std::list;SCP_vector;SCP_deque;SCP_list'
|
||||
- key: 'readability-braces-around-statements.ShortStatementLines'
|
||||
value: '4' # Avoid flagging simple if (...) return false; statements
|
||||
- key: 'performance-move-const-arg.CheckTriviallyCopyableMove' # This isn't actually performance relevant, but using move on trivially copyable types can well indicate that the variable should not be used anymore, such as when passing a builder type to the finialization function
|
||||
value: 'false'
|
||||
...
|
||||
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
arch: [Win32, x64]
|
||||
simd: [SSE2]
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
@@ -155,13 +155,13 @@ jobs:
|
||||
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
|
||||
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
|
||||
-DFSO_INSTALL_DEBUG_FILES="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -A "$ARCHITECTURE" \
|
||||
-G "Visual Studio 16 2019" -T "v142" ..
|
||||
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
else
|
||||
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
|
||||
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
|
||||
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
|
||||
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
|
||||
-G "Visual Studio 16 2019" -T "v142" ..
|
||||
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
fi
|
||||
- name: Compile
|
||||
working-directory: ./build
|
||||
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
simd: [SSE2, AVX]
|
||||
needs: create_release
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
# Checkout repo
|
||||
@@ -211,13 +211,13 @@ jobs:
|
||||
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
|
||||
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
|
||||
-DFSO_INSTALL_DEBUG_FILES="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -A "$ARCHITECTURE" \
|
||||
-G "Visual Studio 16 2019" -T "v142" ..
|
||||
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
else
|
||||
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
|
||||
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
|
||||
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
|
||||
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
|
||||
-G "Visual Studio 16 2019" -T "v142" ..
|
||||
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
fi
|
||||
- name: Compile
|
||||
working-directory: ./build
|
||||
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
arch: [Win32, x64]
|
||||
simd: [SSE2]
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
@@ -153,13 +153,13 @@ jobs:
|
||||
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
|
||||
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
|
||||
-DFSO_INSTALL_DEBUG_FILES="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -A "$ARCHITECTURE" \
|
||||
-G "Visual Studio 16 2019" -T "v142" ..
|
||||
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
else
|
||||
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
|
||||
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
|
||||
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
|
||||
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
|
||||
-G "Visual Studio 16 2019" -T "v142" ..
|
||||
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
fi
|
||||
- name: Compile
|
||||
working-directory: ./build
|
||||
|
||||
@@ -76,11 +76,11 @@ jobs:
|
||||
build_windows:
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Debug, Release]
|
||||
configuration: [Debug, FastDebug, Release]
|
||||
compiler: [MSVC]
|
||||
arch: [Win32, x64]
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Prepare Environment
|
||||
run: choco install ninja
|
||||
@@ -139,12 +139,12 @@ jobs:
|
||||
else
|
||||
if [ "$ARCHITECTURE" = "Win32" ]; then
|
||||
cmake -DFSO_USE_SPEECH="ON" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
|
||||
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -G "Visual Studio 16 2019" \
|
||||
-DFSO_BUILD_QTFRED=OFF -T "v142" -A "$ARCHITECTURE" ..
|
||||
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -G "Visual Studio 17 2022" \
|
||||
-DFSO_BUILD_QTFRED=OFF -T "v143" -A "$ARCHITECTURE" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
else
|
||||
cmake -DFSO_USE_SPEECH="ON" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
|
||||
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -G "Visual Studio 16 2019" \
|
||||
-DFSO_BUILD_QTFRED=OFF -T "v142" -A "$ARCHITECTURE" ..
|
||||
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -G "Visual Studio 17 2022" \
|
||||
-DFSO_BUILD_QTFRED=OFF -T "v143" -A "$ARCHITECTURE" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
|
||||
fi
|
||||
fi
|
||||
- name: Compile
|
||||
|
||||
@@ -42,6 +42,7 @@ _ReSharper*/
|
||||
|
||||
#Project files
|
||||
[Bb]uild/
|
||||
[Oo]ut/
|
||||
|
||||
#Subversion files
|
||||
.svn
|
||||
@@ -259,3 +260,5 @@ CMakePresets.json
|
||||
!/code/scripting/lua/LuaConvert.h
|
||||
!/code/debugconsole/
|
||||
!/docker/build
|
||||
CMakeFiles/cmake.check_cache
|
||||
CMakeCache.txt
|
||||
+8
-9
@@ -1,5 +1,5 @@
|
||||
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.9)
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.21)
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
# Make sure the source and binary directory are not the sample
|
||||
@@ -8,12 +8,12 @@ if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
include(cmake/util.cmake)
|
||||
include(cmake/embed_file.cmake)
|
||||
|
||||
# Set a few policies
|
||||
set_policy(CMP0051 NEW)
|
||||
set_policy(CMP0053 NEW)
|
||||
set_policy(CMP0054 NEW)
|
||||
set_policy(CMP0091 NEW) # Enable use of CMAKE_MSVC_RUNTIME_LIBRARY
|
||||
|
||||
SET(FSO_PROJECT_DESCRIPTION "Open source project based on the original FreeSpace 2 source code.")
|
||||
|
||||
@@ -57,12 +57,16 @@ LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/finder")
|
||||
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/rpavlik-cmake-modules")
|
||||
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/cotire")
|
||||
|
||||
INCLUDE(globals)
|
||||
INCLUDE(toolchain)
|
||||
include(embed_file)
|
||||
|
||||
IF(RESET_INSTALL_PREFIX)
|
||||
IF(NOT $ENV{FS2PATH} STREQUAL "")
|
||||
IF(NOT IS_DIRECTORY $ENV{FS2PATH})
|
||||
MESSAGE(WARNING "The path '$ENV{FS2PATH}' of the environment variable FS2PATH does not point to a directory! Install prefix wasn't set.")
|
||||
ELSE(NOT IS_DIRECTORY $ENV{FS2PATH})
|
||||
SET(CMAKE_INSTALL_PREFIX "$ENV{FS2PATH}" CACHE FILEPATH "Install path" FORCE)
|
||||
SET(CMAKE_INSTALL_PREFIX "$ENV{FS2PATH}" CACHE PATH "Install path" FORCE)
|
||||
FILE(TO_CMAKE_PATH ${CMAKE_INSTALL_PREFIX} CMAKE_INSTALL_PREFIX)
|
||||
ENDIF(NOT IS_DIRECTORY $ENV{FS2PATH})
|
||||
ELSE(NOT $ENV{FS2PATH} STREQUAL "")
|
||||
@@ -105,7 +109,7 @@ OPTION(FSO_USE_OPENALSOFT "Download and build OpenAL Soft instead of using the s
|
||||
|
||||
OPTION(FSO_FATAL_WARNINGS "Determines if warnings in the build are considered fatal errors, primarily used for CI" OFF)
|
||||
|
||||
SET(FSO_FREESPACE_PATH "${CMAKE_INSTALL_PREFIX}" CACHE FILEPATH "The path of the FreeSpace directory you want to use. Please note that you will have to change CMAKE_INSTALL_PREFIX if you change this at some point.")
|
||||
SET(FSO_FREESPACE_PATH "${CMAKE_INSTALL_PREFIX}" CACHE PATH "The path of the FreeSpace directory you want to use. Please note that you will have to change CMAKE_INSTALL_PREFIX if you change this at some point.")
|
||||
|
||||
SET(FSO_RUN_ARGUMENTS "" CACHE STRING "Additional arguments passed to a generated executable when run with the generated build files.")
|
||||
|
||||
@@ -156,11 +160,6 @@ mark_as_advanced(FORCE FSO_BUILD_WITH_VULKAN)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR_DEBUG)
|
||||
|
||||
# Include cotire file from https://github.com/sakra/cotire/
|
||||
include(cotire)
|
||||
|
||||
INCLUDE(globals)
|
||||
INCLUDE(toolchain)
|
||||
INCLUDE(platforms)
|
||||
include(version)
|
||||
|
||||
|
||||
@@ -26,5 +26,5 @@ git diff -U0 --no-color "$BASE_COMMIT..$2" | \
|
||||
-extra-arg="-DWITH_VULKAN" \
|
||||
-extra-arg="-DVULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1" \
|
||||
-extra-arg="-DVK_NO_PROTOTYPES" \
|
||||
-regex '(code(?!((\/graphics\/shaders\/compiled)|(\/globalincs\/windebug)))|freespace2|qtfred|test|build|tools)\/.*\.(cpp|h)' \
|
||||
-regex '(code(?!((\/graphics\/shaders\/compiled)|(\/globalincs\/windebug)|(\/def_files\/data)))|freespace2|qtfred|test\/src|build|tools)\/.*\.(cpp|h)' \
|
||||
-clang-tidy-binary /usr/bin/clang-tidy-16 -j$(nproc) -export-fixes "$(pwd)/clang-fixes.yaml"
|
||||
@@ -42,7 +42,7 @@ export CXXFLAGS
|
||||
export CFLAGS
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
if [ "$RUNNER_OS" = "macOS" ]; then
|
||||
if [ "$RUNNER_OS" = "macOS" ] && [ ! -x "$(which ninja)" ]; then
|
||||
brew install ninja
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Mako==1.2.2
|
||||
requests==2.32.0
|
||||
requests==2.32.4
|
||||
semantic-version==2.8.5
|
||||
Vendored
-4054
File diff suppressed because it is too large.
Load diff
+6
-1
@@ -9,14 +9,19 @@ else()
|
||||
endif()
|
||||
|
||||
set(IS_ARM64 FALSE)
|
||||
set(IS_ARMV7A FALSE)
|
||||
|
||||
if (NOT "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "") # needed to cover Visual Studio generator
|
||||
if(CMAKE_GENERATOR_PLATFORM MATCHES "^(aarch64|arm64|ARM64)")
|
||||
set(IS_ARM64 TRUE)
|
||||
elseif(CMAKE_GENERATOR_PLATFORM MATCHES "^(armv7)")
|
||||
set(IS_ARMV7A TRUE)
|
||||
endif()
|
||||
else()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)")
|
||||
set(IS_ARM64 TRUE)
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv7)")
|
||||
set(IS_ARMV7A TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -33,6 +38,6 @@ else()
|
||||
endif()
|
||||
|
||||
set(IS_X86 FALSE)
|
||||
if (NOT IS_ARM64 AND NOT IS_RISCV)
|
||||
if (NOT IS_ARM64 AND NOT IS_RISCV AND NOT IS_ARMV7A)
|
||||
set(IS_X86 TRUE)
|
||||
endif()
|
||||
@@ -15,4 +15,4 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-unused-variable -Wno-unused-parameter")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -Wshadow")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wshadow")
|
||||
@@ -119,14 +119,14 @@ string(REGEX MATCH "-O[a-zA-Z|0-9]+" C_OPT_FLAG ${C_BASE_FLAGS})
|
||||
# If no user-set opt flag, set -O3 and -Og
|
||||
if ("${CXX_OPT_FLAG}" STREQUAL "")
|
||||
set(CXX_OPT_FLAG_RELEASE "-O3")
|
||||
set(CXX_OPT_FLAG_DEBUG "-Og")
|
||||
set(CXX_OPT_FLAG_DEBUG "-O0")
|
||||
else()
|
||||
set(CXX_OPT_FLAG_RELEASE "${CXX_OPT_FLAG}")
|
||||
set(CXX_OPT_FLAG_DEBUG "${CXX_OPT_FLAG}")
|
||||
endif()
|
||||
if ("${C_OPT_FLAG}" STREQUAL "")
|
||||
set(C_OPT_FLAG_RELEASE "-O3")
|
||||
set(C_OPT_FLAG_DEBUG "-Og")
|
||||
set(C_OPT_FLAG_DEBUG "-O0")
|
||||
else()
|
||||
set(C_OPT_FLAG_RELEASE "${C_OPT_FLAG}")
|
||||
set(C_OPT_FLAG_DEBUG "${C_OPT_FLAG}")
|
||||
|
||||
@@ -161,14 +161,14 @@ string(REGEX MATCH "-O[a-zA-Z|0-9]+" C_OPT_FLAG ${C_BASE_FLAGS})
|
||||
# If no user-set opt flag, set -O3 and -Og
|
||||
if ("${CXX_OPT_FLAG}" STREQUAL "")
|
||||
set(CXX_OPT_FLAG_RELEASE "-O3")
|
||||
set(CXX_OPT_FLAG_DEBUG "-Og")
|
||||
set(CXX_OPT_FLAG_DEBUG "-O0")
|
||||
else()
|
||||
set(CXX_OPT_FLAG_RELEASE "${CXX_OPT_FLAG}")
|
||||
set(CXX_OPT_FLAG_DEBUG "${CXX_OPT_FLAG}")
|
||||
endif()
|
||||
if ("${C_OPT_FLAG}" STREQUAL "")
|
||||
set(C_OPT_FLAG_RELEASE "-O3")
|
||||
set(C_OPT_FLAG_DEBUG "-Og")
|
||||
set(C_OPT_FLAG_DEBUG "-O0")
|
||||
else()
|
||||
set(C_OPT_FLAG_RELEASE "${C_OPT_FLAG}")
|
||||
set(C_OPT_FLAG_DEBUG "${C_OPT_FLAG}")
|
||||
|
||||
@@ -72,11 +72,10 @@ if (MSVC_RELEASE_DEBUGGING)
|
||||
endif()
|
||||
|
||||
IF(MSVC_USE_RUNTIME_DLL)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MD")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<NOT:$<CONFIG:Release>>:Debug>DLL")
|
||||
add_compile_definitions(_AFXDLL)
|
||||
ELSE(MSVC_USE_RUNTIME_DLL)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<NOT:$<CONFIG:Release>>:Debug>")
|
||||
ENDIF(MSVC_USE_RUNTIME_DLL)
|
||||
|
||||
# Debug
|
||||
@@ -84,14 +83,6 @@ set(CMAKE_C_FLAGS_DEBUG "/W4 /Gy /Zi /Od /RTC1 /Gd /Oy-")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/W4 /Gy /Zi /Od /RTC1 /Gd /Oy-")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /INCREMENTAL:NO /MAPINFO:EXPORTS /NODEFAULTLIB:libcmt.lib")
|
||||
|
||||
IF(MSVC_USE_RUNTIME_DLL)
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MDd")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")
|
||||
ELSE(MSVC_USE_RUNTIME_DLL)
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
|
||||
ENDIF(MSVC_USE_RUNTIME_DLL)
|
||||
|
||||
INCLUDE(MSVCMultipleProcessCompile)
|
||||
|
||||
# Visual Studio supports compiling for multiple vector instruction sets
|
||||
@@ -122,8 +113,8 @@ if(IS_X86)
|
||||
|
||||
endif()
|
||||
|
||||
target_compile_definitions(compiler INTERFACE _CRT_SECURE_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS _SECURE_SCL=0 NOMINMAX
|
||||
"$<$<CONFIG:FastDebug>:_ITERATOR_DEBUG_LEVEL=0>" _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING)
|
||||
add_compile_definitions("$<$<CONFIG:FastDebug>:_ITERATOR_DEBUG_LEVEL=0>")
|
||||
target_compile_definitions(compiler INTERFACE _CRT_SECURE_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS _SECURE_SCL=0 NOMINMAX _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING)
|
||||
|
||||
if (FSO_FATAL_WARNINGS)
|
||||
# Make warnings fatal if the right variable is set
|
||||
|
||||
+11
-21
@@ -158,27 +158,17 @@ function(set_precompiled_header _target _headerPath)
|
||||
target_compile_definitions(${_target} PRIVATE CMAKE_PCH)
|
||||
target_precompile_headers(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${_headerPath}>")
|
||||
else()
|
||||
if (ENABLE_COTIRE)
|
||||
message("${_headerPath}")
|
||||
set_target_properties(${_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${_headerPath}")
|
||||
|
||||
# Disable unity build as it doesn't work well for us
|
||||
set_target_properties(${_target} PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
|
||||
|
||||
cotire(${_target})
|
||||
else()
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:/FI ${_headerPath}>")
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
|
||||
ELSE()
|
||||
MESSAGE("Unknown compiler for global includes. This will probably break the build.")
|
||||
ENDIF()
|
||||
endif()
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:/FI ${_headerPath}>")
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
|
||||
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
|
||||
ELSE()
|
||||
MESSAGE("Unknown compiler for global includes. This will probably break the build.")
|
||||
ENDIF()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ endif()
|
||||
|
||||
target_link_libraries(code PUBLIC libRocket)
|
||||
|
||||
target_link_libraries(code PUBLIC pcp)
|
||||
target_link_libraries(code PUBLIC pcpnatpmp)
|
||||
|
||||
target_link_libraries(code PUBLIC parsers)
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ class ExpressionBuilderVisitor : public ActionExpressionVisitor {
|
||||
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>>();
|
||||
const auto leftExpr = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(0)));
|
||||
const auto rightExpr = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(1)));
|
||||
|
||||
auto operatorExpression = std::make_shared<nodes::FunctionCallExpression>(operatorTok,
|
||||
true,
|
||||
@@ -88,8 +88,8 @@ class ExpressionBuilderVisitor : public ActionExpressionVisitor {
|
||||
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>>());
|
||||
std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(0))),
|
||||
std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(1))));
|
||||
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
|
||||
}
|
||||
antlrcpp::Any visitLiteral_expression(ActionExpressionParser::Literal_expressionContext* context) override
|
||||
@@ -122,9 +122,9 @@ class ExpressionBuilderVisitor : public ActionExpressionVisitor {
|
||||
}
|
||||
antlrcpp::Any visitVec3d_constructor(ActionExpressionParser::Vec3d_constructorContext* context) override
|
||||
{
|
||||
const auto xExpression = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
const auto yExpression = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
const auto zExpression = visit(context->expression(2)).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
const auto xExpression = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(0)));
|
||||
const auto yExpression = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(1)));
|
||||
const auto zExpression = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(2)));
|
||||
|
||||
auto expression = std::make_shared<nodes::VectorConstructorExpression>(context->L_PAREN()->getSymbol(),
|
||||
xExpression,
|
||||
@@ -157,7 +157,7 @@ std::shared_ptr<nodes::AbstractExpression> ExpressionParser::parse(const ParseCo
|
||||
if (errListener.diagnostics.empty()) {
|
||||
// Only do semantic checking if syntax is valid
|
||||
ExpressionBuilderVisitor builder;
|
||||
exprReturn = tree->accept(&builder).as<std::shared_ptr<nodes::AbstractExpression>>();
|
||||
exprReturn = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(tree->accept(&builder));
|
||||
|
||||
if (!exprReturn->validate(&parser, context)) {
|
||||
exprReturn = nullptr;
|
||||
|
||||
+4
-4
@@ -527,7 +527,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, int ship_info_index = -1);
|
||||
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1, int class_type = -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);
|
||||
@@ -559,7 +559,7 @@ extern void ai_set_guard_wing(object *objp, int wingnum);
|
||||
extern void ai_warp_out(object *objp, vec3d *vp);
|
||||
extern void ai_attack_wing(object *attacker, int wingnum);
|
||||
extern void ai_deathroll_start(object *ship_obj);
|
||||
extern int set_target_objnum(ai_info *aip, int objnum);
|
||||
extern int set_target_objnum(ai_info* aip, int objnum);
|
||||
extern void ai_form_on_wing(object *objp, object *goal_objp);
|
||||
extern void ai_do_stay_near(object *objp, object *other_obj, float dist, int additional_data);
|
||||
extern ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int parent_objnum);
|
||||
@@ -596,7 +596,7 @@ extern int ai_maybe_fire_afterburner(object *objp, ai_info *aip);
|
||||
extern void set_predicted_enemy_pos(vec3d *predicted_enemy_pos, object *pobjp, vec3d *enemy_pos, vec3d *enemy_vel, ai_info *aip);
|
||||
|
||||
extern int is_instructor(object *objp);
|
||||
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1);
|
||||
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1, int class_type = -1);
|
||||
|
||||
float ai_get_weapon_speed(const ship_weapon *swp);
|
||||
void set_predicted_enemy_pos_turret(vec3d *predicted_enemy_pos, const vec3d *gun_pos, const object *pobjp, const vec3d *enemy_pos, const vec3d *enemy_vel, float weapon_speed, float time_enemy_in_range);
|
||||
@@ -617,7 +617,7 @@ extern float dock_orient_and_approach(object *docker_objp, int docker_index, obj
|
||||
void ai_set_mode_warp_out(object *objp, ai_info *aip);
|
||||
|
||||
// prototyped by Goober5000
|
||||
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index);
|
||||
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type = -1);
|
||||
|
||||
// moved to header file by Goober5000
|
||||
void ai_announce_ship_dying(object *dying_objp);
|
||||
|
||||
+12
-1
@@ -44,12 +44,15 @@ namespace AI {
|
||||
Goal_on_hold, // when set, this goal cannot currently be satisfied, although it could be in the future
|
||||
Subsys_needs_fixup, // when set, the subsystem index (for a destroy subsystem goal) is invalid and must be gotten from the subsys name stored in docker.name field!!
|
||||
Goal_override, // paired with ai_goal_type::DYNAMIC to mean this goal overrides any other goal
|
||||
Want_override, // a goal should set this flag if Goal_override should be assigned when the goal is achievable
|
||||
Purge, // purge this goal next time we process
|
||||
Goals_purged, // this goal has already caused other goals to get purged
|
||||
Purge_when_new_goal_added, // this goal is perpetual until any other goal is added, at which time it sets its own Purge flag
|
||||
Goals_purged, // this goal has already caused other goals to get purged (because it is something like ai-disarm that renders other goals invalid)
|
||||
Depart_sound_played,// Goober5000 - replacement for AL's hack ;)
|
||||
Target_own_team, // this attack goal is allowed to target friendlies
|
||||
Afterburn_hard, // afterburn as hard as possible to the goal
|
||||
Waypoints_in_reverse, // decrement instead of increment
|
||||
Clear_all_goals_first, // this goal wipes all goals (including itself) before it runs (handled separately, and in a different place, from purging invalid goals)
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
@@ -142,6 +145,7 @@ namespace AI {
|
||||
Fightercraft_nonshielded_ships_can_manage_ets,
|
||||
Ships_playing_dead_dont_manage_ets,
|
||||
Better_combat_collision_avoidance,
|
||||
Better_combat_collision_avoid_includes_target,
|
||||
Better_guard_collision_avoidance,
|
||||
Require_exact_los,
|
||||
Improved_missile_avoidance,
|
||||
@@ -170,6 +174,7 @@ namespace AI {
|
||||
Debris_respects_big_damage,
|
||||
Dont_limit_change_in_speed_due_to_physics_whack,
|
||||
Guards_ignore_protected_attackers,
|
||||
Fix_standard_strafe,
|
||||
Standard_strafe_used_more,
|
||||
Unify_usage_ai_shield_manage_delay,
|
||||
Fix_AI_shield_management_bug,
|
||||
@@ -178,6 +183,12 @@ namespace AI {
|
||||
Freespace_1_missile_behavior,
|
||||
ETS_uses_power_output,
|
||||
ETS_energy_same_regardless_of_system_presence,
|
||||
Dont_form_on_wing_at_mission_start,
|
||||
Do_not_clear_goals_when_running_form_on_wing,
|
||||
Do_not_clear_goals_when_running_stay_still,
|
||||
Do_not_set_override_when_assigning_form_on_wing,
|
||||
Purge_player_issued_form_on_wing_after_subsequent_order,
|
||||
Cancel_future_waves_of_any_wing_launched_from_an_exited_ship,
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
|
||||
@@ -477,6 +477,9 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$support don't add primaries:", AI::Profile_Flags::Support_dont_add_primaries);
|
||||
|
||||
if (optional_string("$max allies rearming threshold:"))
|
||||
stuff_int(&profile->max_allies_rearming_threshold);
|
||||
|
||||
set_flag(profile, "$firing requires exact los:", AI::Profile_Flags::Require_exact_los);
|
||||
|
||||
if (optional_string("$exact los minimum detection radius:")) {
|
||||
@@ -588,6 +591,8 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
stuff_float(&profile->better_collision_avoid_aggression_combat);
|
||||
}
|
||||
|
||||
set_flag(profile, "+combat collision avoidance for fightercraft includes target:", AI::Profile_Flags::Better_combat_collision_avoid_includes_target);
|
||||
|
||||
set_flag(profile, "$better guard collision avoidance for fightercraft:", AI::Profile_Flags::Better_guard_collision_avoidance);
|
||||
|
||||
if (optional_string("+guard collision avoidance aggression for fightercraft:")) {
|
||||
@@ -669,6 +674,8 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$fix avoid-shockwave bugs:", AI::Profile_Flags::Fix_avoid_shockwave_bugs);
|
||||
|
||||
set_flag(profile, "$fix standard strafe:", AI::Profile_Flags::Fix_standard_strafe);
|
||||
|
||||
set_flag(profile, "$standard strafe used more:", AI::Profile_Flags::Standard_strafe_used_more);
|
||||
|
||||
if (optional_string("$standard strafe triggers under this speed:")) {
|
||||
@@ -697,6 +704,10 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
}
|
||||
}
|
||||
|
||||
if (optional_string("$attack-any idle circle distance:")) {
|
||||
stuff_float(&profile->attack_any_idle_circle_distance);
|
||||
}
|
||||
|
||||
set_flag(profile, "$unify usage of AI Shield Manage Delay:", AI::Profile_Flags::Unify_usage_ai_shield_manage_delay);
|
||||
|
||||
set_flag(profile, "$fix AI shield management bug:", AI::Profile_Flags::Fix_AI_shield_management_bug);
|
||||
@@ -711,6 +722,28 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$ETS energy same regardless of system presence:", AI::Profile_Flags::ETS_energy_same_regardless_of_system_presence);
|
||||
|
||||
set_flag(profile, "$don't issue form-on-wing goals at mission start:", AI::Profile_Flags::Dont_form_on_wing_at_mission_start);
|
||||
|
||||
if (optional_string("$default form-on-wing priority:")) {
|
||||
int priority;
|
||||
stuff_int(&priority);
|
||||
if (priority > 0) {
|
||||
profile->default_form_on_wing_priority = priority;
|
||||
} else {
|
||||
mprintf(("Warning: $default form-on-wing priority: should be > 0 (read %d). Value will not be used.\n", priority));
|
||||
}
|
||||
}
|
||||
|
||||
set_flag(profile, "$do not clear goals when running form-on-wing:", AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing);
|
||||
|
||||
set_flag(profile, "$do not clear goals when running stay-still:", AI::Profile_Flags::Do_not_clear_goals_when_running_stay_still);
|
||||
|
||||
set_flag(profile, "$do not set override when assigning form-on-wing:", AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing);
|
||||
|
||||
set_flag(profile, "$purge player-issued form-on-wing after subsequent order:", AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order);
|
||||
|
||||
set_flag(profile, "$cancel future waves of any wing launched from an exited ship:", AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship);
|
||||
|
||||
|
||||
// end of options ----------------------------------------
|
||||
|
||||
@@ -803,6 +836,7 @@ void ai_profile_t::reset()
|
||||
ai_range_aware_secondary_select_mode = AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL;
|
||||
turret_target_recheck_time = 2000.0f;
|
||||
rot_fac_multiplier_ply_collisions = 0.0f;
|
||||
max_allies_rearming_threshold = 2;
|
||||
|
||||
better_collision_avoid_aggression_combat = 3.5f;
|
||||
better_collision_avoid_aggression_guard = 3.5f;
|
||||
@@ -813,6 +847,9 @@ void ai_profile_t::reset()
|
||||
|
||||
guard_big_orbit_above_target_radius = 500.0f;
|
||||
guard_big_orbit_max_speed_percent = 1.0f;
|
||||
attack_any_idle_circle_distance = 100.0f;
|
||||
|
||||
default_form_on_wing_priority = 99; // as originally assigned in ai_add_goal_sub_sexp()
|
||||
|
||||
for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
|
||||
max_incoming_asteroids[i] = 0;
|
||||
@@ -927,5 +964,7 @@ void ai_profile_t::reset()
|
||||
}
|
||||
if (mod_supports_version(25, 0, 0)) {
|
||||
flags.set(AI::Profile_Flags::Fix_avoid_shockwave_bugs);
|
||||
flags.set(AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order);
|
||||
flags.set(AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship);
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,9 @@ public:
|
||||
|
||||
float detail_distance_mult[MAX_DETAIL_VALUE + 1]; //MAX_DETAIL_VALUE really needs to be 4
|
||||
|
||||
// max number of allies allowed to be rearming at the same time
|
||||
int max_allies_rearming_threshold;
|
||||
|
||||
// minimum radius for the line-of-sight (los) detection --wookieejedi
|
||||
float los_min_detection_radius;
|
||||
|
||||
@@ -144,6 +147,11 @@ public:
|
||||
float guard_big_orbit_above_target_radius; // Radius of guardee that triggers ai_big_guard()
|
||||
float guard_big_orbit_max_speed_percent; // Max percent of forward speed that is used in ai_big_guard()
|
||||
|
||||
// AI attack any option --wookieejedi
|
||||
float attack_any_idle_circle_distance; // Radius that AI circles around a point while waiting for new enemies in attack-any mode
|
||||
|
||||
int default_form_on_wing_priority; // the priority used if not specified in the sexp
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
|
||||
+40
-19
@@ -62,9 +62,9 @@
|
||||
void ai_big_evade_ship();
|
||||
void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float dist_to_enemy, int modelnum);
|
||||
void ai_big_avoid_ship();
|
||||
int ai_big_maybe_follow_subsys_path(int do_dot_check=1);
|
||||
bool ai_big_maybe_follow_subsys_path(bool do_dot_check=true);
|
||||
void ai_big_strafe_position();
|
||||
static int ai_big_strafe_maybe_retreat(const vec3d *target_pos);
|
||||
static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos);
|
||||
|
||||
extern void compute_desired_rvec(vec3d *rvec, const vec3d *goal_pos, const vec3d *cur_pos);
|
||||
extern void big_ship_collide_recover_start(const object *objp, const object *big_objp, const vec3d *collision_normal);
|
||||
@@ -146,6 +146,10 @@ done_1:
|
||||
|
||||
*attack_point = best_point;
|
||||
|
||||
//The following raycast tends to make up 10%(!) of total AI-frametime for basically no benefit, especially in the common case for turrets where the normal isn't even queried.
|
||||
if (surface_normal == nullptr && Disable_expensive_turret_target_check)
|
||||
return;
|
||||
|
||||
// Cast from attack_objp_pos to local_attack_pos and check for nearest collision.
|
||||
// If no collision, cast to (0,0,0) [center of big ship]** [best_point initialized to 000]
|
||||
|
||||
@@ -347,10 +351,10 @@ void ai_big_subsys_path_cleanup(ai_info *aip)
|
||||
}
|
||||
|
||||
// Maybe Pl_objp needs to follow a path to get in line-of-sight to a subsystem
|
||||
// input: do_dot_check => default value 0, flag to indicate whether check should be done to ensure
|
||||
// input: do_dot_check => default value true, flag to indicate whether check should be done to ensure
|
||||
// subsystem is within certain field of view. We don't want to check fov when
|
||||
// strafing, since ship is weaving to avoid turret fire
|
||||
int ai_big_maybe_follow_subsys_path(int do_dot_check)
|
||||
bool ai_big_maybe_follow_subsys_path(bool do_dot_check)
|
||||
{
|
||||
ai_info *aip;
|
||||
float dot = 1.0f, min_dot;
|
||||
@@ -864,7 +868,7 @@ void ai_big_chase_ct()
|
||||
extern bool ai_select_secondary_weapon(object *objp, ship_weapon *swp, int priority1 = -1, int priority2 = -1);
|
||||
extern float set_secondary_fire_delay(ai_info *aip, ship *shipp, weapon_info *swip, bool burst);
|
||||
extern bool ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp);
|
||||
extern int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f);
|
||||
extern bool maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f);
|
||||
|
||||
extern void maybe_cheat_fire_synaptic(object *objp);
|
||||
|
||||
@@ -1416,7 +1420,7 @@ void ai_big_attack_get_data(vec3d *enemy_pos, float *dist_to_enemy, float *dot_t
|
||||
|
||||
// check to see if Pl_objp has gotten too close to attacking point.. if so, break off by entering
|
||||
// AIS_STRAFE_RETREAT
|
||||
static int ai_big_strafe_maybe_retreat(const vec3d *target_pos)
|
||||
static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
|
||||
{
|
||||
ai_info *aip;
|
||||
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
||||
@@ -1424,25 +1428,42 @@ static int ai_big_strafe_maybe_retreat(const vec3d *target_pos)
|
||||
vec3d vec_to_target;
|
||||
vm_vec_sub(&vec_to_target, target_pos, &Pl_objp->pos);
|
||||
|
||||
float dist_to_target, dist_normal_to_target, time_to_target;
|
||||
dist_to_target = vm_vec_mag_quick(&vec_to_target);
|
||||
if (vm_vec_mag_quick(&aip->big_attack_surface_normal) > 0.9) {
|
||||
dist_normal_to_target = -vm_vec_dot(&vec_to_target, &aip->big_attack_surface_normal);
|
||||
} else {
|
||||
dist_normal_to_target = 0.2f * vm_vec_mag_quick(&vec_to_target);
|
||||
}
|
||||
|
||||
dist_normal_to_target = MAX(0.2f*dist_to_target, dist_normal_to_target);
|
||||
time_to_target = dist_normal_to_target / Pl_objp->phys_info.speed;
|
||||
float dist_to_target = vm_vec_mag(&vec_to_target);
|
||||
|
||||
// add distance penalty for going too fast
|
||||
float speed_to_dist_penalty = MAX(0.0f, (Pl_objp->phys_info.speed-50));
|
||||
|
||||
bool collide_time;
|
||||
bool collide_distance;
|
||||
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_standard_strafe]) {
|
||||
// check if ship facing target, as likely will only collide if facing
|
||||
vec3d vec_to_tpos;
|
||||
vm_vec_normalized_dir(&vec_to_tpos, target_pos, &Pl_objp->pos);
|
||||
float dot = vm_vec_dot(&vec_to_tpos, &Pl_objp->orient.vec.fvec);
|
||||
if (dot <= 0.0f) {
|
||||
collide_time = false;
|
||||
collide_distance = false;
|
||||
} else {
|
||||
collide_time = (dist_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
|
||||
collide_distance = dist_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
|
||||
}
|
||||
} else {
|
||||
float dist_normal_to_target;
|
||||
if (vm_vec_mag(&aip->big_attack_surface_normal) > 0.9) {
|
||||
dist_normal_to_target =
|
||||
MAX(0.2f * dist_to_target, -vm_vec_dot(&vec_to_target, &aip->big_attack_surface_normal));
|
||||
} else {
|
||||
dist_normal_to_target = 0.2f * dist_to_target;
|
||||
}
|
||||
collide_time = (dist_normal_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
|
||||
collide_distance = dist_normal_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
|
||||
}
|
||||
|
||||
//if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f))
|
||||
|
||||
// Inside 2 sec retreat, setting goal point to box point + strafe_retreat_box_dist
|
||||
// If collision, use std collision resolution.
|
||||
if ( !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && ((aip->ai_flags[AI::AI_Flags::Target_collision]) || (time_to_target < STRAFE_RETREAT_COLLIDE_TIME) || (dist_normal_to_target < STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty)) ) {
|
||||
if ( !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && ((aip->ai_flags[AI::AI_Flags::Target_collision]) || (collide_time) || (collide_distance)) ) {
|
||||
if (aip->ai_flags[AI::AI_Flags::Target_collision]) {
|
||||
// use standard collision resolution
|
||||
aip->ai_flags.remove(AI::AI_Flags::Target_collision);
|
||||
@@ -1486,7 +1507,7 @@ void ai_big_strafe_attack()
|
||||
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))
|
||||
} else if (ai_big_maybe_follow_subsys_path(false))
|
||||
return;
|
||||
|
||||
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
||||
@@ -1606,7 +1627,7 @@ void ai_big_strafe_glide_attack()
|
||||
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))
|
||||
} else if (ai_big_maybe_follow_subsys_path(false))
|
||||
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.
|
||||
|
||||
+131
-70
@@ -542,6 +542,19 @@ void garbage_collect_path_points()
|
||||
|
||||
}
|
||||
|
||||
void reset_ai_path_points()
|
||||
{
|
||||
memset(Path_points, 0, sizeof(Path_points)); // optional: zero out for safety
|
||||
Ppfp = Path_points;
|
||||
|
||||
// Also clear any AI path state
|
||||
for (int i = 0; i < MAX_SHIPS; ++i) {
|
||||
Ai_info[i].path_start = -1;
|
||||
Ai_info[i].path_cur = -1;
|
||||
Ai_info[i].path_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash two values together, return result.
|
||||
* Hash function: curval shifted right circular by one, newval xored in.
|
||||
@@ -2216,6 +2229,7 @@ typedef struct eval_nearest_objnum {
|
||||
object *trial_objp;
|
||||
int enemy_team_mask;
|
||||
int enemy_ship_info_index;
|
||||
int enemy_class_type;
|
||||
int enemy_wing;
|
||||
float range;
|
||||
int max_attackers;
|
||||
@@ -2249,6 +2263,10 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
|
||||
if ((eno->enemy_ship_info_index >= 0) && (shipp->ship_info_index != eno->enemy_ship_info_index))
|
||||
return;
|
||||
|
||||
// If only supposed to attack ships of a certain ship type, don't attack other ships.
|
||||
if ((eno->enemy_class_type >= 0) && (Ship_info[shipp->ship_info_index].class_type != eno->enemy_class_type))
|
||||
return;
|
||||
|
||||
// Don't keep firing at a ship that is in its death throes.
|
||||
if (shipp->flags[Ship::Ship_Flags::Dying])
|
||||
return;
|
||||
@@ -2337,8 +2355,9 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
|
||||
* @param range Ship must be within range "range".
|
||||
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
|
||||
* @param ship_info_index If >=0, the enemy object must be of the specified ship class
|
||||
* @param class_type If >=0, the enemy object must be of the specified ship type
|
||||
*/
|
||||
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index)
|
||||
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type)
|
||||
{
|
||||
object *danger_weapon_objp;
|
||||
ai_info *aip;
|
||||
@@ -2348,6 +2367,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
|
||||
eval_nearest_objnum eno;
|
||||
eno.enemy_team_mask = enemy_team_mask;
|
||||
eno.enemy_ship_info_index = ship_info_index;
|
||||
eno.enemy_class_type = class_type;
|
||||
eno.enemy_wing = enemy_wing;
|
||||
eno.max_attackers = max_attackers;
|
||||
eno.objnum = objnum;
|
||||
@@ -2393,7 +2413,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
|
||||
// If only looking for target in certain wing and couldn't find anything in
|
||||
// that wing, look for any object.
|
||||
if ((eno.nearest_objnum == -1) && (enemy_wing != -1)) {
|
||||
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index);
|
||||
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index, class_type);
|
||||
}
|
||||
|
||||
return eno.nearest_objnum;
|
||||
@@ -2497,13 +2517,15 @@ int get_enemy_timestamp()
|
||||
/**
|
||||
* Return objnum if enemy found, else return -1;
|
||||
*
|
||||
* @param objnum Object number
|
||||
* @param range Range within which to look
|
||||
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
|
||||
* @param objnum Object number
|
||||
* @param range Range within which to look
|
||||
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
|
||||
* @param ship_info_index If specified, restrict the search to enemies with this ship class
|
||||
* @param class_type If specified, restrict the search to enemies with this ship type
|
||||
*/
|
||||
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
|
||||
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index, int class_type)
|
||||
{
|
||||
int enemy_team_mask;
|
||||
int enemy_team_mask;
|
||||
|
||||
if (objnum < 0)
|
||||
return -1;
|
||||
@@ -2511,19 +2533,25 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
|
||||
enemy_team_mask = iff_get_attackee_mask(obj_team(&Objects[objnum]));
|
||||
|
||||
// if target_objnum != -1, use that as goal.
|
||||
ai_info *aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
|
||||
ai_info* aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
|
||||
if (timestamp_elapsed(aip->choose_enemy_timestamp)) {
|
||||
aip->choose_enemy_timestamp = timestamp(get_enemy_timestamp());
|
||||
|
||||
if (aip->target_objnum != -1) {
|
||||
int target_objnum = aip->target_objnum;
|
||||
int target_objnum = aip->target_objnum;
|
||||
|
||||
// DKA don't undo object as target in nebula missions.
|
||||
// This could cause attack on ship on fringe on nebula to stop if attackee moves our of nebula range. (BAD)
|
||||
if ( Objects[target_objnum].signature == aip->target_signature ) {
|
||||
if (iff_matches_mask(Ships[Objects[target_objnum].instance].team, enemy_team_mask)) {
|
||||
if (ship_info_index < 0 || ship_info_index == Ships[Objects[target_objnum].instance].ship_info_index) {
|
||||
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
|
||||
return target_objnum;
|
||||
// This could cause attack on ship on fringe on nebula to stop if attackee moves out of nebula range. (BAD)
|
||||
if (Objects[target_objnum].signature == aip->target_signature) {
|
||||
ship* target_shipp = (Objects[target_objnum].type == OBJ_SHIP) ? &Ships[Objects[target_objnum].instance] : nullptr;
|
||||
|
||||
if (target_shipp && iff_matches_mask(target_shipp->team, enemy_team_mask)) {
|
||||
if (ship_info_index < 0 || ship_info_index == target_shipp->ship_info_index) {
|
||||
if (class_type < 0 || (target_shipp->ship_info_index >= 0 &&
|
||||
class_type == Ship_info[target_shipp->ship_info_index].class_type)) {
|
||||
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
|
||||
return target_objnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2532,9 +2560,8 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
|
||||
aip->target_signature = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index);
|
||||
|
||||
|
||||
return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index, class_type);
|
||||
} else {
|
||||
aip->target_objnum = -1;
|
||||
aip->target_signature = -1;
|
||||
@@ -2575,7 +2602,7 @@ void force_avoid_player_check(object *objp, ai_info *aip)
|
||||
* If attacked == NULL, then attack any enemy object.
|
||||
* Attack point *rel_pos on object. This is for supporting attacking subsystems.
|
||||
*/
|
||||
void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
|
||||
void ai_attack_object(object* attacker, object* attacked, int ship_info_index, int class_type)
|
||||
{
|
||||
int temp;
|
||||
ai_info* aip;
|
||||
@@ -2608,7 +2635,7 @@ void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
|
||||
if (attacked == nullptr) {
|
||||
aip->choose_enemy_timestamp = timestamp(0);
|
||||
// nebula safe
|
||||
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index));
|
||||
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index, class_type));
|
||||
} else {
|
||||
// check if we can see attacked in nebula
|
||||
if (aip->target_objnum != OBJ_INDEX(attacked)) {
|
||||
@@ -3650,6 +3677,14 @@ void ai_form_on_wing(object *objp, object *goal_objp)
|
||||
aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
|
||||
aip->ai_flags.set(AI::AI_Flags::Formation_object);
|
||||
|
||||
// if this goal is going to stick around for a while, change the mode
|
||||
if (The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing])
|
||||
{
|
||||
aip->mode = AIM_NONE;
|
||||
aip->submode = -1;
|
||||
aip->submode_start_time = Missiontime;
|
||||
}
|
||||
|
||||
aip->goal_objnum = OBJ_INDEX(goal_objp);
|
||||
|
||||
ai_formation_object_recalculate_slotnums(aip->goal_objnum);
|
||||
@@ -7665,7 +7700,7 @@ void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point
|
||||
/**
|
||||
* Return true if a large ship is being ignored.
|
||||
*/
|
||||
int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f)
|
||||
bool maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f)
|
||||
{
|
||||
if (timestamp_elapsed(aip->avoid_check_timestamp)) {
|
||||
float distance;
|
||||
@@ -7684,7 +7719,7 @@ int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d
|
||||
aip->ai_flags.remove(AI::AI_Flags::Avoiding_small_ship);
|
||||
aip->avoid_ship_num = -1;
|
||||
next_check_time = (int) (1500 * time_scale);
|
||||
aip->avoid_check_timestamp = timestamp(1500);
|
||||
aip->avoid_check_timestamp = timestamp(next_check_time);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7700,10 +7735,30 @@ int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d
|
||||
if (ai_willing_to_afterburn_hard(aip) && dot > 0.99f)
|
||||
ai_afterburn_hard(Pl_objp, aip);
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if small ship and it will likely collide with large ship
|
||||
* developed by Asteroth
|
||||
*/
|
||||
bool better_collision_avoidance_triggered(bool flag_to_check, float avoidance_aggression, object* pl_objp, object* ignore_objp)
|
||||
{
|
||||
ship* shipp = &Ships[pl_objp->instance];
|
||||
ship_info* sip = &Ship_info[shipp->ship_info_index];
|
||||
|
||||
if ((flag_to_check) && sip->is_small_ship()) {
|
||||
vec3d collide_vec = pl_objp->phys_info.vel * (avoidance_aggression / (PI2 / sip->srotation_time));
|
||||
float radius_contribution = (pl_objp->phys_info.speed + pl_objp->radius) / pl_objp->phys_info.speed;
|
||||
collide_vec *= radius_contribution;
|
||||
|
||||
collide_vec += pl_objp->pos;
|
||||
return (maybe_avoid_big_ship(pl_objp, ignore_objp, &Ai_info[shipp->ai_index], &collide_vec, 0.f, 0.1f));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8928,14 +8983,12 @@ void ai_chase()
|
||||
return;
|
||||
}
|
||||
|
||||
if ((The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoidance]) && sip->is_small_ship()) {
|
||||
vec3d collide_vec = Pl_objp->phys_info.vel * (The_mission.ai_profile->better_collision_avoid_aggression_combat / (PI2 / sip->srotation_time));
|
||||
float radius_contribution = (Pl_objp->phys_info.speed + Pl_objp->radius) / Pl_objp->phys_info.speed;
|
||||
collide_vec *= radius_contribution;
|
||||
|
||||
collide_vec += Pl_objp->pos;
|
||||
if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &collide_vec, 0.f, 0.1f))
|
||||
return;
|
||||
if (better_collision_avoidance_triggered(
|
||||
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoidance],
|
||||
The_mission.ai_profile->better_collision_avoid_aggression_combat,
|
||||
Pl_objp,
|
||||
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoid_includes_target] ? nullptr : En_objp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enemy_sip_flags.any_set()) {
|
||||
@@ -10849,16 +10902,12 @@ void ai_guard()
|
||||
return;
|
||||
}
|
||||
|
||||
ship_info* sip = &Ship_info[shipp->ship_info_index];
|
||||
if ((The_mission.ai_profile->flags[AI::Profile_Flags::Better_guard_collision_avoidance]) && sip->is_small_ship()) {
|
||||
|
||||
vec3d collide_vec = Pl_objp->phys_info.vel * (The_mission.ai_profile->better_collision_avoid_aggression_guard / (PI2 / sip->srotation_time));
|
||||
float radius_contribution = (Pl_objp->phys_info.speed + Pl_objp->radius) / Pl_objp->phys_info.speed;
|
||||
collide_vec *= radius_contribution;
|
||||
|
||||
collide_vec += Pl_objp->pos;
|
||||
if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &collide_vec, 0.f, 0.1f))
|
||||
return;
|
||||
if (better_collision_avoidance_triggered(
|
||||
The_mission.ai_profile->flags[AI::Profile_Flags::Better_guard_collision_avoidance],
|
||||
The_mission.ai_profile->better_collision_avoid_aggression_guard,
|
||||
Pl_objp,
|
||||
En_objp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// handler for guard object with BIG radius
|
||||
@@ -11476,7 +11525,7 @@ void ai_stay_near()
|
||||
else if (aip->submode_parm0 != 0) {
|
||||
vec3d rand_vec, center_to_goal, center_to_objp;
|
||||
auto goal_objp = &Objects[goal_objnum];
|
||||
float max_dist = aip->submode_float0;
|
||||
float max_dist = std::max(aip->submode_float0, 1.0f);
|
||||
|
||||
// Figure out a goal point to fly to
|
||||
// Make not all ships pursue same point.
|
||||
@@ -11507,7 +11556,7 @@ void ai_stay_near()
|
||||
else {
|
||||
vec3d rand_vec, goal_pos, vec_to_goal;
|
||||
auto goal_objp = &Objects[goal_objnum];
|
||||
float max_dist = aip->submode_float0;
|
||||
float max_dist = std::max(aip->submode_float0, 1.0f);
|
||||
|
||||
// Make not all ships pursue same point.
|
||||
// Calculate the seed from both shipnums so the same Pl_objp doesn't always choose the same spot
|
||||
@@ -12100,9 +12149,11 @@ void ai_process_subobjects(int objnum)
|
||||
|
||||
bool in_lab = gameseq_get_state() == GS_STATE_LAB;
|
||||
|
||||
// non-player ships that are playing dead do not process subsystems or turrets
|
||||
if ((!(objp->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) && aip->mode == AIM_PLAY_DEAD)
|
||||
return;
|
||||
// non-player ships that are playing dead do not process subsystems or turrets unless we're in the lab
|
||||
if (gameseq_get_state() != GS_STATE_LAB) {
|
||||
if ((!(objp->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) && aip->mode == AIM_PLAY_DEAD)
|
||||
return;
|
||||
}
|
||||
|
||||
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
|
||||
polymodel *pm = model_get(pmi->model_num);
|
||||
@@ -13203,8 +13254,6 @@ static void ai_preprocess_ignore_objnum(ai_info *aip)
|
||||
aip->target_objnum = -1;
|
||||
}
|
||||
|
||||
#define CHASE_CIRCLE_DIST 100.0f
|
||||
|
||||
void ai_chase_circle(object *objp)
|
||||
{
|
||||
float dist_to_goal;
|
||||
@@ -13225,11 +13274,11 @@ void ai_chase_circle(object *objp)
|
||||
if (aip->ignore_objnum == UNUSED_OBJNUM) {
|
||||
dist_to_goal = vm_vec_dist_quick(&aip->goal_point, &objp->pos);
|
||||
|
||||
if (dist_to_goal > 2*CHASE_CIRCLE_DIST) {
|
||||
if (dist_to_goal > 2 * The_mission.ai_profile->attack_any_idle_circle_distance) {
|
||||
vec3d vec_to_goal;
|
||||
// Too far from circle goal, create a new goal point.
|
||||
vm_vec_normalized_dir(&vec_to_goal, &aip->goal_point, &objp->pos);
|
||||
vm_vec_scale_add(&aip->goal_point, &objp->pos, &vec_to_goal, CHASE_CIRCLE_DIST);
|
||||
vm_vec_scale_add(&aip->goal_point, &objp->pos, &vec_to_goal, The_mission.ai_profile->attack_any_idle_circle_distance);
|
||||
}
|
||||
|
||||
goal_point = aip->goal_point;
|
||||
@@ -13656,7 +13705,7 @@ int ai_acquire_emerge_path(object *pl_objp, int parent_objnum, int allowed_path_
|
||||
ship_info* parent_sip = &Ship_info[parent_shipp->ship_info_index];
|
||||
|
||||
polymodel *pm = model_get( parent_sip->model_num );
|
||||
ship_bay *bay = pm->ship_bay;
|
||||
const auto& bay = pm->ship_bay;
|
||||
|
||||
if ( bay == nullptr ) {
|
||||
Warning(LOCATION, "Ship %s was set to arrive from fighter bay on object %s, but no fighter bay exists on that ships' model (%s).\n", shipp->ship_name, parent_shipp->ship_name, pm->filename);
|
||||
@@ -13871,9 +13920,8 @@ int ai_find_closest_depart_path(ai_info *aip, polymodel *pm, int allowed_path_ma
|
||||
float dist, min_dist, min_free_dist;
|
||||
vec3d *source;
|
||||
model_path *mp;
|
||||
ship_bay *bay;
|
||||
|
||||
bay = pm->ship_bay;
|
||||
const auto& bay = pm->ship_bay;
|
||||
|
||||
best_free_path = best_path = -1;
|
||||
min_free_dist = min_dist = 1e20f;
|
||||
@@ -13950,9 +13998,9 @@ int ai_acquire_depart_path(object *pl_objp, int parent_objnum, int allowed_path_
|
||||
|
||||
object *parent_objp = &Objects[parent_objnum];
|
||||
polymodel *pm = model_get(Ship_info[Ships[parent_objp->instance].ship_info_index].model_num );
|
||||
ship_bay *bay = pm->ship_bay;
|
||||
const auto& bay = pm->ship_bay;
|
||||
|
||||
if ( bay == NULL )
|
||||
if ( bay == nullptr )
|
||||
return -1;
|
||||
if ( bay->num_paths <= 0 )
|
||||
return -1;
|
||||
@@ -14007,20 +14055,28 @@ void ai_bay_depart()
|
||||
return;
|
||||
}
|
||||
|
||||
ship* shipp;
|
||||
|
||||
// check if parent ship valid; if not, abort depart
|
||||
auto anchor_ship_entry = ship_registry_get(Parse_names[Ships[Pl_objp->instance].departure_anchor]);
|
||||
if (!anchor_ship_entry || !ship_useful_for_departure(anchor_ship_entry->shipnum, Ships[Pl_objp->instance].departure_path_mask))
|
||||
{
|
||||
mprintf(("Aborting bay departure!\n"));
|
||||
aip->mode = AIM_NONE;
|
||||
|
||||
Ships[Pl_objp->instance].flags.remove(Ship::Ship_Flags::Depart_dockbay);
|
||||
return;
|
||||
if (gameseq_get_state() != GS_STATE_LAB) {
|
||||
auto anchor_ship_entry = ship_registry_get(Parse_names[Ships[Pl_objp->instance].departure_anchor]);
|
||||
if (!anchor_ship_entry ||
|
||||
!ship_useful_for_departure(anchor_ship_entry->shipnum, Ships[Pl_objp->instance].departure_path_mask)) {
|
||||
mprintf(("Aborting bay departure!\n"));
|
||||
aip->mode = AIM_NONE;
|
||||
|
||||
Ships[Pl_objp->instance].flags.remove(Ship::Ship_Flags::Depart_dockbay);
|
||||
return;
|
||||
}
|
||||
|
||||
shipp = anchor_ship_entry->shipp();
|
||||
} else {
|
||||
shipp = &Ships[Objects[aip->goal_objnum].instance];
|
||||
}
|
||||
|
||||
ai_manage_bay_doors(Pl_objp, aip, false);
|
||||
|
||||
if ( anchor_ship_entry->shipp()->bay_doors_status != MA_POS_READY )
|
||||
if ( shipp->bay_doors_status != MA_POS_READY )
|
||||
return;
|
||||
|
||||
// follow the path to the final point
|
||||
@@ -14035,11 +14091,10 @@ void ai_bay_depart()
|
||||
|
||||
// Volition bay code
|
||||
polymodel *pm;
|
||||
ship_bay *bay;
|
||||
|
||||
pm = model_get(Ship_info[Ships[Objects[aip->goal_objnum].instance].ship_info_index].model_num);
|
||||
bay = pm->ship_bay;
|
||||
if ( bay != NULL ) {
|
||||
const auto& bay = pm->ship_bay;
|
||||
if ( bay != nullptr ) {
|
||||
bay->depart_flags &= ~(1<<aip->submode_parm0);
|
||||
}
|
||||
|
||||
@@ -14140,7 +14195,13 @@ void ai_execute_behavior(ai_info *aip)
|
||||
case AIM_STRAFE:
|
||||
if (En_objp) {
|
||||
Assert(En_objp->type == OBJ_SHIP);
|
||||
ai_big_strafe(); // strafe a big ship
|
||||
if (!(better_collision_avoidance_triggered(
|
||||
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoidance],
|
||||
The_mission.ai_profile->better_collision_avoid_aggression_combat,
|
||||
Pl_objp,
|
||||
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoid_includes_target] ? nullptr : En_objp))) {
|
||||
ai_big_strafe(); // strafe a big ship
|
||||
}
|
||||
} else {
|
||||
aip->mode = AIM_NONE;
|
||||
}
|
||||
@@ -14322,7 +14383,7 @@ int maybe_request_support(object *objp)
|
||||
try_to_rearm = true;
|
||||
} else if (ai_bad_time_to_rearm(objp)) {
|
||||
try_to_rearm = false;
|
||||
} else if (num_allies_rearming(objp) < 2) {
|
||||
} else if (num_allies_rearming(objp) < The_mission.ai_profile->max_allies_rearming_threshold) {
|
||||
if (desire >= 8) { // guarantees disabled will cause repair request
|
||||
try_to_rearm = true;
|
||||
} else if (desire >= 3) { // >= 3 means having a single subsystem fully blown will cause repair.
|
||||
|
||||
+266
-109
@@ -40,28 +40,43 @@ struct ship_registry_entry;
|
||||
// dynamic goals are not handled here.
|
||||
|
||||
// defines for player issued goal priorities
|
||||
#define PLAYER_PRIORITY_MIN 90
|
||||
#define PLAYER_PRIORITY_SHIP 100
|
||||
#define PLAYER_PRIORITY_WING 95
|
||||
#define PLAYER_PRIORITY_SUPPORT_LOW 10
|
||||
constexpr int PLAYER_PRIORITY_MIN = 90;
|
||||
constexpr int PLAYER_PRIORITY_SHIP = 100;
|
||||
constexpr int PLAYER_PRIORITY_WING = 95;
|
||||
constexpr int PLAYER_PRIORITY_SUPPORT_LOW = 10;
|
||||
|
||||
#define MAX_GOAL_PRIORITY 200
|
||||
constexpr int MAX_GOAL_PRIORITY = 200;
|
||||
constexpr int MIN_GOAL_PRIORITY = 1;
|
||||
|
||||
|
||||
// note: "purging" is not the same as "clearing" here
|
||||
// purging: any goal that no longer makes sense (i.e. is "invalid") in light of a new goal is removed
|
||||
// clearing: every goal is removed, including, ironically, the goal that caused the clearing
|
||||
|
||||
// define for which goals cause other goals to get purged
|
||||
// Goober5000 - okay, this seems really stupid. If any ship in the mission is assigned a goal
|
||||
// in PURGE_GOALS_ALL_SHIPS, *every* other ship will have certain goals purged. So I added
|
||||
// PURGE_GOALS_ONE_SHIP for goals which should only purge other goals in the one ship.
|
||||
// Goober5000 - note that the new disable and disarm goals (AI_GOAL_DISABLE_SHIP_TACTICAL and
|
||||
// AI_GOAL_DISARM_SHIP_TACTICAL) do not purge ANY goals, not even the ones in the one ship
|
||||
inline bool purge_goals_all_ships(ai_goal_mode ai_mode)
|
||||
[[nodiscard]] bool causes_invalid_goal_purge_all_ships(ai_goal_mode ai_mode)
|
||||
{
|
||||
return ai_mode == AI_GOAL_IGNORE || ai_mode == AI_GOAL_DISABLE_SHIP || ai_mode == AI_GOAL_DISARM_SHIP;
|
||||
}
|
||||
inline bool purge_goals_one_ship(ai_goal_mode ai_mode)
|
||||
[[nodiscard]] bool causes_invalid_goal_purge_one_ship(ai_goal_mode ai_mode)
|
||||
{
|
||||
return ai_mode == AI_GOAL_IGNORE_NEW;
|
||||
}
|
||||
|
||||
// function for which goals cause other goals to be cleared -- see comments above on purging vs clearing
|
||||
// NOTE: this function is only evaluated in ai_add_goal_sub_player() and ai_add_goal_sub_scripting(); for
|
||||
// ai_add_goal_sub_sexp(), the goal clearing is integrated into the logic of the relevant sexp operators
|
||||
[[nodiscard]] bool causes_goal_clearing(ai_goal_mode ai_mode)
|
||||
{
|
||||
return ((ai_mode == AI_GOAL_STAY_STILL && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_stay_still])
|
||||
|| (ai_mode == AI_GOAL_FORM_ON_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing])
|
||||
|| (ai_mode == AI_GOAL_PLAY_DEAD));
|
||||
}
|
||||
|
||||
// goals given from the player to other ships in the game are also handled in this
|
||||
// code
|
||||
|
||||
@@ -100,6 +115,7 @@ ai_goal_list Ai_goal_names[] =
|
||||
{ "Attack weapon", AI_GOAL_CHASE_WEAPON, 0 },
|
||||
{ "Fly to ship", AI_GOAL_FLY_TO_SHIP, 0 },
|
||||
{ "Attack ship class", AI_GOAL_CHASE_SHIP_CLASS, 0 },
|
||||
{ "Attack ship type", AI_GOAL_CHASE_SHIP_TYPE, 0 },
|
||||
};
|
||||
|
||||
int Num_ai_goals = sizeof(Ai_goal_names) / sizeof(ai_goal_list);
|
||||
@@ -114,6 +130,7 @@ const char *Ai_goal_text(ai_goal_mode goal, int submode)
|
||||
case AI_GOAL_CHASE:
|
||||
case AI_GOAL_CHASE_WING:
|
||||
case AI_GOAL_CHASE_SHIP_CLASS:
|
||||
case AI_GOAL_CHASE_SHIP_TYPE:
|
||||
return XSTR( "attack ", 474);
|
||||
case AI_GOAL_DOCK:
|
||||
return XSTR( "dock ", 475);
|
||||
@@ -195,12 +212,15 @@ void ai_maybe_add_form_goal(wing* wingp)
|
||||
return;
|
||||
}
|
||||
|
||||
int j;
|
||||
// we may simply not want the mission to do this
|
||||
if (The_mission.ai_profile->flags[AI::Profile_Flags::Dont_form_on_wing_at_mission_start]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// iterate through the ship_index list of this wing and check for orders. We will do
|
||||
// this for all ships in the wing instead of on a wing only basis in case some ships
|
||||
// in the wing actually have different orders than others
|
||||
for (j = 0; j < wingp->current_count; j++) {
|
||||
for (int j = 0; j < wingp->current_count; j++) {
|
||||
ai_info* aip;
|
||||
|
||||
Assert(wingp->ship_index[j] != -1); // get Allender
|
||||
@@ -212,7 +232,8 @@ void ai_maybe_add_form_goal(wing* wingp)
|
||||
}
|
||||
|
||||
// need to add a form on my wing goal here. Ships are always forming on the player's wing.
|
||||
// it is sufficient enough to check the first goal entry to see if it has a valid goal
|
||||
// it is sufficient enough to check the first goal entry to see if it has a valid goal.
|
||||
// Note: Although the player didn't explicitly order this, it is treated as a player-issued order
|
||||
if (aip->goals[0].ai_mode == AI_GOAL_NONE) {
|
||||
// Need to have a more specific target in multi, or they may end up trying to target standalone placeholder.
|
||||
// So form on their team leader. In dogfight, all player-slot ai die, so just exclude.
|
||||
@@ -513,13 +534,25 @@ void ai_goal_purge_invalid_goals( ai_goal *aigp, ai_goal *goal_list, ai_info *ai
|
||||
if ( purge_goal->target_name == NULL )
|
||||
continue;
|
||||
|
||||
// goals operating on ship classes are handled slightly differently
|
||||
// goals operating on ship classes and ship types are handled slightly differently
|
||||
if ( purge_ai_mode == AI_GOAL_CHASE_SHIP_CLASS ) {
|
||||
// if the target of the purge goal is the same class of ship we are concerned about, then we have a match;
|
||||
// if it is not, then we can continue (see standard ship check below)
|
||||
if ( stricmp(purge_goal->target_name, Ship_info[Ships[ship_index].ship_info_index].name) != 0 )
|
||||
continue;
|
||||
}
|
||||
else if (purge_ai_mode == AI_GOAL_CHASE_SHIP_TYPE) {
|
||||
// Get the ship type of the ship we're concerned about
|
||||
int ship_type = Ship_info[Ships[ship_index].ship_info_index].class_type;
|
||||
|
||||
// If the ship type is invalid, we can't match it, so continue
|
||||
if (ship_type < 0)
|
||||
continue;
|
||||
|
||||
// Check if the target name of the purge goal matches the ship type name
|
||||
if (stricmp(purge_goal->target_name, Ship_types[ship_type].name) != 0)
|
||||
continue;
|
||||
}
|
||||
// standard goals operating on either wings or ships
|
||||
else {
|
||||
// determine if the purge goal is acting either on the ship or the ship's wing.
|
||||
@@ -588,8 +621,8 @@ void ai_goal_purge_all_invalid_goals(ai_goal *aigp)
|
||||
int i;
|
||||
ship_obj *sop;
|
||||
|
||||
// only purge goals if a new goal is one of the types in next statement
|
||||
if (!purge_goals_all_ships(aigp->ai_mode))
|
||||
// only purge goals if a new goal is one of the types that require purging
|
||||
if (!causes_invalid_goal_purge_all_ships(aigp->ai_mode))
|
||||
return;
|
||||
|
||||
for (sop = GET_FIRST(&Ship_obj_list); sop != END_OF_LIST(&Ship_obj_list); sop = GET_NEXT(sop))
|
||||
@@ -732,12 +765,14 @@ void ai_goal_fixup_dockpoints(ai_info *aip, ai_goal *aigp)
|
||||
// from the mission goals (i.e. those goals which come from events) in that we don't
|
||||
// use sexpressions for goals from the player...so we enumerate all the parameters
|
||||
|
||||
void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *target_name, ai_goal *aigp, const ai_lua_parameters& lua_target )
|
||||
void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *target_name, ai_goal *aigp, int int_data, float float_data, const ai_lua_parameters& lua_target )
|
||||
{
|
||||
Assert ( (type == ai_goal_type::PLAYER_WING) || (type == ai_goal_type::PLAYER_SHIP) );
|
||||
|
||||
ai_goal_reset(aigp, true, mode, submode, type);
|
||||
|
||||
aigp->int_data = int_data;
|
||||
aigp->float_data = float_data;
|
||||
aigp->lua_ai_target = lua_target;
|
||||
|
||||
if ( mode == AI_GOAL_WARP ) {
|
||||
@@ -755,6 +790,18 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
|
||||
if ( target_name != NULL )
|
||||
aigp->target_name = ai_get_goal_target_name( target_name, &aigp->target_name_index );
|
||||
|
||||
// set up the clear-goals flag for certain goals
|
||||
if (causes_goal_clearing(mode))
|
||||
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
|
||||
|
||||
// also set up the override, since it's no longer done automatically in ai_mission_goal_achievable
|
||||
if (mode == AI_GOAL_FORM_ON_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing])
|
||||
aigp->flags.set(AI::Goal_Flags::Want_override);
|
||||
|
||||
// generally, players want ships to form-on-wing only until they need to do something else
|
||||
if (mode == AI_GOAL_FORM_ON_WING && The_mission.ai_profile->flags[AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order])
|
||||
aigp->flags.set(AI::Goal_Flags::Purge_when_new_goal_added);
|
||||
|
||||
if (The_mission.ai_profile->flags[AI::Profile_Flags::Player_orders_afterburn_hard])
|
||||
aigp->flags.set(AI::Goal_Flags::Afterburn_hard);
|
||||
|
||||
@@ -766,10 +813,6 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
|
||||
if ( (mode == AI_GOAL_STAY_NEAR_SHIP) || (mode == AI_GOAL_KEEP_SAFE_DISTANCE) )
|
||||
aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW;
|
||||
|
||||
// Goober5000 - same with form-on-wing, since it's a type of staying near
|
||||
else if ( mode == AI_GOAL_FORM_ON_WING )
|
||||
aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW;
|
||||
|
||||
else if ( aigp->type == ai_goal_type::PLAYER_WING ) // NOLINT(readability-braces-around-statements)
|
||||
aigp->priority = PLAYER_PRIORITY_WING; // player wing goals not as high as ship goals
|
||||
else
|
||||
@@ -784,17 +827,22 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
|
||||
// my new docking code. :)
|
||||
int ai_goal_find_empty_slot( ai_goal *goals, int active_goal )
|
||||
{
|
||||
int gindex, oldest_index;
|
||||
int oldest_index = -1, first_empty_index = -1;
|
||||
|
||||
oldest_index = -1;
|
||||
for ( gindex = 0; gindex < MAX_AI_GOALS; gindex++ )
|
||||
for ( int gindex = 0; gindex < MAX_AI_GOALS; gindex++ )
|
||||
{
|
||||
// get the index for the first unused goal
|
||||
if (goals[gindex].ai_mode == AI_GOAL_NONE)
|
||||
return gindex;
|
||||
{
|
||||
if (first_empty_index < 0)
|
||||
first_empty_index = gindex;
|
||||
}
|
||||
// if any goal needs to be purged when we add a goal, set the flag
|
||||
else if (goals[gindex].flags[AI::Goal_Flags::Purge_when_new_goal_added])
|
||||
goals[gindex].flags.set(AI::Goal_Flags::Purge);
|
||||
|
||||
// if this is the active goal, don't consider it for pre-emption!!
|
||||
if (gindex == active_goal)
|
||||
// if this is the active goal, and a real goal, don't consider it for pre-emption!!
|
||||
if (gindex == active_goal && goals[gindex].ai_mode != AI_GOAL_NONE)
|
||||
continue;
|
||||
|
||||
// store the index of the oldest goal
|
||||
@@ -804,6 +852,10 @@ int ai_goal_find_empty_slot( ai_goal *goals, int active_goal )
|
||||
oldest_index = gindex;
|
||||
}
|
||||
|
||||
// try to use the first empty slot
|
||||
if (first_empty_index >= 0)
|
||||
return first_empty_index;
|
||||
|
||||
// if we didn't find an empty slot, use the oldest goal's slot
|
||||
return oldest_index;
|
||||
}
|
||||
@@ -836,9 +888,26 @@ void ai_add_goal_sub_scripting(ai_goal_type type, ai_goal_mode mode, int submode
|
||||
if ( target_name != NULL )
|
||||
aigp->target_name = ai_get_goal_target_name( target_name, &aigp->target_name_index );
|
||||
|
||||
// set up the clear-goals flag for certain goals
|
||||
if (causes_goal_clearing(mode))
|
||||
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
|
||||
|
||||
// also set up the override, since it's no longer done automatically in ai_mission_goal_achievable
|
||||
if (mode == AI_GOAL_FORM_ON_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing])
|
||||
aigp->flags.set(AI::Goal_Flags::Want_override);
|
||||
|
||||
aigp->priority = priority;
|
||||
aigp->int_data = int_data;
|
||||
aigp->float_data = float_data;
|
||||
|
||||
// range check
|
||||
if ( aigp->priority > MAX_GOAL_PRIORITY ) {
|
||||
nprintf (("AI", "bashing scripting priority of goal %d from %d to %d.\n", mode, aigp->priority, MAX_GOAL_PRIORITY));
|
||||
aigp->priority = MAX_GOAL_PRIORITY;
|
||||
} else if ( aigp->priority < MIN_GOAL_PRIORITY ) {
|
||||
nprintf (("AI", "bashing scripting priority of goal %d from %d to %d.\n", mode, aigp->priority, MIN_GOAL_PRIORITY));
|
||||
aigp->priority = MIN_GOAL_PRIORITY;
|
||||
}
|
||||
}
|
||||
|
||||
void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, const char *shipname, ai_info *aip, int int_data, float float_data)
|
||||
@@ -865,14 +934,14 @@ void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, co
|
||||
// is issued to ship or wing (from player), mode is AI_GOAL_*. submode is the submode the
|
||||
// ship should go into. shipname is the object of the action. aip is the ai_info pointer
|
||||
// of the ship receiving the order
|
||||
void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, ai_info *aip, const ai_lua_parameters& lua_target)
|
||||
void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, ai_info *aip, int int_data, float float_data, const ai_lua_parameters& lua_target)
|
||||
{
|
||||
int empty_index;
|
||||
ai_goal *aigp;
|
||||
|
||||
empty_index = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
|
||||
aigp = &aip->goals[empty_index];
|
||||
ai_add_goal_sub_player( type, mode, submode, shipname, aigp, lua_target );
|
||||
ai_add_goal_sub_player( type, mode, submode, shipname, aigp, int_data, float_data, lua_target );
|
||||
|
||||
// if the goal is to dock, then we must determine which dock points on the two ships to use.
|
||||
// If the target of the dock is a cargo type container, then we should use DOCK_TYPE_CARGO
|
||||
@@ -886,7 +955,7 @@ void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
|
||||
|
||||
// adds a goal from the player to the given wing (which in turn will add it to the proper
|
||||
// ships in the wing
|
||||
void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, int wingnum, const ai_lua_parameters& lua_target)
|
||||
void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, int wingnum, int int_data, float float_data, const ai_lua_parameters& lua_target)
|
||||
{
|
||||
int i, empty_index;
|
||||
wing *wingp = &Wings[wingnum];
|
||||
@@ -897,7 +966,7 @@ void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
|
||||
int num = wingp->ship_index[i];
|
||||
if ( num == -1 ) // ship must have been destroyed or departed
|
||||
continue;
|
||||
ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index], lua_target );
|
||||
ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index], int_data, float_data, lua_target );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,7 +974,7 @@ void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
|
||||
// there are more waves to come. We use the same method here as when adding a goal to
|
||||
// a ship -- find the first empty entry. If none exists, take the oldest entry and replace it.
|
||||
empty_index = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
|
||||
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], lua_target );
|
||||
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], int_data, float_data, lua_target );
|
||||
}
|
||||
|
||||
|
||||
@@ -938,11 +1007,9 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
// for achievability.
|
||||
aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name;
|
||||
|
||||
|
||||
aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever);
|
||||
aigp->ai_mode = AI_GOAL_WAYPOINTS;
|
||||
if ( op == OP_AI_WAYPOINTS_ONCE )
|
||||
aigp->ai_mode = AI_GOAL_WAYPOINTS_ONCE;
|
||||
aigp->ai_mode = (op == OP_AI_WAYPOINTS_ONCE) ? AI_GOAL_WAYPOINTS_ONCE : AI_GOAL_WAYPOINTS;
|
||||
|
||||
if (CDDDDR(node) < 0)
|
||||
aigp->int_data = 0; // handle optional node separately because we don't subtract 1 here
|
||||
else
|
||||
@@ -1021,10 +1088,27 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
}
|
||||
|
||||
case OP_AI_STAY_STILL:
|
||||
{
|
||||
int n = CDR(node);
|
||||
|
||||
aigp->target_name = ai_get_goal_target_name(CTEXT(n), &aigp->target_name_index); // waypoint path name;
|
||||
n = CDR(n);
|
||||
|
||||
aigp->priority = eval_num(n, priority_is_nan, priority_is_nan_forever);
|
||||
n = CDR(n);
|
||||
|
||||
bool clear_goals = !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_stay_still];
|
||||
if (n >= 0)
|
||||
{
|
||||
clear_goals = is_sexp_true(n);
|
||||
n = CDR(n);
|
||||
}
|
||||
if (clear_goals)
|
||||
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
|
||||
|
||||
aigp->ai_mode = AI_GOAL_STAY_STILL;
|
||||
aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name;
|
||||
aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever);
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_AI_DOCK:
|
||||
aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
|
||||
@@ -1042,13 +1126,12 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
break;
|
||||
|
||||
case OP_AI_PLAY_DEAD:
|
||||
aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever);
|
||||
aigp->ai_mode = AI_GOAL_PLAY_DEAD;
|
||||
break;
|
||||
|
||||
// we don't clear out ship goals for the "persistent" goal variant
|
||||
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
|
||||
FALLTHROUGH;
|
||||
case OP_AI_PLAY_DEAD_PERSISTENT:
|
||||
aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever);
|
||||
aigp->ai_mode = AI_GOAL_PLAY_DEAD_PERSISTENT;
|
||||
aigp->ai_mode = (op == OP_AI_PLAY_DEAD) ? AI_GOAL_PLAY_DEAD : AI_GOAL_PLAY_DEAD_PERSISTENT;
|
||||
break;
|
||||
|
||||
case OP_AI_KEEP_SAFE_DISTANCE:
|
||||
@@ -1084,14 +1167,53 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
}
|
||||
|
||||
case OP_AI_FORM_ON_WING:
|
||||
aigp->priority = 99;
|
||||
aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index);
|
||||
{
|
||||
int n = CDR(node);
|
||||
|
||||
aigp->target_name = ai_get_goal_target_name(CTEXT(n), &aigp->target_name_index);
|
||||
n = CDR(n);
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
aigp->priority = eval_num(n, priority_is_nan, priority_is_nan_forever);
|
||||
n = CDR(n);
|
||||
}
|
||||
else
|
||||
aigp->priority = The_mission.ai_profile->default_form_on_wing_priority;
|
||||
|
||||
bool clear_goals = !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing];
|
||||
if (n >= 0)
|
||||
{
|
||||
clear_goals = is_sexp_true(n);
|
||||
n = CDR(n);
|
||||
}
|
||||
if (clear_goals)
|
||||
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
|
||||
|
||||
bool set_override = !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing];
|
||||
if (n >= 0)
|
||||
{
|
||||
set_override = is_sexp_true(n);
|
||||
n = CDR(n);
|
||||
}
|
||||
if (set_override)
|
||||
aigp->flags.set(AI::Goal_Flags::Want_override);
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
if (is_sexp_true(n))
|
||||
aigp->flags.set(AI::Goal_Flags::Purge_when_new_goal_added);
|
||||
n = CDR(n);
|
||||
}
|
||||
|
||||
aigp->ai_mode = AI_GOAL_FORM_ON_WING;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_AI_CHASE:
|
||||
case OP_AI_CHASE_WING:
|
||||
case OP_AI_CHASE_SHIP_CLASS:
|
||||
case OP_AI_CHASE_SHIP_TYPE:
|
||||
case OP_AI_GUARD:
|
||||
case OP_AI_GUARD_WING:
|
||||
case OP_AI_EVADE_SHIP:
|
||||
@@ -1123,6 +1245,8 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
aigp->ai_mode = AI_GOAL_CHASE_WING;
|
||||
} else if (op == OP_AI_CHASE_SHIP_CLASS) {
|
||||
aigp->ai_mode = AI_GOAL_CHASE_SHIP_CLASS;
|
||||
} else if (op == OP_AI_CHASE_SHIP_TYPE) {
|
||||
aigp->ai_mode = AI_GOAL_CHASE_SHIP_TYPE;
|
||||
} else if ( op == OP_AI_IGNORE ) {
|
||||
aigp->ai_mode = AI_GOAL_IGNORE;
|
||||
} else if ( op == OP_AI_IGNORE_NEW ) {
|
||||
@@ -1164,10 +1288,13 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
} else if ( aigp->priority > MAX_GOAL_PRIORITY ) {
|
||||
nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MAX_GOAL_PRIORITY));
|
||||
aigp->priority = MAX_GOAL_PRIORITY;
|
||||
} else if ( aigp->priority < MIN_GOAL_PRIORITY ) {
|
||||
nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MIN_GOAL_PRIORITY));
|
||||
aigp->priority = MIN_GOAL_PRIORITY;
|
||||
}
|
||||
|
||||
// Goober5000 - we now have an extra optional chase argument to allow chasing our own team
|
||||
if ( op == OP_AI_CHASE || op == OP_AI_CHASE_WING || op == OP_AI_CHASE_SHIP_CLASS
|
||||
if (op == OP_AI_CHASE || op == OP_AI_CHASE_WING || op == OP_AI_CHASE_SHIP_CLASS || op == OP_AI_CHASE_SHIP_TYPE
|
||||
|| op == OP_AI_DISABLE_SHIP || op == OP_AI_DISABLE_SHIP_TACTICAL || op == OP_AI_DISARM_SHIP || op == OP_AI_DISARM_SHIP_TACTICAL ) {
|
||||
if (is_sexp_true(CDDDR(node)))
|
||||
aigp->flags.set(AI::Goal_Flags::Target_own_team);
|
||||
@@ -1192,6 +1319,7 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
|
||||
if (op == OP_AI_CHASE ||
|
||||
op == OP_AI_CHASE_WING ||
|
||||
op == OP_AI_CHASE_SHIP_CLASS ||
|
||||
op == OP_AI_CHASE_SHIP_TYPE ||
|
||||
op == OP_AI_DISABLE_SHIP ||
|
||||
op == OP_AI_DISABLE_SHIP_TACTICAL ||
|
||||
op == OP_AI_DISARM_SHIP ||
|
||||
@@ -1275,6 +1403,11 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
|
||||
nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MAX_GOAL_PRIORITY));
|
||||
_priority = MAX_GOAL_PRIORITY;
|
||||
}
|
||||
else if (_priority < MIN_GOAL_PRIORITY)
|
||||
{
|
||||
nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MIN_GOAL_PRIORITY));
|
||||
_priority = MIN_GOAL_PRIORITY;
|
||||
}
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
@@ -1295,12 +1428,9 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
|
||||
/* We now need to determine what the mode and submode values are*/
|
||||
switch( op )
|
||||
{
|
||||
case OP_AI_WAYPOINTS_ONCE:
|
||||
goalmode = AI_GOAL_WAYPOINTS_ONCE;
|
||||
priority = eval_priority_et_seq(CDDR(node));
|
||||
break;
|
||||
case OP_AI_WAYPOINTS:
|
||||
goalmode = AI_GOAL_WAYPOINTS;
|
||||
case OP_AI_WAYPOINTS_ONCE:
|
||||
goalmode = (op == OP_AI_WAYPOINTS_ONCE) ? AI_GOAL_WAYPOINTS_ONCE : AI_GOAL_WAYPOINTS;
|
||||
priority = eval_priority_et_seq(CDDR(node));
|
||||
break;
|
||||
case OP_AI_DESTROY_SUBSYS:
|
||||
@@ -1378,6 +1508,10 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
|
||||
priority = eval_priority_et_seq(CDDR(node));
|
||||
goalmode = AI_GOAL_CHASE_SHIP_CLASS;
|
||||
break;
|
||||
case OP_AI_CHASE_SHIP_TYPE:
|
||||
priority = eval_priority_et_seq(CDDR(node));
|
||||
goalmode = AI_GOAL_CHASE_SHIP_TYPE;
|
||||
break;
|
||||
case OP_AI_EVADE_SHIP:
|
||||
priority = eval_priority_et_seq(CDDR(node));
|
||||
goalmode = AI_GOAL_EVADE_SHIP;
|
||||
@@ -1392,7 +1526,7 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
|
||||
goalmode = (op == OP_AI_IGNORE) ? AI_GOAL_IGNORE : AI_GOAL_IGNORE_NEW;
|
||||
break;
|
||||
case OP_AI_FORM_ON_WING:
|
||||
priority = eval_priority_et_seq(-1, 99);
|
||||
priority = eval_priority_et_seq(CDDR(node), The_mission.ai_profile->default_form_on_wing_priority);
|
||||
goalmode = AI_GOAL_FORM_ON_WING;
|
||||
break;
|
||||
case OP_AI_FLY_TO_SHIP:
|
||||
@@ -1655,7 +1789,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
|
||||
if (!target_ship_entry || !target_ship_entry->has_shipp())
|
||||
return ai_achievability::NOT_ACHIEVABLE;
|
||||
|
||||
aigp->flags.set(AI::Goal_Flags::Goal_override);
|
||||
// the override flag is now set in the calling function, ai_mission_goal_achievable
|
||||
return ai_achievability::ACHIEVABLE;
|
||||
}
|
||||
|
||||
@@ -1687,7 +1821,20 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
|
||||
}
|
||||
return ai_achievability::NOT_KNOWN;
|
||||
}
|
||||
|
||||
// and similarly for chasing all ships of a certain ship type
|
||||
if (aigp->ai_mode == AI_GOAL_CHASE_SHIP_TYPE) {
|
||||
for (auto so : list_range(&Ship_obj_list)) {
|
||||
auto type_objp = &Objects[so->objnum];
|
||||
if (type_objp->type != OBJ_SHIP || type_objp->flags[Object::Object_Flags::Should_be_dead])
|
||||
continue;
|
||||
int ship_info_idx = Ships[type_objp->instance].ship_info_index;
|
||||
int class_type = Ship_info[ship_info_idx].class_type;
|
||||
if (class_type >= 0 && !strcmp(aigp->target_name, Ship_types[class_type].name)) {
|
||||
return ai_achievability::ACHIEVABLE;
|
||||
}
|
||||
}
|
||||
return ai_achievability::NOT_KNOWN;
|
||||
}
|
||||
|
||||
return_val = ai_achievability::SATISFIED;
|
||||
|
||||
@@ -1917,15 +2064,15 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
|
||||
// Goober5000 - see note at PURGE_GOALS_ALL_SHIPS... this is bizarre
|
||||
if ((status == SHIP_STATUS_ARRIVED) && !(aigp->flags[AI::Goal_Flags::Goals_purged]))
|
||||
{
|
||||
if (purge_goals_all_ships(aigp->ai_mode)) {
|
||||
if (causes_invalid_goal_purge_all_ships(aigp->ai_mode)) {
|
||||
ai_goal_purge_all_invalid_goals(aigp);
|
||||
aigp->flags.set(AI::Goal_Flags::Goals_purged);
|
||||
}
|
||||
else if (purge_goals_one_ship(aigp->ai_mode)) {
|
||||
else if (causes_invalid_goal_purge_one_ship(aigp->ai_mode)) {
|
||||
ai_goal_purge_invalid_goals(aigp, aip->goals, aip, -1);
|
||||
aigp->flags.set(AI::Goal_Flags::Goals_purged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we are docking, validate the docking indices on both ships. We might have to change names to indices.
|
||||
// only enter this calculation if the ship we are docking with has arrived. If the ship is gone, then
|
||||
@@ -2225,6 +2372,10 @@ void validate_mission_goals(int objnum, ai_info *aip)
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the status is achievable, and we want to set the override, set it
|
||||
if ( (state == ai_achievability::ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Want_override]))
|
||||
aigp->flags.set(AI::Goal_Flags::Goal_override);
|
||||
|
||||
// if the status is achievable, and the on_hold flag is set, clear the flagb
|
||||
if ( (state == ai_achievability::ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Goal_on_hold]) )
|
||||
aigp->flags.remove(AI::Goal_Flags::Goal_on_hold);
|
||||
@@ -2274,7 +2425,7 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
object *objp = &Objects[objnum];
|
||||
object *other_obj;
|
||||
ai_goal *current_goal;
|
||||
int wingnum, shipnum;
|
||||
int wingnum;
|
||||
int original_signature;
|
||||
|
||||
/* if (!stricmp(Ships[objp->instance].ship_name, "gtt comet")) {
|
||||
@@ -2349,14 +2500,34 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<ai_goal> current_goal_backup;
|
||||
auto current_goal_target_ship = current_goal->target_name ? ship_registry_get(current_goal->target_name) : nullptr;
|
||||
|
||||
if (current_goal->flags[AI::Goal_Flags::Clear_all_goals_first]) {
|
||||
// save the current goal before we wipe it out by clearing everything
|
||||
current_goal_backup.reset(new ai_goal(*current_goal));
|
||||
current_goal = current_goal_backup.get();
|
||||
|
||||
// stay-still, form-on-wing, and play-dead all clear their goals here...
|
||||
//
|
||||
// clear out the object's goals. Seems to me that if a ship is staying still for a purpose
|
||||
// then we need to clear everything out since there is not a real way to get rid of this goal
|
||||
//
|
||||
// for form on wing, we need to clear out all goals for this ship, and then call the form on wing AI code
|
||||
//
|
||||
// if a ship is playing dead, MWA says that it shouldn't try to do anything else.
|
||||
//
|
||||
// clearing out goals is okay here since we are now what mode to set this AI object to.
|
||||
|
||||
ai_clear_ship_goals(aip);
|
||||
}
|
||||
|
||||
switch ( current_goal->ai_mode ) {
|
||||
|
||||
case AI_GOAL_CHASE:
|
||||
if ( current_goal->target_name ) {
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert (shipnum != -1 ); // shouldn't get here if this is false!!!!
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
if (current_goal->target_name) {
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
} else
|
||||
other_obj = NULL; // we get this case when we tell ship to engage enemy!
|
||||
|
||||
@@ -2373,9 +2544,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
break;
|
||||
|
||||
case AI_GOAL_GUARD:
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert (shipnum != -1 ); // shouldn't get here if this is false!!!!
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
// shipnum and other_obj are the shipnumber and object pointer of the object that you should
|
||||
// guard.
|
||||
if (objp != other_obj) {
|
||||
@@ -2406,9 +2576,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
}
|
||||
|
||||
case AI_GOAL_DOCK: {
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert (shipnum != -1 ); // shouldn't get here if this is false!!!!
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
|
||||
// be sure that we have indices for docking points here! If we ever had names, they should
|
||||
// get fixed up in goal_achievable so that the points can be checked there for validity
|
||||
@@ -2418,24 +2587,22 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
}
|
||||
|
||||
case AI_GOAL_UNDOCK:
|
||||
// try to find the object which which this object is docked with. Use that object as the
|
||||
// try to find the object with which this object is docked. Use that object as the
|
||||
// "other object" for the undocking proceedure. If "other object" isn't found, then the undock
|
||||
// goal cannot continue. Spit out a warning and remove the goal.
|
||||
|
||||
// Goober5000 - do we have a specific ship to undock from?
|
||||
if ( current_goal->target_name != NULL )
|
||||
if (current_goal->target_name)
|
||||
{
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
|
||||
// hmm, perhaps he was destroyed
|
||||
if (shipnum == -1)
|
||||
if (!current_goal_target_ship || !current_goal_target_ship->has_objp())
|
||||
{
|
||||
other_obj = NULL;
|
||||
}
|
||||
// he exists... let's undock from him
|
||||
else
|
||||
{
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
}
|
||||
}
|
||||
// no specific ship
|
||||
@@ -2488,16 +2655,15 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
case AI_GOAL_DISABLE_SHIP_TACTICAL:
|
||||
case AI_GOAL_DISARM_SHIP:
|
||||
case AI_GOAL_DISARM_SHIP_TACTICAL: {
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert( shipnum >= 0 );
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp());
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
ai_attack_object( objp, other_obj);
|
||||
ai_set_attack_subsystem( objp, current_goal->ai_submode ); // submode stored the subsystem type
|
||||
|
||||
// don't protect-ship for tactical goals
|
||||
if (current_goal->ai_mode != AI_GOAL_DESTROY_SUBSYSTEM && current_goal->ai_mode != AI_GOAL_DISABLE_SHIP_TACTICAL && current_goal->ai_mode != AI_GOAL_DISARM_SHIP_TACTICAL) {
|
||||
if (aip->target_objnum != -1) {
|
||||
int class_type = Ship_info[Ships[shipnum].ship_info_index].class_type;
|
||||
int class_type = Ship_info[current_goal_target_ship->shipp()->ship_info_index].class_type;
|
||||
// Only protect if _not_ a capital ship. We don't want the Lucifer accidentally getting protected.
|
||||
if (class_type >= 0 && Ship_types[class_type].flags[Ship::Type_Info_Flags::AI_protected_on_cripple])
|
||||
Objects[aip->target_objnum].flags.set(Object::Object_Flags::Protected);
|
||||
@@ -2521,10 +2687,20 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
|
||||
// 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, nullptr, shipnum );
|
||||
{
|
||||
int ship_info_index = ship_info_lookup(current_goal->target_name);
|
||||
Assertion(ship_info_index >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!");
|
||||
ai_attack_object(objp, nullptr, ship_info_index);
|
||||
break;
|
||||
}
|
||||
// similarly for chase-ship-type
|
||||
case AI_GOAL_CHASE_SHIP_TYPE:
|
||||
{
|
||||
int class_type = ship_type_name_lookup(current_goal->target_name);
|
||||
Assertion(class_type >= 0, "The target of AI_GOAL_CHASE_SHIP_TYPE must refer to a valid ship type!");
|
||||
ai_attack_object(objp, nullptr, -1, class_type);
|
||||
break;
|
||||
}
|
||||
|
||||
case AI_GOAL_WARP: {
|
||||
mission_do_departure( objp, true );
|
||||
@@ -2532,46 +2708,29 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
}
|
||||
|
||||
case AI_GOAL_EVADE_SHIP:
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert( shipnum >= 0 );
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
ai_evade_object( objp, other_obj);
|
||||
break;
|
||||
|
||||
case AI_GOAL_STAY_STILL:
|
||||
// NOTE: goal clearing moved above this enclosing switch block
|
||||
// for now, ignore any other parameters!!!!
|
||||
// clear out the object's goals. Seems to me that if a ship is staying still for a purpose
|
||||
// then we need to clear everything out since there is not a real way to get rid of this goal
|
||||
// clearing out goals is okay here since we are now what mode to set this AI object to.
|
||||
ai_clear_ship_goals( aip );
|
||||
ai_stay_still( objp, NULL );
|
||||
break;
|
||||
|
||||
case AI_GOAL_PLAY_DEAD:
|
||||
// if a ship is playing dead, MWA says that it shouldn't try to do anything else.
|
||||
// clearing out goals is okay here since we are now what mode to set this AI object to.
|
||||
ai_clear_ship_goals( aip );
|
||||
aip->mode = AIM_PLAY_DEAD;
|
||||
aip->submode = -1;
|
||||
aip->submode_start_time = Missiontime;
|
||||
break;
|
||||
|
||||
case AI_GOAL_PLAY_DEAD_PERSISTENT:
|
||||
// same as above, but we don't clear out ship goals
|
||||
// NOTE: goal clearing moved above this enclosing switch block
|
||||
aip->mode = AIM_PLAY_DEAD;
|
||||
aip->submode = -1;
|
||||
aip->submode_start_time = Missiontime;
|
||||
break;
|
||||
|
||||
case AI_GOAL_FORM_ON_WING:
|
||||
// get the ship first, since we're going to wipe it out next
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert( shipnum >= 0 );
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
// for form on wing, we need to clear out all goals for this ship, and then call the form
|
||||
// on wing AI code
|
||||
// clearing out goals is okay here since we are now what mode to set this AI object to.
|
||||
ai_clear_ship_goals( aip );
|
||||
// NOTE: goal clearing moved above this enclosing switch block
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
ai_form_on_wing( objp, other_obj );
|
||||
break;
|
||||
|
||||
@@ -2580,9 +2739,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
case AI_GOAL_STAY_NEAR_SHIP:
|
||||
case AI_GOAL_FLY_TO_SHIP:
|
||||
{
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert( shipnum >= 0 );
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
float dist = current_goal->float_data; // How far away to stay from ship. Should be set in SEXP?
|
||||
int additional_data = current_goal->int_data; // Whether to target a particular point as if escorting
|
||||
ai_do_stay_near(objp, other_obj, dist, additional_data);
|
||||
@@ -2597,9 +2755,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
|
||||
break;
|
||||
|
||||
case AI_GOAL_REARM_REPAIR:
|
||||
shipnum = ship_name_lookup( current_goal->target_name );
|
||||
Assert( shipnum >= 0 );
|
||||
other_obj = &Objects[Ships[shipnum].objnum];
|
||||
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
|
||||
other_obj = current_goal_target_ship->objp();
|
||||
ai_rearm_repair( objp, current_goal->docker.index, other_obj, current_goal->dockee.index );
|
||||
break;
|
||||
|
||||
|
||||
+9
-6
@@ -47,9 +47,9 @@ enum class ai_goal_type
|
||||
enum ai_goal_mode : uint8_t
|
||||
{
|
||||
AI_GOAL_NONE = 0,
|
||||
AI_GOAL_PLACEHOLDER_1,
|
||||
AI_GOAL_SCHROEDINGER, // used for FRED when multiple ships are selected with different orders
|
||||
|
||||
AI_GOAL_CHASE, // per the original #define list, AI_GOAL_CHASE started at 2 (1<<1)
|
||||
AI_GOAL_CHASE,
|
||||
AI_GOAL_DOCK, // used for undocking as well
|
||||
AI_GOAL_WAYPOINTS,
|
||||
AI_GOAL_WAYPOINTS_ONCE,
|
||||
@@ -80,7 +80,8 @@ enum ai_goal_mode : uint8_t
|
||||
AI_GOAL_FLY_TO_SHIP,
|
||||
AI_GOAL_IGNORE_NEW,
|
||||
AI_GOAL_CHASE_SHIP_CLASS,
|
||||
AI_GOAL_PLAY_DEAD_PERSISTENT,
|
||||
AI_GOAL_CHASE_SHIP_TYPE,
|
||||
AI_GOAL_PLAY_DEAD_PERSISTENT, // Disables subsystem rotation/translation among other things but there is a carveout for that in the lab in ship_move_subsystems() and ai_process_subobjects()
|
||||
AI_GOAL_LUA,
|
||||
AI_GOAL_DISARM_SHIP_TACTICAL,
|
||||
AI_GOAL_DISABLE_SHIP_TACTICAL,
|
||||
@@ -103,7 +104,7 @@ inline bool ai_goal_is_disable_or_disarm(ai_goal_mode ai_mode)
|
||||
}
|
||||
inline bool ai_goal_is_specific_chase(ai_goal_mode ai_mode)
|
||||
{
|
||||
return ai_mode == AI_GOAL_CHASE || ai_mode == AI_GOAL_CHASE_WING || ai_mode == AI_GOAL_CHASE_SHIP_CLASS;
|
||||
return ai_mode == AI_GOAL_CHASE || ai_mode == AI_GOAL_CHASE_WING || ai_mode == AI_GOAL_CHASE_SHIP_CLASS || ai_mode == AI_GOAL_CHASE_SHIP_TYPE;
|
||||
}
|
||||
|
||||
enum class ai_achievability { ACHIEVABLE, NOT_ACHIEVABLE, NOT_KNOWN, SATISFIED };
|
||||
@@ -152,6 +153,8 @@ typedef struct ai_goal {
|
||||
|
||||
extern void ai_goal_reset(ai_goal *aigp, bool adding_goal = false, ai_goal_mode ai_mode = AI_GOAL_NONE, int ai_submode = -1, ai_goal_type type = ai_goal_type::INVALID);
|
||||
|
||||
// Reset all path points. Used in the ship lab. Missions clean up path points with the garbage collector in garbage_collect_path_points()
|
||||
extern void reset_ai_path_points();
|
||||
|
||||
typedef flag_def_list_templated<ai_goal_mode> ai_goal_list;
|
||||
|
||||
@@ -185,8 +188,8 @@ extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
|
||||
extern void ai_remove_wing_goal_sexp( int sexp, wing *wingp );
|
||||
|
||||
// adds goals to ships/sings through player orders
|
||||
extern void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, ai_info* aip, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
|
||||
extern void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, int wingnum, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
|
||||
extern void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, ai_info* aip, int int_data = 0, float float_data = 0.0f, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
|
||||
extern void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, int wingnum, int int_data = 0, float float_data = 0.0f, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
|
||||
|
||||
extern void ai_remove_ship_goal( ai_info *aip, int index );
|
||||
extern void ai_clear_ship_goals( ai_info *aip );
|
||||
|
||||
+11
-13
@@ -1089,9 +1089,11 @@ int find_turret_enemy(const ship_subsys *turret_subsys, int objnum, const vec3d
|
||||
int target_objnum = aip->target_objnum;
|
||||
|
||||
if (Objects[target_objnum].signature == aip->target_signature) {
|
||||
if (iff_matches_mask(Ships[Objects[target_objnum].instance].team, enemy_team_mask)) {
|
||||
if ( !(Objects[target_objnum].flags[Object::Object_Flags::Protected]) ) { // check this flag as well
|
||||
// nprintf(("AI", "Frame %i: Object %i resuming goal of object %i\n", AI_FrameCount, objnum, target_objnum));
|
||||
ship* target_shipp = &Ships[Objects[target_objnum].instance];
|
||||
if (target_shipp && iff_matches_mask(target_shipp->team, enemy_team_mask)) {
|
||||
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) { // check this flag as well
|
||||
// nprintf(("AI", "Frame %i: Object %i resuming goal of object %i\n", AI_FrameCount, objnum,
|
||||
// target_objnum));
|
||||
return target_objnum;
|
||||
}
|
||||
}
|
||||
@@ -1559,7 +1561,9 @@ void turret_set_next_fire_timestamp(int weapon_num, const weapon_info *wip, ship
|
||||
float burst_shots_mult = wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::BURST_SHOTS_MULT, launch_curve_data);
|
||||
int burst_shots = MAX(fl2i(i2fl(base_burst_shots) * burst_shots_mult) - 1, 0);
|
||||
|
||||
if (burst_shots > turret->weapons.burst_counter[weapon_num]) {
|
||||
bool burst = burst_shots > turret->weapons.burst_counter[weapon_num];
|
||||
|
||||
if (burst) {
|
||||
wait *= wip->burst_delay;
|
||||
wait *= wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::BURST_DELAY_MULT, launch_curve_data);
|
||||
turret->weapons.burst_counter[weapon_num]++;
|
||||
@@ -1659,7 +1663,7 @@ void turret_set_next_fire_timestamp(int weapon_num, const weapon_info *wip, ship
|
||||
wait *= frand_range(0.9f, 1.1f);
|
||||
}
|
||||
|
||||
if(turret->rof_scaler != 1.0f)
|
||||
if(turret->rof_scaler != 1.0f && !(burst && turret->system_info->flags[Model::Subsystem_Flags::Burst_ignores_RoF_Mult]))
|
||||
wait /= get_adjusted_turret_rof(turret);
|
||||
|
||||
(*fs_dest) = timestamp((int)wait);
|
||||
@@ -2038,14 +2042,11 @@ bool turret_fire_weapon(int weapon_num,
|
||||
}
|
||||
//spawn particle effect
|
||||
auto particleSource = particle::ParticleManager::get()->createSource(wip->muzzle_effect);
|
||||
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[parent_ship->objnum], turret->system_info->turret_gun_sobj, turret->turret_next_fire_pos));
|
||||
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[parent_ship->objnum], turret->system_info->turret_gun_sobj, turret->turret_next_fire_pos, false));
|
||||
particleSource->setTriggerRadius(objp->radius * radius_mult);
|
||||
particleSource->setTriggerVelocity(vm_vec_mag_quick(&objp->phys_info.vel));
|
||||
particleSource->finishCreation();
|
||||
}
|
||||
else if (wip->muzzle_flash >= 0) {
|
||||
mflash_create(firing_pos, firing_vec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
|
||||
}
|
||||
|
||||
// in multiplayer (and the master), then send a turret fired packet.
|
||||
if ( MULTIPLAYER_MASTER && (weapon_objnum != -1) ) {
|
||||
@@ -2162,13 +2163,10 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
|
||||
if (Weapon_info[tsi->weapon_class].muzzle_effect.isValid()) {
|
||||
//spawn particle effect
|
||||
auto particleSource = particle::ParticleManager::get()->createSource(Weapon_info[tsi->weapon_class].muzzle_effect);
|
||||
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[tsi->parent_objnum], tsi->turret->system_info->turret_gun_sobj, tsi->turret->turret_next_fire_pos - 1));
|
||||
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[tsi->parent_objnum], tsi->turret->system_info->turret_gun_sobj, tsi->turret->turret_next_fire_pos - 1, false));
|
||||
particleSource->setTriggerRadius(Objects[weapon_objnum].radius);
|
||||
particleSource->finishCreation();
|
||||
}
|
||||
else if (Weapon_info[tsi->weapon_class].muzzle_flash >= 0) {
|
||||
mflash_create(&turret_pos, &turret_fvec, &Objects[tsi->parent_objnum].phys_info, Weapon_info[tsi->weapon_class].muzzle_flash);
|
||||
}
|
||||
|
||||
// maybe sound
|
||||
if ( Weapon_info[tsi->weapon_class].launch_snd.isValid() ) {
|
||||
|
||||
+126
-68
@@ -32,8 +32,9 @@
|
||||
#include "object/objcollide.h"
|
||||
#include "object/object.h"
|
||||
#include "parse/parselo.h"
|
||||
#include "particle/ParticleEffect.h"
|
||||
#include "scripting/global_hooks.h"
|
||||
#include "particle/particle.h"
|
||||
#include "particle/hosts/EffectHostVector.h"
|
||||
#include "render/3d.h"
|
||||
#include "ship/ship.h"
|
||||
#include "ship/shipfx.h"
|
||||
@@ -63,8 +64,7 @@ asteroid Asteroids[MAX_ASTEROIDS];
|
||||
asteroid_field Asteroid_field;
|
||||
|
||||
|
||||
static int Asteroid_impact_explosion_ani;
|
||||
static float Asteroid_impact_explosion_radius;
|
||||
static particle::ParticleEffectHandle Asteroid_impact_explosion_ani;
|
||||
char Asteroid_icon_closeup_model[NAME_LENGTH];
|
||||
vec3d Asteroid_icon_closeup_position;
|
||||
float Asteroid_icon_closeup_zoom;
|
||||
@@ -824,18 +824,7 @@ void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, SCP_vec
|
||||
*/
|
||||
void asteroid_level_init()
|
||||
{
|
||||
Asteroid_field.num_initial_asteroids = 0; // disable asteroid field by default.
|
||||
Asteroid_field.speed = 0.0f;
|
||||
vm_vec_make(&Asteroid_field.min_bound, -1000.0f, -1000.0f, -1000.0f);
|
||||
vm_vec_make(&Asteroid_field.max_bound, 1000.0f, 1000.0f, 1000.0f);
|
||||
vm_vec_make(&Asteroid_field.inner_min_bound, -500.0f, -500.0f, -500.0f);
|
||||
vm_vec_make(&Asteroid_field.inner_max_bound, 500.0f, 500.0f, 500.0f);
|
||||
Asteroid_field.has_inner_bound = false;
|
||||
Asteroid_field.field_type = FT_ACTIVE;
|
||||
Asteroid_field.debris_genre = DG_ASTEROID;
|
||||
Asteroid_field.field_debris_type.clear();
|
||||
Asteroid_field.field_asteroid_type.clear();
|
||||
Asteroid_field.target_names.clear();
|
||||
Asteroid_field = {};
|
||||
|
||||
if (!Fred_running)
|
||||
{
|
||||
@@ -1326,9 +1315,9 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
model_get_moving_submodel_list(submodel_vector, heavy_obj);
|
||||
|
||||
// turn off all moving submodels, collide against only 1 at a time.
|
||||
// turn off collision detection for all moving submodels
|
||||
mc.collision_checked.assign(pm->n_models, 0);
|
||||
for (auto submodel: submodel_vector) {
|
||||
pmi->submodel[submodel].collision_checked = true;
|
||||
mc.collision_checked[submodel] = true;
|
||||
}
|
||||
|
||||
// Only check single submodel now, since children of moving submodels are handled as moving as well
|
||||
@@ -1336,10 +1325,8 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
|
||||
// check each submodel in turn
|
||||
for (auto submodel: submodel_vector) {
|
||||
auto smi = &pmi->submodel[submodel];
|
||||
|
||||
// turn on just one submodel for collision test
|
||||
smi->collision_checked = false;
|
||||
mc.collision_checked[submodel] = false;
|
||||
|
||||
// find the start and end positions of the sphere in submodel RF
|
||||
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
|
||||
@@ -1371,9 +1358,12 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
}
|
||||
}
|
||||
// Don't look at this submodel again
|
||||
smi->collision_checked = true;
|
||||
mc.collision_checked[submodel] = true;
|
||||
}
|
||||
|
||||
// Clear collision_checked before base model pass so it auto-inits fresh
|
||||
mc.collision_checked.clear();
|
||||
|
||||
}
|
||||
|
||||
// Now complete base model collision checks that do not take into account rotating submodels.
|
||||
@@ -1705,37 +1695,57 @@ static void asteroid_do_area_effect(object *asteroid_objp)
|
||||
*/
|
||||
void asteroid_hit( object * pasteroid_obj, object * other_obj, vec3d * hitpos, float damage, vec3d* force )
|
||||
{
|
||||
float explosion_life;
|
||||
asteroid *asp;
|
||||
|
||||
asp = &Asteroids[pasteroid_obj->instance];
|
||||
|
||||
asteroid *asp = &Asteroids[pasteroid_obj->instance];
|
||||
asteroid_info *asip = &Asteroid_info[Asteroids[pasteroid_obj->instance].asteroid_type];
|
||||
|
||||
if (pasteroid_obj->flags[Object::Object_Flags::Should_be_dead]){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ( MULTIPLAYER_MASTER ){
|
||||
send_asteroid_hit( pasteroid_obj, other_obj, hitpos, damage, force );
|
||||
}
|
||||
|
||||
|
||||
if (hitpos && force && The_mission.ai_profile->flags[AI::Profile_Flags::Whackable_asteroids]) {
|
||||
vec3d rel_hit_pos = *hitpos - pasteroid_obj->pos;
|
||||
physics_calculate_and_apply_whack(force, &rel_hit_pos, &pasteroid_obj->phys_info, &pasteroid_obj->orient, &pasteroid_obj->phys_info.I_body_inv);
|
||||
pasteroid_obj->phys_info.desired_vel = pasteroid_obj->phys_info.vel;
|
||||
}
|
||||
|
||||
|
||||
pasteroid_obj->hull_strength -= damage;
|
||||
|
||||
|
||||
if (pasteroid_obj->hull_strength < 0.0f) {
|
||||
if ( !asp->final_death_time.isValid() ) {
|
||||
int play_loud_collision = 0;
|
||||
|
||||
float explosion_life = 1.f;
|
||||
int breakup_timestamp;
|
||||
|
||||
Assertion(!asip->end_particles.isValid() || asip->breakup_delay.has_value(), "Asteroid %s has end particles but no breakup delay. Parsing should not have allowed this!", asip->name);
|
||||
|
||||
explosion_life = asteroid_create_explosion(pasteroid_obj);
|
||||
if (asip->end_particles.isValid()) {
|
||||
auto source = particle::ParticleManager::get()->createSource(asip->end_particles);
|
||||
|
||||
// Use the position since the asteroid is going to be invalid soon
|
||||
auto host = std::make_unique<EffectHostVector>(pasteroid_obj->pos, pasteroid_obj->orient, pasteroid_obj->phys_info.vel);
|
||||
host->setRadius(pasteroid_obj->radius);
|
||||
source->setHost(std::move(host));
|
||||
source->setNormal(pasteroid_obj->orient.vec.uvec);
|
||||
source->finishCreation();
|
||||
} else {
|
||||
explosion_life = asteroid_create_explosion(pasteroid_obj);
|
||||
}
|
||||
|
||||
if (asip->breakup_delay.has_value()) {
|
||||
breakup_timestamp = fl2i(*asip->breakup_delay * MILLISECONDS_PER_SECOND);
|
||||
} else {
|
||||
breakup_timestamp = fl2i((explosion_life * MILLISECONDS_PER_SECOND) / 5.f);
|
||||
}
|
||||
|
||||
asteroid_explode_sound(pasteroid_obj, asp->asteroid_type, play_loud_collision);
|
||||
asteroid_do_area_effect(pasteroid_obj);
|
||||
|
||||
asp->final_death_time = _timestamp( fl2i(explosion_life*MILLISECONDS_PER_SECOND)/5 ); // Wait till 30% of vclip time before breaking the asteroid up.
|
||||
asp->final_death_time = _timestamp( breakup_timestamp ); // Wait till 20% of vclip time before breaking the asteroid up, or use the specified delay
|
||||
if ( hitpos ) {
|
||||
asp->death_hit_pos = *hitpos;
|
||||
} else {
|
||||
@@ -1751,8 +1761,10 @@ void asteroid_hit( object * pasteroid_obj, object * other_obj, vec3d * hitpos, f
|
||||
weapon_info *wip;
|
||||
wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
|
||||
// If the weapon didn't play any impact animation, play custom asteroid impact animation
|
||||
if (!wip->impact_weapon_expl_effect.isValid()) {
|
||||
particle::create( hitpos, &vmd_zero_vector, 0.0f, Asteroid_impact_explosion_radius, Asteroid_impact_explosion_ani );
|
||||
if (!wip->impact_weapon_expl_effect.isValid() && Asteroid_impact_explosion_ani.isValid()) {
|
||||
auto source = particle::ParticleManager::get()->createSource(Asteroid_impact_explosion_ani);
|
||||
source->setHost(std::make_unique<EffectHostVector>(*hitpos, vmd_identity_matrix, vmd_zero_vector));
|
||||
source->finishCreation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2313,7 +2325,7 @@ static void asteroid_parse_section()
|
||||
}
|
||||
|
||||
if (optional_string("$Detail distance:")) {
|
||||
asteroid_p->num_detail_levels = (int)stuff_int_list(asteroid_p->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
|
||||
asteroid_p->num_detail_levels = sz2i(stuff_int_list(asteroid_p->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, ParseLookupType::RAW_INTEGER_TYPE));
|
||||
}
|
||||
|
||||
if (optional_string("$Max Speed:")) {
|
||||
@@ -2330,13 +2342,25 @@ static void asteroid_parse_section()
|
||||
asteroid_p->damage_type_idx_sav = damage_type_add(buf);
|
||||
asteroid_p->damage_type_idx = asteroid_p->damage_type_idx_sav;
|
||||
}
|
||||
|
||||
if(optional_string("$Explosion Animations:")){
|
||||
stuff_fireball_index_list(asteroid_p->explosion_bitmap_anims, asteroid_p->name);
|
||||
|
||||
if (optional_string("$Explosion Effect:")) {
|
||||
asteroid_p->end_particles = particle::util::parseEffect(asteroid_p->name);
|
||||
} else {
|
||||
if(optional_string("$Explosion Animations:")){
|
||||
stuff_fireball_index_list(asteroid_p->explosion_bitmap_anims, asteroid_p->name);
|
||||
}
|
||||
|
||||
if (optional_string("$Explosion Radius Mult:")) {
|
||||
stuff_float(&asteroid_p->fireball_radius_multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
if (optional_string("$Explosion Radius Mult:")) {
|
||||
stuff_float(&asteroid_p->fireball_radius_multiplier);
|
||||
if (optional_string("$Breakup Delay:")) {
|
||||
stuff_float(&asteroid_p->breakup_delay.emplace());
|
||||
}
|
||||
|
||||
if (asteroid_p->end_particles.isValid() && !asteroid_p->breakup_delay.has_value()) {
|
||||
error_display(0, "Asteroid %s has an explosion effect but no breakup delay!", asteroid_p->name);
|
||||
}
|
||||
|
||||
if (optional_string("$Expl inner rad:")){
|
||||
@@ -2461,18 +2485,52 @@ static void asteroid_parse_tbl(const char* filename)
|
||||
|
||||
required_string("#End");
|
||||
|
||||
if (optional_string("$Impact Explosion:")) {
|
||||
char impact_ani_file[MAX_FILENAME_LEN];
|
||||
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
|
||||
|
||||
if (VALID_FNAME(impact_ani_file)) {
|
||||
int num_frames;
|
||||
Asteroid_impact_explosion_ani = bm_load_animation(impact_ani_file, &num_frames, nullptr, nullptr, nullptr, true);
|
||||
}
|
||||
if (optional_string("$Impact Explosion Effect:")) {
|
||||
Asteroid_impact_explosion_ani = particle::util::parseEffect();
|
||||
}
|
||||
else {
|
||||
char impact_ani_file[MAX_FILENAME_LEN] = {0};
|
||||
float Asteroid_impact_explosion_radius = 0.0f;
|
||||
int num_frames;
|
||||
|
||||
if (optional_string("$Impact Explosion Radius:")) {
|
||||
stuff_float(&Asteroid_impact_explosion_radius);
|
||||
if (optional_string("$Impact Explosion:")) {
|
||||
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
|
||||
}
|
||||
if (optional_string("$Impact Explosion Radius:")) {
|
||||
stuff_float(&Asteroid_impact_explosion_radius);
|
||||
}
|
||||
|
||||
if(VALID_FNAME(impact_ani_file)) {
|
||||
Asteroid_impact_explosion_ani = particle::ParticleManager::get()->addEffect(particle::ParticleEffect(
|
||||
"", //Name
|
||||
::util::UniformFloatRange(1.f), //Particle num
|
||||
particle::ParticleEffect::Duration::ONETIME, //Single Particle Emission
|
||||
::util::UniformFloatRange(), //No duration
|
||||
::util::UniformFloatRange(-1.f), //Single particle only
|
||||
particle::ParticleEffect::ShapeDirection::ALIGNED, //Particle direction
|
||||
::util::UniformFloatRange(), //Velocity Inherit
|
||||
false, //Velocity Inherit absolute?
|
||||
nullptr, //Velocity volume
|
||||
::util::UniformFloatRange(), //Velocity volume multiplier
|
||||
particle::ParticleEffect::VelocityScaling::NONE, //Velocity directional scaling
|
||||
std::nullopt, //Orientation-based velocity
|
||||
std::nullopt, //Position-based velocity
|
||||
nullptr, //Position volume
|
||||
particle::ParticleEffectHandle::invalid(), //Trail
|
||||
1.f, //Chance
|
||||
false, //Affected by detail
|
||||
-1.f, //Culling range multiplier
|
||||
false, //Disregard Animation Length. Must be true for everything using particle::Anim_bitmap_X
|
||||
false, //Don't reverse animation
|
||||
false, //parent local
|
||||
false, //ignore velocity inherit if parented
|
||||
false, //position velocity inherit absolute?
|
||||
std::nullopt, //Local velocity offset
|
||||
std::nullopt, //Local offset
|
||||
::util::UniformFloatRange(-1.f), //Lifetime
|
||||
::util::UniformFloatRange(Asteroid_impact_explosion_radius), //Radius
|
||||
bm_load_animation(impact_ani_file, &num_frames, nullptr, nullptr, nullptr, true))); //Bitmap
|
||||
}
|
||||
}
|
||||
|
||||
if (optional_string("$Briefing Icon Closeup Model:")) {
|
||||
@@ -2507,24 +2565,21 @@ int get_asteroid_index(const char* asteroid_name)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// For FRED. Gets a list of unique asteroid subtype names
|
||||
SCP_vector<SCP_string> get_list_valid_asteroid_subtypes()
|
||||
// Returns the list of unique asteroid subtype names.
|
||||
// List is cached after the first call since Asteroid_info cannot change during an engine instance.
|
||||
const SCP_vector<SCP_string>& get_list_valid_asteroid_subtypes()
|
||||
{
|
||||
SCP_vector<SCP_string> list;
|
||||
static SCP_vector<SCP_string> list;
|
||||
|
||||
for (const auto& this_asteroid : Asteroid_info) {
|
||||
if (this_asteroid.type != ASTEROID_TYPE_DEBRIS) {
|
||||
for (const auto& subtype : this_asteroid.subtypes) {
|
||||
bool exists = false;
|
||||
for (const auto& entry : list) {
|
||||
if (subtype.type_name == entry) {
|
||||
exists = true;
|
||||
if (list.empty()) {
|
||||
for (const auto& this_asteroid : Asteroid_info) {
|
||||
if (this_asteroid.type != ASTEROID_TYPE_DEBRIS) {
|
||||
for (const auto& subtype : this_asteroid.subtypes) {
|
||||
// Only add unique names
|
||||
if (std::find(list.begin(), list.end(), subtype.type_name) == list.end()) {
|
||||
list.push_back(subtype.type_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
list.push_back(subtype.type_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2532,6 +2587,7 @@ SCP_vector<SCP_string> get_list_valid_asteroid_subtypes()
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
static void verify_asteroid_splits()
|
||||
{
|
||||
|
||||
@@ -2622,8 +2678,7 @@ static void verify_asteroid_list()
|
||||
*/
|
||||
void asteroid_init()
|
||||
{
|
||||
Asteroid_impact_explosion_ani = -1;
|
||||
Asteroid_impact_explosion_radius = 20.0; // retail value
|
||||
Asteroid_impact_explosion_ani = particle::ParticleEffectHandle::invalid();
|
||||
Asteroid_icon_closeup_model[0] = '\0';
|
||||
vm_vec_make(&Asteroid_icon_closeup_position, 0.0f, 0.0f, -334.0f); // magic numbers from retail
|
||||
Asteroid_icon_closeup_zoom = 0.5f; // magic number from retail
|
||||
@@ -2644,8 +2699,11 @@ void asteroid_init()
|
||||
// now that Asteroid_info is filled in we can verify the asteroid splits and set their indecies
|
||||
verify_asteroid_splits();
|
||||
|
||||
if (Asteroid_impact_explosion_ani == -1) {
|
||||
Error(LOCATION, "Missing valid asteroid impact explosion definition in asteroid.tbl!");
|
||||
if (!Asteroid_impact_explosion_ani.isValid()) {
|
||||
// this will always be missing on a standalone
|
||||
if ( !Is_standalone ) {
|
||||
Error(LOCATION, "Missing valid asteroid impact explosion definition in asteroid.tbl!");
|
||||
}
|
||||
}
|
||||
|
||||
if (Asteroid_icon_closeup_model[0] == '\0')
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "globalincs/pstypes.h"
|
||||
#include "object/object_flags.h"
|
||||
#include "io/timer.h"
|
||||
#include "particle/ParticleSource.h"
|
||||
#include "math/vecmat.h"
|
||||
|
||||
class object;
|
||||
class polymodel;
|
||||
@@ -79,7 +81,9 @@ public:
|
||||
float initial_asteroid_strength; // starting strength of asteroid
|
||||
SCP_vector< asteroid_split_info > split_info;
|
||||
SCP_vector<int> explosion_bitmap_anims;
|
||||
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
|
||||
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
|
||||
particle::ParticleEffectHandle end_particles;
|
||||
std::optional<float> breakup_delay;
|
||||
SCP_string display_name; // only used for hud targeting display and for debris
|
||||
float spawn_weight; // debris only, relative proportion to spawn compared to other types in its asteroid field
|
||||
float gravity_const; // multiplier for mission gravity
|
||||
@@ -90,7 +94,7 @@ public:
|
||||
rotational_vel_multiplier(1), damage_type_idx(0),
|
||||
damage_type_idx_sav( -1 ), inner_rad( 0 ), outer_rad( 0 ),
|
||||
damage( 0 ), blast( 0 ), initial_asteroid_strength( 0 ),
|
||||
fireball_radius_multiplier( -1 ), spawn_weight( 1 ), gravity_const( 0 )
|
||||
fireball_radius_multiplier( -1 ), end_particles( particle::ParticleEffectHandle::invalid() ), breakup_delay( std::nullopt ), spawn_weight( 1 ), gravity_const( 0 )
|
||||
{
|
||||
name[ 0 ] = 0;
|
||||
display_name = "";
|
||||
@@ -130,7 +134,7 @@ typedef enum {
|
||||
FT_PASSIVE
|
||||
} field_type_t;
|
||||
|
||||
typedef struct asteroid_field {
|
||||
struct asteroid_field {
|
||||
vec3d min_bound; // Minimum range of field.
|
||||
vec3d max_bound; // Maximum range of field.
|
||||
float bound_rad;
|
||||
@@ -147,7 +151,24 @@ typedef struct asteroid_field {
|
||||
bool enhanced_visibility_checks; // if true then range checks are overridden for spawning and wrapping asteroids in the field
|
||||
|
||||
SCP_vector<SCP_string> target_names; // default retail behavior is to just throw at the first big ship in the field
|
||||
} asteroid_field;
|
||||
|
||||
asteroid_field()
|
||||
{
|
||||
num_initial_asteroids = 0; // disable the field by default
|
||||
speed = 0.0f;
|
||||
vm_vec_make(&min_bound, -1000.0f, -1000.0f, -1000.0f);
|
||||
vm_vec_make(&max_bound, 1000.0f, 1000.0f, 1000.0f);
|
||||
vm_vec_make(&inner_min_bound, -500.0f, -500.0f, -500.0f);
|
||||
vm_vec_make(&inner_max_bound, 500.0f, 500.0f, 500.0f);
|
||||
has_inner_bound = false;
|
||||
field_type = FT_ACTIVE;
|
||||
debris_genre = DG_ASTEROID;
|
||||
enhanced_visibility_checks = false;
|
||||
bound_rad = 0.0f;
|
||||
vel = ZERO_VECTOR;
|
||||
// the vectors default-construct to empty
|
||||
}
|
||||
};
|
||||
|
||||
extern SCP_vector< asteroid_info > Asteroid_info;
|
||||
extern asteroid Asteroids[MAX_ASTEROIDS];
|
||||
@@ -179,7 +200,8 @@ void asteroid_show_brackets();
|
||||
void asteroid_target_closest_danger();
|
||||
void asteroid_add_target(object* objp);
|
||||
int get_asteroid_index(const char* asteroid_name);
|
||||
SCP_vector<SCP_string> get_list_valid_asteroid_subtypes();
|
||||
const SCP_vector<SCP_string>& get_list_valid_asteroid_subtypes();
|
||||
int get_asteroid_subtype_index_by_name(const SCP_string& name, int asteroid_idx);
|
||||
|
||||
// extern for the lab
|
||||
void asteroid_load(int asteroid_info_index, int asteroid_subtype);
|
||||
|
||||
@@ -135,6 +135,7 @@ void bm_close();
|
||||
#define BMP_FLAG_RENDER_TARGET_DYNAMIC (1<<1)
|
||||
#define BMP_FLAG_CUBEMAP (1<<2)
|
||||
#define BMP_FLAG_RENDER_TARGET_MIPMAP (1<<3)
|
||||
#define BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT (1<<4)
|
||||
|
||||
/**
|
||||
* @brief Allocates memory for the given handle.
|
||||
|
||||
@@ -1294,6 +1294,9 @@ float cam_get_bbox_dist(const object* viewer_obj, float preferred_distance, cons
|
||||
return preferred_distance;
|
||||
|
||||
polymodel* pm = object_get_model(viewer_obj);
|
||||
if (pm == nullptr)
|
||||
return preferred_distance;
|
||||
|
||||
vec3d adjusted_bbox;
|
||||
const vec3d* cam_vec = &cam_orient->vec.fvec;
|
||||
|
||||
|
||||
@@ -902,7 +902,7 @@ static CFILE *cf_open_fill_cfblock(const char* source, int line, const char* ori
|
||||
cfp->source_file = source;
|
||||
cfp->line_num = line;
|
||||
|
||||
int pos = ftell(fp);
|
||||
auto pos = ftell(fp);
|
||||
if(pos == -1L)
|
||||
pos = 0;
|
||||
cf_init_lowlevel_read_code(cfp,0,filelength(fileno(fp)), 0 );
|
||||
|
||||
@@ -143,7 +143,7 @@ void lz41_load_offsets(CFILE* cf)
|
||||
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);
|
||||
fso_fseek(cf, static_cast<int>( ( 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);
|
||||
|
||||
@@ -534,6 +534,7 @@ cmdline_parm luadev_arg("-luadev", "Make lua errors non-fatal", AT_NONE); // Cmd
|
||||
cmdline_parm override_arg("-override_data", "Enable override directory", AT_NONE); // Cmdline_override_data
|
||||
cmdline_parm imgui_debug_arg("-imgui_debug", nullptr, AT_NONE);
|
||||
cmdline_parm vulkan("-vulkan", nullptr, AT_NONE);
|
||||
cmdline_parm multithreading("-threads", nullptr, AT_INT);
|
||||
|
||||
char *Cmdline_start_mission = NULL;
|
||||
int Cmdline_dis_collisions = 0;
|
||||
@@ -572,6 +573,7 @@ bool Cmdline_lua_devmode = false;
|
||||
bool Cmdline_override_data = false;
|
||||
bool Cmdline_show_imgui_debug = false;
|
||||
bool Cmdline_vulkan = false;
|
||||
int Cmdline_multithreading = 1;
|
||||
|
||||
// Other
|
||||
cmdline_parm get_flags_arg(GET_FLAGS_STRING, "Output the launcher flags file", AT_STRING);
|
||||
@@ -2407,6 +2409,10 @@ bool SetCmdlineParams()
|
||||
}
|
||||
}
|
||||
|
||||
if (multithreading.found()) {
|
||||
Cmdline_multithreading = abs(multithreading.get_int());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ extern bool Cmdline_lua_devmode;
|
||||
extern bool Cmdline_override_data;
|
||||
extern bool Cmdline_show_imgui_debug;
|
||||
extern bool Cmdline_vulkan;
|
||||
extern int Cmdline_multithreading;
|
||||
|
||||
enum class WeaponSpewType { NONE = 0, STANDARD, ALL };
|
||||
extern WeaponSpewType Cmdline_spew_weapon_stats;
|
||||
|
||||
@@ -1298,7 +1298,8 @@ bool control_config_accept(bool API_Access)
|
||||
cstr = popup_input(flags,
|
||||
XSTR( "Confirm new custom preset name.\n\nThe name must not be empty or a default preset.\n\n Press [Enter] to accept, [Esc] to abort to config menu.", 1867),
|
||||
32 - 6,
|
||||
default_string.c_str());
|
||||
default_string.c_str(),
|
||||
"-_");
|
||||
if (cstr == nullptr) {
|
||||
// Abort
|
||||
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
||||
@@ -2084,7 +2085,6 @@ int control_config_bind_key_on_frame(int ctrl, selItem item, bool API_Access)
|
||||
|
||||
if (!done && bind) {
|
||||
if (!Axis_override.empty()) {
|
||||
control_config_bind(ctrl, Axis_override, item, API_Access);
|
||||
control_config_bind(ctrl, Axis_override, item, API_Access);
|
||||
done = true;
|
||||
strcpy_s(bound_string, Axis_override.textify().c_str());
|
||||
@@ -2802,8 +2802,22 @@ void control_check_indicate()
|
||||
Control_check_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a control is being used by the player this frame by checking the bound control input bindings.
|
||||
*
|
||||
* @param[in] id The IoActionId of the control to check
|
||||
* @param[in] key DO NOT USE. Key combination acquired from game_poll(), or -1 to check against a previous poll
|
||||
*
|
||||
* @returns 0 If the control was disabled by a mod, or
|
||||
* @returns 0 If locked by a script, or
|
||||
* @returns 0 If controls need to be ignored for multiplayer, or
|
||||
* @returns 0 If the control wasn't used, or
|
||||
* @returns 1 If the control was used
|
||||
*/
|
||||
int check_control_used(int id, int key)
|
||||
{
|
||||
// Make sure mouse_down() is only called once during any logic path,
|
||||
// or the mousewheel may be decayed multiple times!!
|
||||
int mask;
|
||||
static int last_key = 0;
|
||||
auto & item = Control_config[id];
|
||||
@@ -2827,8 +2841,8 @@ int check_control_used(int id, int key)
|
||||
|
||||
if (item.type == CC_TYPE_CONTINUOUS) {
|
||||
|
||||
// this is awful, need to make a reverse lookup table to do button -> control instead of this control -> button
|
||||
// nonsense.
|
||||
// this is awful, need to make a reverse lookup table to do
|
||||
// button -> control instead of this control -> button nonsense.
|
||||
if ((joy_down(item.first) || joy_down_count(item.first, 1)) ||
|
||||
(joy_down(item.second) || joy_down_count(item.second, 1))) {
|
||||
// Joy button bound to this control was pressed, control activated
|
||||
@@ -2838,7 +2852,7 @@ int check_control_used(int id, int key)
|
||||
|
||||
if ((mouse_down(item.first) || mouse_down_count(item.first, 1)) ||
|
||||
(mouse_down(item.second) || mouse_down_count(item.second, 1))) {
|
||||
// Joy button bound to this control was pressed, control activated
|
||||
// Mouse button bound to this control was pressed, control activated
|
||||
control_used(id);
|
||||
return 1;
|
||||
}
|
||||
@@ -2884,6 +2898,15 @@ int check_control_used(int id, int key)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// special case to allow actual mouse wheel to work with trigger controls --wookieejedi
|
||||
if (item.type == CC_TYPE_TRIGGER) {
|
||||
if ( mouse_down(item.first, true) || mouse_down(item.second, true) ) {
|
||||
// Mouse wheel bound to this trigger control was pressed, control activated
|
||||
control_used(id);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -297,6 +297,13 @@ enum IoActionId : int {
|
||||
JOY_ABS_THROTTLE_AXIS = 127,
|
||||
JOY_REL_THROTTLE_AXIS = 128,
|
||||
|
||||
//!< @n
|
||||
//! Communication menu controls
|
||||
//! ----------------------------
|
||||
COMMS_MENU_MOVE_UP,
|
||||
COMMS_MENU_MOVE_DOWN,
|
||||
COMMS_MENU_SELECT,
|
||||
|
||||
TOGGLE_HUD_SHADOWS,
|
||||
|
||||
CYCLE_PRIMARY_WEAPON_PATTERN,
|
||||
@@ -937,8 +944,12 @@ 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.
|
||||
*
|
||||
* @param[in] id The IoActionId of the control to check
|
||||
* @param[in] key The key combo to check against the control. If -1, re-check the last key passed to this function
|
||||
* @param[in] key DO NOT USE OUTSIDE OF THE MAIN LOOP. Key combination acquired from game_poll(), or -1 to check against a previous poll
|
||||
*
|
||||
* @returns 0 If the control was disabled by a mod, or
|
||||
* @returns 0 If locked by a script, or
|
||||
* @returns 0 If controls need to be ignored for multiplayer, or
|
||||
* @returns 0 If a script is handling the control execution, or
|
||||
* @returns 0 If the control wasn't used, or
|
||||
* @returns 1 If the control was used
|
||||
*
|
||||
|
||||
@@ -272,6 +272,13 @@ void control_config_common_init_bindings() {
|
||||
(CUSTOM_CONTROL_3, KEY_ALTED | KEY_SHIFTED | KEY_3, -1, COMPUTER_TAB, 1786, "Custom Control 3", CC_TYPE_TRIGGER, true)
|
||||
(CUSTOM_CONTROL_4, KEY_ALTED | KEY_SHIFTED | KEY_4, -1, COMPUTER_TAB, 1787, "Custom Control 4", CC_TYPE_TRIGGER, true)
|
||||
(CUSTOM_CONTROL_5, KEY_ALTED | KEY_SHIFTED | KEY_5, -1, COMPUTER_TAB, 1788, "Custom Control 5", CC_TYPE_TRIGGER, true)
|
||||
|
||||
|
||||
//Comms Controls
|
||||
(COMMS_MENU_MOVE_UP, -1, -1, COMPUTER_TAB, 1883, "Communication Menu Move Up", CC_TYPE_TRIGGER)
|
||||
(COMMS_MENU_MOVE_DOWN, -1, -1, COMPUTER_TAB, 1884, "Communication Menu Move Down", CC_TYPE_TRIGGER)
|
||||
(COMMS_MENU_SELECT, -1, -1, COMPUTER_TAB, 1885, "Communication Menu Select", CC_TYPE_TRIGGER)
|
||||
|
||||
.end(); // Builder
|
||||
|
||||
// init default preset
|
||||
@@ -438,6 +445,9 @@ SCP_unordered_map<SCP_string, IoActionId> old_text = {
|
||||
{"Custom Control 3", CUSTOM_CONTROL_3},
|
||||
{"Custom Control 4", CUSTOM_CONTROL_4},
|
||||
{"Custom Control 5", CUSTOM_CONTROL_5},
|
||||
{"Communication Menu Move Up", COMMS_MENU_MOVE_UP},
|
||||
{"Communication Menu Move Down", COMMS_MENU_MOVE_DOWN},
|
||||
{"Communication Menu Select", COMMS_MENU_SELECT},
|
||||
};
|
||||
|
||||
// Localization strings for hat positions. Back[0], Forward[1], Left[2], Right[3]
|
||||
@@ -1164,6 +1174,10 @@ void LoadEnumsIntoActionMap() {
|
||||
ADD_ENUM_TO_ACTION_MAP(JOY_ABS_THROTTLE_AXIS)
|
||||
ADD_ENUM_TO_ACTION_MAP(JOY_REL_THROTTLE_AXIS)
|
||||
|
||||
ADD_ENUM_TO_ACTION_MAP(COMMS_MENU_MOVE_UP)
|
||||
ADD_ENUM_TO_ACTION_MAP(COMMS_MENU_MOVE_DOWN)
|
||||
ADD_ENUM_TO_ACTION_MAP(COMMS_MENU_SELECT)
|
||||
|
||||
|
||||
#undef ADD_ENUM_TO_ACTION_MAP
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ DecoderStatus::~DecoderStatus() {
|
||||
videoCodec = nullptr;
|
||||
|
||||
if (videoCodecCtx != nullptr) {
|
||||
avcodec_close(videoCodecCtx);
|
||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
||||
avcodec_free_context(&videoCodecCtx);
|
||||
#else
|
||||
avcodec_close(videoCodecCtx);
|
||||
#endif
|
||||
videoCodecCtx = nullptr;
|
||||
}
|
||||
@@ -24,9 +25,10 @@ DecoderStatus::~DecoderStatus() {
|
||||
audioCodec = nullptr;
|
||||
|
||||
if (audioCodecCtx != nullptr) {
|
||||
avcodec_close(audioCodecCtx);
|
||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
||||
avcodec_free_context(&audioCodecCtx);
|
||||
#else
|
||||
avcodec_close(audioCodecCtx);
|
||||
#endif
|
||||
audioCodecCtx = nullptr;
|
||||
}
|
||||
@@ -36,9 +38,10 @@ DecoderStatus::~DecoderStatus() {
|
||||
subtitleCodec = nullptr;
|
||||
|
||||
if (subtitleCodecCtx != nullptr) {
|
||||
avcodec_close(subtitleCodecCtx);
|
||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
||||
avcodec_free_context(&subtitleCodecCtx);
|
||||
#else
|
||||
avcodec_close(subtitleCodecCtx);
|
||||
#endif
|
||||
subtitleCodecCtx = nullptr;
|
||||
}
|
||||
|
||||
+191
-31
@@ -1,4 +1,4 @@
|
||||
/* bcdec.h - v0.96
|
||||
/* bcdec.h - v0.97
|
||||
provides functions to decompress blocks of BC compressed images
|
||||
written by Sergii "iOrange" Kudlai in 2022
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
|
||||
For more info, issues and suggestions please visit https://github.com/iOrange/bcdec
|
||||
|
||||
Configuration:
|
||||
#define BCDEC_BC4BC5_PRECISE:
|
||||
enables more precise but slower BC4/BC5 decoding + signed/unsigned mode
|
||||
|
||||
CREDITS:
|
||||
Aras Pranckevicius (@aras-p) - BC1/BC3 decoders optimizations (up to 3x the speed)
|
||||
- BC6H/BC7 bits pulling routines optimizations
|
||||
@@ -30,6 +34,11 @@
|
||||
- Split BC6H decompression function into 'half' and
|
||||
'float' variants
|
||||
|
||||
Michael Schmidt (@RunDevelopment) - Found better "magic" coefficients for integer interpolation
|
||||
of reference colors in BC1 color block, that match with
|
||||
the floating point interpolation. This also made it faster
|
||||
than integer division by 3!
|
||||
|
||||
bugfixes:
|
||||
@linkmauve
|
||||
|
||||
@@ -39,6 +48,9 @@
|
||||
#ifndef BCDEC_HEADER_INCLUDED
|
||||
#define BCDEC_HEADER_INCLUDED
|
||||
|
||||
#define BCDEC_VERSION_MAJOR 0
|
||||
#define BCDEC_VERSION_MINOR 98
|
||||
|
||||
/* if BCDEC_STATIC causes problems, try defining BCDECDEF to 'inline' or 'static inline' */
|
||||
#ifndef BCDECDEF
|
||||
#ifdef BCDEC_STATIC
|
||||
@@ -90,12 +102,20 @@
|
||||
BCDECDEF void bcdec_bc1(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
|
||||
BCDECDEF void bcdec_bc2(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
|
||||
BCDECDEF void bcdec_bc3(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
|
||||
#ifndef BCDEC_BC4BC5_PRECISE
|
||||
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
|
||||
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
|
||||
#else
|
||||
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
|
||||
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
|
||||
BCDECDEF void bcdec_bc4_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
|
||||
BCDECDEF void bcdec_bc5_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
|
||||
#endif
|
||||
BCDECDEF void bcdec_bc6h_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
|
||||
BCDECDEF void bcdec_bc6h_half(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
|
||||
BCDECDEF void bcdec_bc7(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
|
||||
|
||||
#endif /* BCDEC_HEADER_INCLUDED */
|
||||
|
||||
#ifdef BCDEC_IMPLEMENTATION
|
||||
|
||||
@@ -110,35 +130,44 @@ static void bcdec__color_block(const void* compressedBlock, void* decompressedBl
|
||||
c0 = ((unsigned short*)compressedBlock)[0];
|
||||
c1 = ((unsigned short*)compressedBlock)[1];
|
||||
|
||||
/* Expand 565 ref colors to 888 */
|
||||
r0 = (((c0 >> 11) & 0x1F) * 527 + 23) >> 6;
|
||||
g0 = (((c0 >> 5) & 0x3F) * 259 + 33) >> 6;
|
||||
b0 = ((c0 & 0x1F) * 527 + 23) >> 6;
|
||||
refColors[0] = 0xFF000000 | (b0 << 16) | (g0 << 8) | r0;
|
||||
/* Unpack 565 ref colors */
|
||||
r0 = (c0 >> 11) & 0x1F;
|
||||
g0 = (c0 >> 5) & 0x3F;
|
||||
b0 = c0 & 0x1F;
|
||||
|
||||
r1 = (((c1 >> 11) & 0x1F) * 527 + 23) >> 6;
|
||||
g1 = (((c1 >> 5) & 0x3F) * 259 + 33) >> 6;
|
||||
b1 = ((c1 & 0x1F) * 527 + 23) >> 6;
|
||||
refColors[1] = 0xFF000000 | (b1 << 16) | (g1 << 8) | r1;
|
||||
r1 = (c1 >> 11) & 0x1F;
|
||||
g1 = (c1 >> 5) & 0x3F;
|
||||
b1 = c1 & 0x1F;
|
||||
|
||||
/* Expand 565 ref colors to 888 */
|
||||
r = (r0 * 527 + 23) >> 6;
|
||||
g = (g0 * 259 + 33) >> 6;
|
||||
b = (b0 * 527 + 23) >> 6;
|
||||
refColors[0] = 0xFF000000 | (b << 16) | (g << 8) | r;
|
||||
|
||||
r = (r1 * 527 + 23) >> 6;
|
||||
g = (g1 * 259 + 33) >> 6;
|
||||
b = (b1 * 527 + 23) >> 6;
|
||||
refColors[1] = 0xFF000000 | (b << 16) | (g << 8) | r;
|
||||
|
||||
if (c0 > c1 || onlyOpaqueMode) { /* Standard BC1 mode (also BC3 color block uses ONLY this mode) */
|
||||
/* color_2 = 2/3*color_0 + 1/3*color_1
|
||||
color_3 = 1/3*color_0 + 2/3*color_1 */
|
||||
r = (2 * r0 + r1 + 1) / 3;
|
||||
g = (2 * g0 + g1 + 1) / 3;
|
||||
b = (2 * b0 + b1 + 1) / 3;
|
||||
r = ((2 * r0 + r1) * 351 + 61) >> 7;
|
||||
g = ((2 * g0 + g1) * 2763 + 1039) >> 11;
|
||||
b = ((2 * b0 + b1) * 351 + 61) >> 7;
|
||||
refColors[2] = 0xFF000000 | (b << 16) | (g << 8) | r;
|
||||
|
||||
r = (r0 + 2 * r1 + 1) / 3;
|
||||
g = (g0 + 2 * g1 + 1) / 3;
|
||||
b = (b0 + 2 * b1 + 1) / 3;
|
||||
r = ((r0 + r1 * 2) * 351 + 61) >> 7;
|
||||
g = ((g0 + g1 * 2) * 2763 + 1039) >> 11;
|
||||
b = ((b0 + b1 * 2) * 351 + 61) >> 7;
|
||||
refColors[3] = 0xFF000000 | (b << 16) | (g << 8) | r;
|
||||
} else { /* Quite rare BC1A mode */
|
||||
/* color_2 = 1/2*color_0 + 1/2*color_1;
|
||||
color_3 = 0; */
|
||||
r = (r0 + r1 + 1) >> 1;
|
||||
g = (g0 + g1 + 1) >> 1;
|
||||
b = (b0 + b1 + 1) >> 1;
|
||||
r = ((r0 + r1) * 1053 + 125) >> 8;
|
||||
g = ((g0 + g1) * 4145 + 1019) >> 11;
|
||||
b = ((b0 + b1) * 1053 + 125) >> 8;
|
||||
refColors[2] = 0xFF000000 | (b << 16) | (g << 8) | r;
|
||||
|
||||
refColors[3] = 0x00000000;
|
||||
@@ -190,19 +219,19 @@ static void bcdec__smooth_alpha_block(const void* compressedBlock, void* decompr
|
||||
|
||||
if (alpha[0] > alpha[1]) {
|
||||
/* 6 interpolated alpha values. */
|
||||
alpha[2] = (6 * alpha[0] + alpha[1] + 1) / 7; /* 6/7*alpha_0 + 1/7*alpha_1 */
|
||||
alpha[3] = (5 * alpha[0] + 2 * alpha[1] + 1) / 7; /* 5/7*alpha_0 + 2/7*alpha_1 */
|
||||
alpha[4] = (4 * alpha[0] + 3 * alpha[1] + 1) / 7; /* 4/7*alpha_0 + 3/7*alpha_1 */
|
||||
alpha[5] = (3 * alpha[0] + 4 * alpha[1] + 1) / 7; /* 3/7*alpha_0 + 4/7*alpha_1 */
|
||||
alpha[6] = (2 * alpha[0] + 5 * alpha[1] + 1) / 7; /* 2/7*alpha_0 + 5/7*alpha_1 */
|
||||
alpha[7] = ( alpha[0] + 6 * alpha[1] + 1) / 7; /* 1/7*alpha_0 + 6/7*alpha_1 */
|
||||
alpha[2] = (6 * alpha[0] + alpha[1]) / 7; /* 6/7*alpha_0 + 1/7*alpha_1 */
|
||||
alpha[3] = (5 * alpha[0] + 2 * alpha[1]) / 7; /* 5/7*alpha_0 + 2/7*alpha_1 */
|
||||
alpha[4] = (4 * alpha[0] + 3 * alpha[1]) / 7; /* 4/7*alpha_0 + 3/7*alpha_1 */
|
||||
alpha[5] = (3 * alpha[0] + 4 * alpha[1]) / 7; /* 3/7*alpha_0 + 4/7*alpha_1 */
|
||||
alpha[6] = (2 * alpha[0] + 5 * alpha[1]) / 7; /* 2/7*alpha_0 + 5/7*alpha_1 */
|
||||
alpha[7] = ( alpha[0] + 6 * alpha[1]) / 7; /* 1/7*alpha_0 + 6/7*alpha_1 */
|
||||
}
|
||||
else {
|
||||
/* 4 interpolated alpha values. */
|
||||
alpha[2] = (4 * alpha[0] + alpha[1] + 1) / 5; /* 4/5*alpha_0 + 1/5*alpha_1 */
|
||||
alpha[3] = (3 * alpha[0] + 2 * alpha[1] + 1) / 5; /* 3/5*alpha_0 + 2/5*alpha_1 */
|
||||
alpha[4] = (2 * alpha[0] + 3 * alpha[1] + 1) / 5; /* 2/5*alpha_0 + 3/5*alpha_1 */
|
||||
alpha[5] = ( alpha[0] + 4 * alpha[1] + 1) / 5; /* 1/5*alpha_0 + 4/5*alpha_1 */
|
||||
alpha[2] = (4 * alpha[0] + alpha[1]) / 5; /* 4/5*alpha_0 + 1/5*alpha_1 */
|
||||
alpha[3] = (3 * alpha[0] + 2 * alpha[1]) / 5; /* 3/5*alpha_0 + 2/5*alpha_1 */
|
||||
alpha[4] = (2 * alpha[0] + 3 * alpha[1]) / 5; /* 2/5*alpha_0 + 3/5*alpha_1 */
|
||||
alpha[5] = ( alpha[0] + 4 * alpha[1]) / 5; /* 1/5*alpha_0 + 4/5*alpha_1 */
|
||||
alpha[6] = 0x00;
|
||||
alpha[7] = 0xFF;
|
||||
}
|
||||
@@ -218,6 +247,117 @@ static void bcdec__smooth_alpha_block(const void* compressedBlock, void* decompr
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BCDEC_BC4BC5_PRECISE
|
||||
static void bcdec__bc4_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int pixelSize, int isSigned) {
|
||||
signed char* sblock;
|
||||
unsigned char* ublock;
|
||||
int alpha[8];
|
||||
int i, j;
|
||||
unsigned long long block, indices;
|
||||
|
||||
static int aWeights4[4] = { 13107, 26215, 39321, 52429 };
|
||||
static int aWeights6[6] = { 9363, 18724, 28086, 37450, 46812, 56173 };
|
||||
|
||||
block = *(unsigned long long*)compressedBlock;
|
||||
|
||||
if (isSigned) {
|
||||
alpha[0] = (char)(block & 0xFF);
|
||||
alpha[1] = (char)((block >> 8) & 0xFF);
|
||||
if (alpha[0] < -127) alpha[0] = -127; /* -128 clamps to -127 */
|
||||
if (alpha[1] < -127) alpha[1] = -127; /* -128 clamps to -127 */
|
||||
} else {
|
||||
alpha[0] = block & 0xFF;
|
||||
alpha[1] = (block >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
if (alpha[0] > alpha[1]) {
|
||||
/* 6 interpolated alpha values. */
|
||||
alpha[2] = (aWeights6[5] * alpha[0] + aWeights6[0] * alpha[1] + 32768) >> 16; /* 6/7*alpha_0 + 1/7*alpha_1 */
|
||||
alpha[3] = (aWeights6[4] * alpha[0] + aWeights6[1] * alpha[1] + 32768) >> 16; /* 5/7*alpha_0 + 2/7*alpha_1 */
|
||||
alpha[4] = (aWeights6[3] * alpha[0] + aWeights6[2] * alpha[1] + 32768) >> 16; /* 4/7*alpha_0 + 3/7*alpha_1 */
|
||||
alpha[5] = (aWeights6[2] * alpha[0] + aWeights6[3] * alpha[1] + 32768) >> 16; /* 3/7*alpha_0 + 4/7*alpha_1 */
|
||||
alpha[6] = (aWeights6[1] * alpha[0] + aWeights6[4] * alpha[1] + 32768) >> 16; /* 2/7*alpha_0 + 5/7*alpha_1 */
|
||||
alpha[7] = (aWeights6[0] * alpha[0] + aWeights6[5] * alpha[1] + 32768) >> 16; /* 1/7*alpha_0 + 6/7*alpha_1 */
|
||||
} else {
|
||||
/* 4 interpolated alpha values. */
|
||||
alpha[2] = (aWeights4[3] * alpha[0] + aWeights4[0] * alpha[1] + 32768) >> 16; /* 4/5*alpha_0 + 1/5*alpha_1 */
|
||||
alpha[3] = (aWeights4[2] * alpha[0] + aWeights4[1] * alpha[1] + 32768) >> 16; /* 3/5*alpha_0 + 2/5*alpha_1 */
|
||||
alpha[4] = (aWeights4[1] * alpha[0] + aWeights4[2] * alpha[1] + 32768) >> 16; /* 2/5*alpha_0 + 3/5*alpha_1 */
|
||||
alpha[5] = (aWeights4[0] * alpha[0] + aWeights4[3] * alpha[1] + 32768) >> 16; /* 1/5*alpha_0 + 4/5*alpha_1 */
|
||||
alpha[6] = isSigned ? -127 : 0;
|
||||
alpha[7] = isSigned ? 127 : 255;
|
||||
}
|
||||
|
||||
indices = block >> 16;
|
||||
if (isSigned) {
|
||||
sblock = (char*)decompressedBlock;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) {
|
||||
sblock[j * pixelSize] = (char)alpha[indices & 0x07];
|
||||
indices >>= 3;
|
||||
}
|
||||
sblock += destinationPitch;
|
||||
}
|
||||
} else {
|
||||
ublock = (unsigned char*)decompressedBlock;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) {
|
||||
ublock[j * pixelSize] = (unsigned char)alpha[indices & 0x07];
|
||||
indices >>= 3;
|
||||
}
|
||||
ublock += destinationPitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bcdec__bc4_block_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int pixelSize, int isSigned) {
|
||||
float* decompressed;
|
||||
float alpha[8];
|
||||
int i, j;
|
||||
unsigned long long block, indices;
|
||||
|
||||
block = *(unsigned long long*)compressedBlock;
|
||||
decompressed = (float*)decompressedBlock;
|
||||
|
||||
if (isSigned) {
|
||||
alpha[0] = (float)((char)(block & 0xFF)) / 127.0f;
|
||||
alpha[1] = (float)((char)((block >> 8) & 0xFF)) / 127.0f;
|
||||
if (alpha[0] < -1.0f) alpha[0] = -1.0f; /* -128 clamps to -127 */
|
||||
if (alpha[1] < -1.0f) alpha[1] = -1.0f; /* -128 clamps to -127 */
|
||||
} else {
|
||||
alpha[0] = (float)(block & 0xFF) / 255.0f;
|
||||
alpha[1] = (float)((block >> 8) & 0xFF) / 255.0f;
|
||||
}
|
||||
|
||||
if (alpha[0] > alpha[1]) {
|
||||
/* 6 interpolated alpha values. */
|
||||
alpha[2] = (6.0f * alpha[0] + alpha[1]) / 7.0f; /* 6/7*alpha_0 + 1/7*alpha_1 */
|
||||
alpha[3] = (5.0f * alpha[0] + 2.0f * alpha[1]) / 7.0f; /* 5/7*alpha_0 + 2/7*alpha_1 */
|
||||
alpha[4] = (4.0f * alpha[0] + 3.0f * alpha[1]) / 7.0f; /* 4/7*alpha_0 + 3/7*alpha_1 */
|
||||
alpha[5] = (3.0f * alpha[0] + 4.0f * alpha[1]) / 7.0f; /* 3/7*alpha_0 + 4/7*alpha_1 */
|
||||
alpha[6] = (2.0f * alpha[0] + 5.0f * alpha[1]) / 7.0f; /* 2/7*alpha_0 + 5/7*alpha_1 */
|
||||
alpha[7] = ( alpha[0] + 6.0f * alpha[1]) / 7.0f; /* 1/7*alpha_0 + 6/7*alpha_1 */
|
||||
} else {
|
||||
/* 4 interpolated alpha values. */
|
||||
alpha[2] = (4.0f * alpha[0] + alpha[1]) / 5.0f; /* 4/5*alpha_0 + 1/5*alpha_1 */
|
||||
alpha[3] = (3.0f * alpha[0] + 2.0f * alpha[1]) / 5.0f; /* 3/5*alpha_0 + 2/5*alpha_1 */
|
||||
alpha[4] = (2.0f * alpha[0] + 3.0f * alpha[1]) / 5.0f; /* 2/5*alpha_0 + 3/5*alpha_1 */
|
||||
alpha[5] = ( alpha[0] + 4.0f * alpha[1]) / 5.0f; /* 1/5*alpha_0 + 4/5*alpha_1 */
|
||||
alpha[6] = isSigned ? -1.0f : 0.0f;
|
||||
alpha[7] = 1.0f;
|
||||
}
|
||||
|
||||
indices = block >> 16;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) {
|
||||
decompressed[j * pixelSize] = alpha[indices & 0x07];
|
||||
indices >>= 3;
|
||||
}
|
||||
decompressed += destinationPitch;
|
||||
}
|
||||
}
|
||||
#endif /* BCDEC_BC4BC5_PRECISE */
|
||||
|
||||
typedef struct bcdec__bitstream {
|
||||
unsigned long long low;
|
||||
unsigned long long high;
|
||||
@@ -270,15 +410,37 @@ BCDECDEF void bcdec_bc3(const void* compressedBlock, void* decompressedBlock, in
|
||||
bcdec__smooth_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch, 4);
|
||||
}
|
||||
|
||||
#ifndef BCDEC_BC4BC5_PRECISE
|
||||
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 1);
|
||||
#else
|
||||
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
|
||||
bcdec__bc4_block(compressedBlock, decompressedBlock, destinationPitch, 1, isSigned);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef BCDEC_BC4BC5_PRECISE
|
||||
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 2);
|
||||
bcdec__smooth_alpha_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 1, destinationPitch, 2);
|
||||
#else
|
||||
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
|
||||
bcdec__bc4_block(compressedBlock, decompressedBlock, destinationPitch, 2, isSigned);
|
||||
bcdec__bc4_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 1, destinationPitch, 2, isSigned);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef BCDEC_BC4BC5_PRECISE
|
||||
BCDECDEF void bcdec_bc4_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
|
||||
bcdec__bc4_block_float(compressedBlock, decompressedBlock, destinationPitch, 1, isSigned);
|
||||
}
|
||||
|
||||
BCDECDEF void bcdec_bc5_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
|
||||
bcdec__bc4_block_float(compressedBlock, decompressedBlock, destinationPitch, 2, isSigned);
|
||||
bcdec__bc4_block_float(((char*)compressedBlock) + 8, ((float*)decompressedBlock) + 1, destinationPitch, 2, isSigned);
|
||||
}
|
||||
#endif /* BCDEC_BC4BC5_PRECISE */
|
||||
|
||||
/* http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend */
|
||||
static int bcdec__extend_sign(int val, int bits) {
|
||||
return (val << (32 - bits)) >> (32 - bits);
|
||||
@@ -1269,8 +1431,6 @@ BCDECDEF void bcdec_bc7(const void* compressedBlock, void* decompressedBlock, in
|
||||
|
||||
#endif /* BCDEC_IMPLEMENTATION */
|
||||
|
||||
#endif /* BCDEC_HEADER_INCLUDED */
|
||||
|
||||
/* LICENSE:
|
||||
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
|
||||
+56
-20
@@ -96,6 +96,11 @@ static uint conversion_resize(DDS_HEADER &dds_header)
|
||||
// drop levels until we get to an appropriate size, but make sure we have
|
||||
// at least 1 mipmap level remaining at the end (in case there's not a full chain)
|
||||
while (((width > MAX_SIZE) || (height > MAX_SIZE)) && (offset < dds_header.dwMipMapCount-1)) {
|
||||
// this shouldn't happen, but catch the obscure case (like 8192x4)
|
||||
if ((width <= 4) || (height <= 4)) {
|
||||
break;
|
||||
}
|
||||
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
depth >>= 1;
|
||||
@@ -194,9 +199,10 @@ static int _dds_read_header(CFILE *ddsfile, DDS_HEADER &dds_header, DDS_HEADER_D
|
||||
return DDS_ERROR_NONE;
|
||||
}
|
||||
|
||||
static size_t compute_dds_size(const DDS_HEADER &dds_header)
|
||||
static size_t compute_dds_size(const DDS_HEADER &dds_header, bool converting = false)
|
||||
{
|
||||
uint d_width, d_height, d_depth;
|
||||
const uint block_sz = 4;
|
||||
uint d_width = 0, d_height = 0, d_depth = 0;
|
||||
size_t d_size = 0;
|
||||
|
||||
for (uint i = 0; i < dds_header.dwMipMapCount; i++) {
|
||||
@@ -204,6 +210,17 @@ static size_t compute_dds_size(const DDS_HEADER &dds_header)
|
||||
d_height = std::max(1U, dds_header.dwHeight >> i);
|
||||
d_depth = std::max(1U, dds_header.dwDepth >> i);
|
||||
|
||||
// When converting we need to pad a bit to compensate for the decompression
|
||||
// size on smaller mipmap levels. We need to ensure there is always enough
|
||||
// room to decode an entire 4x4 block in rgba space
|
||||
if (converting) {
|
||||
auto sz = std::min(d_width, d_height);
|
||||
|
||||
if (sz < block_sz) {
|
||||
d_size += ((block_sz * block_sz) - (sz * sz)) * d_depth * 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (dds_header.ddspf.dwFlags & DDPF_FOURCC) {
|
||||
// size of data block (4x4)
|
||||
d_size += ((d_width + 3) / 4) * ((d_height + 3) / 4) * d_depth * ((dds_header.ddspf.dwFourCC == FOURCC_DXT1) ? 8 : 16);
|
||||
@@ -247,6 +264,7 @@ int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *heigh
|
||||
int retval = DDS_ERROR_NONE;
|
||||
int ct = DDS_UNCOMPRESSED;
|
||||
int is_cubemap = 0;
|
||||
bool convert = false;
|
||||
|
||||
|
||||
if (img_cfp == NULL) {
|
||||
@@ -337,7 +355,9 @@ int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *heigh
|
||||
}
|
||||
|
||||
// maybe do conversion if format not supported
|
||||
if (conversion_needed(dds_header)) {
|
||||
convert = conversion_needed(dds_header);
|
||||
|
||||
if (convert) {
|
||||
// switch to uncompressed format and reset vars
|
||||
dds_header.ddspf.dwFlags &= ~DDPF_FOURCC;
|
||||
dds_header.ddspf.dwFlags |= DDPF_RGB;
|
||||
@@ -359,7 +379,7 @@ int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *heigh
|
||||
|
||||
// stuff important info
|
||||
if (size)
|
||||
*size = compute_dds_size(dds_header);
|
||||
*size = compute_dds_size(dds_header, convert);
|
||||
|
||||
if (bpp)
|
||||
*bpp = get_bit_count(dds_header);
|
||||
@@ -386,6 +406,9 @@ Done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void (*decompress_dds)(const void *in, void *out, int pitch) = nullptr;
|
||||
static uint32_t BLOCK_SIZE = 0;
|
||||
|
||||
//reads pixel info from a dds file
|
||||
int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
|
||||
{
|
||||
@@ -423,7 +446,7 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
|
||||
|
||||
cfseek(cfp, (dds_header.ddspf.dwFourCC == FOURCC_DX10) ? DX10_OFFSET : DDS_OFFSET, CF_SEEK_SET);
|
||||
|
||||
size = compute_dds_size(dds_header);
|
||||
size = compute_dds_size(dds_header); // don't add padding on this one!!
|
||||
|
||||
// read in the data
|
||||
if ( !conversion_needed(dds_header) ) {
|
||||
@@ -447,6 +470,28 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
|
||||
const int num_faces = (dds_header.dwCaps2 & DDSCAPS2_CUBEMAP) ? 6 : 1;
|
||||
const bool has_depth = (dds_header.dwFlags & DDSD_DEPTH) == DDSD_DEPTH;
|
||||
|
||||
switch (dds_header.ddspf.dwFourCC) {
|
||||
case FOURCC_DX10:
|
||||
decompress_dds = bcdec_bc7;
|
||||
BLOCK_SIZE = BCDEC_BC7_BLOCK_SIZE;
|
||||
break;
|
||||
case FOURCC_DXT5:
|
||||
decompress_dds = bcdec_bc3;
|
||||
BLOCK_SIZE = BCDEC_BC3_BLOCK_SIZE;
|
||||
break;
|
||||
case FOURCC_DXT1:
|
||||
decompress_dds = bcdec_bc1;
|
||||
BLOCK_SIZE = BCDEC_BC1_BLOCK_SIZE;
|
||||
break;
|
||||
case FOURCC_DXT3:
|
||||
decompress_dds = bcdec_bc2;
|
||||
BLOCK_SIZE = BCDEC_BC2_BLOCK_SIZE;
|
||||
break;
|
||||
default:
|
||||
Error(LOCATION, "Invalid FourCC (%d) for DDS decompression!", dds_header.ddspf.dwFourCC);
|
||||
break;
|
||||
}
|
||||
|
||||
for (int f = 0; f < num_faces; ++f) {
|
||||
// if we resized then skip over all of that data (altering values to match pre-resize)
|
||||
for (uint x = 0; x < mipmap_offset; ++x) {
|
||||
@@ -454,7 +499,7 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
|
||||
d_height = std::max(1U, dds_header.dwHeight << (mipmap_offset - x));
|
||||
d_depth = has_depth ? std::max(1U, dds_header.dwDepth << (mipmap_offset - x)) : 1U;
|
||||
|
||||
src += (d_width * d_height * d_depth);
|
||||
src += ((d_width + 3) / 4) * ((d_height + 3) / 4) * d_depth * BLOCK_SIZE;
|
||||
}
|
||||
|
||||
for (uint m = mipmap_offset; m < dds_header.dwMipMapCount; ++m) {
|
||||
@@ -464,23 +509,14 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
|
||||
d_depth = std::max(1U, dds_header.dwDepth >> (m - mipmap_offset));
|
||||
|
||||
for (uint d = 0; d < d_depth; ++d) {
|
||||
auto depth_offset = d * d_width * d_height * 4;
|
||||
|
||||
for (uint i = 0; i < d_height; i += 4) {
|
||||
for (uint j = 0; j < d_width; j += 4) {
|
||||
dst = data + data_offset + ((i * d_width + j) * 4);
|
||||
dst = data + data_offset + depth_offset + ((i * d_width + j) * 4);
|
||||
|
||||
if (dds_header.ddspf.dwFourCC == FOURCC_DX10) {
|
||||
bcdec_bc7(src, dst, d_width * 4);
|
||||
src += BCDEC_BC7_BLOCK_SIZE;
|
||||
} else if (dds_header.ddspf.dwFourCC == FOURCC_DXT5) {
|
||||
bcdec_bc3(src, dst, d_width * 4);
|
||||
src += BCDEC_BC3_BLOCK_SIZE;
|
||||
} else if (dds_header.ddspf.dwFourCC == FOURCC_DXT1) {
|
||||
bcdec_bc1(src, dst, d_width * 4);
|
||||
src += BCDEC_BC1_BLOCK_SIZE;
|
||||
} else if (dds_header.ddspf.dwFourCC == FOURCC_DXT3) {
|
||||
bcdec_bc2(src, dst, d_width * 4);
|
||||
src += BCDEC_BC2_BLOCK_SIZE;
|
||||
}
|
||||
decompress_dds(src, dst, d_width * 4);
|
||||
src += BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+170
-77
@@ -37,7 +37,6 @@
|
||||
#include "utils/Random.h"
|
||||
#include "weapon/weapon.h"
|
||||
|
||||
constexpr int MIN_RADIUS_FOR_PERSISTENT_DEBRIS = 50; // ship radius at which debris from it becomes persistant
|
||||
constexpr int DEBRIS_SOUND_DELAY = 2000; // time to start debris sound after created
|
||||
|
||||
int Num_hull_pieces; // number of hull pieces in existence
|
||||
@@ -59,30 +58,55 @@ particle::ParticleEffectHandle Debris_hit_particle;
|
||||
|
||||
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
|
||||
|
||||
// Find the first available arc slot. If none is available, and no_create is false, add one.
|
||||
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create);
|
||||
|
||||
/**
|
||||
* Start the sequence of a piece of debris writhing in unholy agony!!!
|
||||
*/
|
||||
static void debris_start_death_roll(object *debris_obj, debris *debris_p, vec3d *hitpos = nullptr)
|
||||
{
|
||||
auto sip = &Ship_info[debris_p->ship_info_index];
|
||||
if (debris_p->is_hull) {
|
||||
// tell everyone else to blow up the piece of debris
|
||||
if( MULTIPLAYER_MASTER )
|
||||
send_debris_update_packet(debris_obj,DEBRIS_UPDATE_NUKE);
|
||||
|
||||
int fireball_type = fireball_ship_explosion_type(&Ship_info[debris_p->ship_info_index]);
|
||||
if(fireball_type < 0) {
|
||||
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
|
||||
if (sip->debris_end_particles.isValid()) {
|
||||
auto source = particle::ParticleManager::get()->createSource(sip->debris_end_particles);
|
||||
|
||||
// Use the position since the object is going to be invalid soon
|
||||
auto host = std::make_unique<EffectHostVector>(debris_obj->pos, debris_obj->orient, debris_obj->phys_info.vel);
|
||||
host->setRadius(debris_obj->radius);
|
||||
source->setHost(std::move(host));
|
||||
source->setNormal(debris_obj->orient.vec.uvec);
|
||||
source->finishCreation();
|
||||
} else {
|
||||
int fireball_type = fireball_ship_explosion_type(sip);
|
||||
if(fireball_type < 0) {
|
||||
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);
|
||||
}
|
||||
fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
|
||||
|
||||
// only play debris destroy sound if hull piece and it has been around for at least 2 seconds
|
||||
if ( Missiontime > debris_p->time_started + 2*F1_0 ) {
|
||||
auto snd_id = Ship_info[debris_p->ship_info_index].debris_explosion_sound;
|
||||
auto snd_id = sip->debris_explosion_sound;
|
||||
if (snd_id.isValid()) {
|
||||
snd_play_3d( gamesnd_get_game_sound(snd_id), &debris_obj->pos, &View_position, debris_obj->radius );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sip->shrapnel_end_particles.isValid()) {
|
||||
auto source = particle::ParticleManager::get()->createSource(sip->shrapnel_end_particles);
|
||||
|
||||
// Use the position since the object is going to be invalid soon
|
||||
auto host = std::make_unique<EffectHostVector>(debris_obj->pos, debris_obj->orient, debris_obj->phys_info.vel);
|
||||
host->setRadius(debris_obj->radius);
|
||||
source->setHost(std::move(host));
|
||||
source->setNormal(debris_obj->orient.vec.uvec);
|
||||
source->finishCreation();
|
||||
}
|
||||
}
|
||||
|
||||
if (scripting::hooks::OnDebrisDeath->isActive()) {
|
||||
@@ -119,6 +143,9 @@ void debris_init()
|
||||
Debris_hit_particle = particle::ParticleManager::get()->addEffect(particle::ParticleEffect(
|
||||
"", //Name
|
||||
::util::UniformFloatRange(10.f), //Particle num
|
||||
particle::ParticleEffect::Duration::ONETIME, //Single Particle Emission
|
||||
::util::UniformFloatRange(), //No duration
|
||||
::util::UniformFloatRange (-1.f), //Single particle only
|
||||
particle::ParticleEffect::ShapeDirection::ALIGNED, //Particle direction
|
||||
::util::UniformFloatRange(1.f), //Velocity Inherit
|
||||
false, //Velocity Inherit absolute?
|
||||
@@ -133,6 +160,12 @@ void debris_init()
|
||||
true, //Affected by detail
|
||||
1.f, //Culling range multiplier
|
||||
true, //Disregard Animation Length. Must be true for everything using particle::Anim_bitmap_X
|
||||
false, //Don't reverse animation
|
||||
false, //parent local
|
||||
false, //ignore velocity inherit if parented
|
||||
false, //position velocity inherit absolute?
|
||||
std::nullopt, //Local velocity offset
|
||||
std::nullopt, //Local offset
|
||||
::util::UniformFloatRange(0.25f, 0.75f), //Lifetime
|
||||
::util::UniformFloatRange(0.2f, 0.4f), //Radius
|
||||
particle::Anim_bitmap_id_fire)); //Bitmap
|
||||
@@ -266,56 +299,48 @@ void debris_process_post(object * obj, float frame_time)
|
||||
return; // If arc_frequency <= 0, this piece has no arcs on it
|
||||
}
|
||||
|
||||
if ( !timestamp_elapsed(db->fire_timeout) && timestamp_elapsed(db->next_fireball)) {
|
||||
if ( !timestamp_elapsed(db->arc_timeout) && timestamp_elapsed(db->arc_next_time)) {
|
||||
|
||||
db->next_fireball = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
|
||||
db->arc_next_time = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
|
||||
db->arc_frequency += 100;
|
||||
|
||||
if (db->is_hull) {
|
||||
|
||||
int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks
|
||||
int n_arcs = Random::next(1, 3); // Create 1-3 sparks
|
||||
|
||||
vec3d v1 = submodel_get_random_point(db->model_num, db->submodel_num);
|
||||
vec3d v2 = submodel_get_random_point(db->model_num, db->submodel_num);
|
||||
vec3d v3 = submodel_get_random_point(db->model_num, db->submodel_num);
|
||||
vec3d v4 = submodel_get_random_point(db->model_num, db->submodel_num);
|
||||
|
||||
n = 0;
|
||||
|
||||
int lifetime = Random::next(100, 1000);
|
||||
|
||||
// Create the spark effects
|
||||
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
|
||||
if ( !db->arc_timestamp[i].isValid() ) {
|
||||
|
||||
db->arc_timestamp[i] = _timestamp(lifetime); // live up to a second
|
||||
for (int n = 0; n < n_arcs; n++) {
|
||||
auto arc = debris_find_or_create_electrical_arc_slot(db, db->electrical_arcs.size() >= MAX_DEBRIS_ARCS);
|
||||
if (arc) {
|
||||
arc->timestamp = _timestamp(lifetime); // live up to a second
|
||||
|
||||
switch( n ) {
|
||||
case 0:
|
||||
db->arc_pts[i][0] = v1;
|
||||
db->arc_pts[i][1] = v2;
|
||||
arc->endpoint_1 = v1;
|
||||
arc->endpoint_2 = v2;
|
||||
break;
|
||||
case 1:
|
||||
db->arc_pts[i][0] = v2;
|
||||
db->arc_pts[i][1] = v3;
|
||||
arc->endpoint_1 = v2;
|
||||
arc->endpoint_2 = v3;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
db->arc_pts[i][0] = v2;
|
||||
db->arc_pts[i][1] = v4;
|
||||
arc->endpoint_1 = v2;
|
||||
arc->endpoint_2 = v4;
|
||||
break;
|
||||
|
||||
default:
|
||||
Int3();
|
||||
UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n);
|
||||
}
|
||||
|
||||
n++;
|
||||
if ( n == n_arcs )
|
||||
break; // Don't need to create anymore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// rotate v2 out of local coordinates into world.
|
||||
// Use v2 since it is used in every bolt. See above switch().
|
||||
vec3d snd_pos;
|
||||
@@ -342,16 +367,20 @@ void debris_process_post(object * obj, float frame_time)
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
|
||||
if ( db->arc_timestamp[i].isValid() ) {
|
||||
if ( timestamp_elapsed( db->arc_timestamp[i] ) ) {
|
||||
for (auto &arc: db->electrical_arcs) {
|
||||
if (arc.timestamp.isValid()) {
|
||||
if (timestamp_elapsed(arc.timestamp)) {
|
||||
// Kill off the spark
|
||||
db->arc_timestamp[i] = TIMESTAMP::invalid();
|
||||
arc.timestamp = TIMESTAMP::invalid();
|
||||
} else {
|
||||
// Maybe move a vertex.... 20% of the time maybe?
|
||||
int mr = Random::next();
|
||||
if ( mr < Random::MAX_VALUE/5 ) {
|
||||
db->arc_pts[i][mr % 2] = submodel_get_random_point(db->model_num, db->submodel_num);
|
||||
auto pt = submodel_get_random_point(db->model_num, db->submodel_num);
|
||||
if (mr % 2 == 0)
|
||||
arc.endpoint_1 = pt;
|
||||
else
|
||||
arc.endpoint_2 = pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,6 +449,32 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, const
|
||||
{
|
||||
debris_create_set_velocity(&Debris[obj->instance], shipp, exp_center, exp_force, source_subsys);
|
||||
debris_create_fire_hook(obj, source_obj);
|
||||
const auto& sip = Ship_info[Ships[source_obj->instance].ship_info_index];
|
||||
particle::ParticleEffectHandle flame_effect;
|
||||
if (source_subsys != nullptr) {
|
||||
if (hull_flag) {
|
||||
if (source_subsys->system_info->debris_flame_particles.isValid()) {
|
||||
flame_effect = source_subsys->system_info->debris_flame_particles;
|
||||
} else {
|
||||
flame_effect = sip.default_subsys_debris_flame_particles;
|
||||
}
|
||||
} else {
|
||||
if (source_subsys->system_info->shrapnel_flame_particles.isValid()) {
|
||||
flame_effect = source_subsys->system_info->shrapnel_flame_particles;
|
||||
} else {
|
||||
flame_effect = sip.default_subsys_shrapnel_flame_particles;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flame_effect = hull_flag ? sip.debris_flame_particles : sip.shrapnel_flame_particles;
|
||||
}
|
||||
if (flame_effect.isValid()) {
|
||||
auto source = particle::ParticleManager::get()->createSource(flame_effect);
|
||||
source->setHost(std::make_unique<EffectHostObject>(obj, vmd_zero_vector));
|
||||
source->setTriggerRadius(source_obj->radius);
|
||||
source->setTriggerVelocity(vm_vec_mag_quick(&source_obj->phys_info.vel));
|
||||
source->finishCreation();
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
@@ -552,22 +607,10 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
|
||||
}
|
||||
|
||||
// Create Debris piece n!
|
||||
if ( hull_flag ) {
|
||||
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 {
|
||||
// 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);
|
||||
}
|
||||
|
||||
//WMC - Oh noes, we may need to change lifeleft
|
||||
if(hull_flag)
|
||||
{
|
||||
// set lifeleft based on tabled entries if they are not negative
|
||||
// coded originally by WMC then clean-up added by wookieejedi
|
||||
if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
|
||||
{
|
||||
db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
|
||||
@@ -582,6 +625,22 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
|
||||
if(db->lifeleft > sip->debris_max_lifetime)
|
||||
db->lifeleft = sip->debris_max_lifetime;
|
||||
}
|
||||
// By default, make some pieces blow up shortly after explosion.
|
||||
else if (Random::next() < (Random::MAX_VALUE / 6))
|
||||
{
|
||||
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
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
// increase lifetime for vaporized debris
|
||||
@@ -595,15 +654,13 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
|
||||
db->ship_info_index = parent_ship_class;
|
||||
db->team = team;
|
||||
db->ambient_sound = (sip == nullptr) ? gamesnd_id(-1) : sip->debris_ambient_sound;
|
||||
db->fire_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false
|
||||
db->arc_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false
|
||||
db->time_started = Missiontime;
|
||||
db->species = (sip == nullptr) ? -1 : sip->species;
|
||||
db->parent_alt_name = alt_type_index;
|
||||
db->damage_mult = 1.0f;
|
||||
|
||||
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) { // NOLINT
|
||||
db->arc_timestamp[i] = TIMESTAMP::invalid();
|
||||
}
|
||||
db->electrical_arcs.clear();
|
||||
|
||||
if ( db->is_hull ) {
|
||||
// Percent of debris pieces with arcs controlled via table (default 50%)
|
||||
@@ -616,7 +673,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
|
||||
db->arc_frequency = 0;
|
||||
}
|
||||
|
||||
db->next_fireball = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later
|
||||
db->arc_next_time = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later
|
||||
|
||||
flagset<Object::Object_Flags> default_flags;
|
||||
default_flags.set(Object::Object_Flags::Renders);
|
||||
@@ -650,19 +707,25 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
|
||||
obj->hull_strength = hull_strength;
|
||||
|
||||
if (hull_flag) {
|
||||
if(sip->debris_min_hitpoints >= 0.0f && sip->debris_max_hitpoints >= 0.0f)
|
||||
{
|
||||
obj->hull_strength = (( sip->debris_max_hitpoints - sip->debris_min_hitpoints ) * frand()) + sip->debris_min_hitpoints;
|
||||
float min_hp = sip->debris_min_hitpoints;
|
||||
float max_hp = sip->debris_max_hitpoints;
|
||||
if (sip->debris_hitpoints_radius_multi >= 0.0f) {
|
||||
min_hp *= sip->debris_hitpoints_radius_multi * radius;
|
||||
max_hp *= sip->debris_hitpoints_radius_multi * radius;
|
||||
}
|
||||
else if(sip->debris_min_hitpoints >= 0.0f)
|
||||
if (min_hp >= 0.0f && max_hp >= 0.0f)
|
||||
{
|
||||
if(obj->hull_strength < sip->debris_min_hitpoints)
|
||||
obj->hull_strength = sip->debris_min_hitpoints;
|
||||
obj->hull_strength = ((max_hp - min_hp) * frand()) + min_hp;
|
||||
}
|
||||
else if(sip->debris_max_hitpoints >= 0.0f)
|
||||
else if(min_hp >= 0.0f)
|
||||
{
|
||||
if(obj->hull_strength > sip->debris_max_hitpoints)
|
||||
obj->hull_strength = sip->debris_max_hitpoints;
|
||||
if (obj->hull_strength < min_hp)
|
||||
obj->hull_strength = min_hp;
|
||||
}
|
||||
else if(max_hp >= 0.0f)
|
||||
{
|
||||
if (obj->hull_strength > max_hp)
|
||||
obj->hull_strength = max_hp;
|
||||
}
|
||||
db->damage_mult = sip->debris_damage_mult;
|
||||
|
||||
@@ -671,21 +734,23 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
|
||||
// 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.
|
||||
if (spark_timeout >= 0) {
|
||||
db->fire_timeout = _timestamp(spark_timeout);
|
||||
db->arc_timeout = _timestamp(spark_timeout);
|
||||
} else if (parent_objnum >= 0) {
|
||||
float t = 1000*Objects[parent_objnum].radius/3 + (fl2i(1000*3*Objects[parent_objnum].radius) == 0 ? 0 : Random::next(fl2i(1000*3*Objects[parent_objnum].radius)));
|
||||
db->fire_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
|
||||
db->arc_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
|
||||
} else {
|
||||
db->fire_timeout = TIMESTAMP::immediate();
|
||||
db->arc_timeout = TIMESTAMP::immediate();
|
||||
}
|
||||
|
||||
if (parent_objnum >= 0 && Objects[parent_objnum].radius >= MIN_RADIUS_FOR_PERSISTENT_DEBRIS) {
|
||||
if (parent_objnum >= 0 && Objects[parent_objnum].radius >= Min_radius_for_persistent_debris) {
|
||||
db->flags.set(Debris_Flags::DoNotExpire);
|
||||
} else {
|
||||
debris_add_to_hull_list(db);
|
||||
}
|
||||
}
|
||||
|
||||
db->max_hull = obj->hull_strength;
|
||||
|
||||
if (hull_flag) {
|
||||
MONITOR_INC(NumHullDebris,1);
|
||||
} else {
|
||||
@@ -965,9 +1030,9 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
model_get_moving_submodel_list(submodel_vector, heavy_obj);
|
||||
|
||||
// turn off all moving submodels, collide against only 1 at a time.
|
||||
// turn off collision detection for all moving submodels
|
||||
mc.collision_checked.assign(pm->n_models, 0);
|
||||
for (auto submodel : submodel_vector) {
|
||||
pmi->submodel[submodel].collision_checked = true;
|
||||
mc.collision_checked[submodel] = true;
|
||||
}
|
||||
|
||||
// Only check single submodel now, since children of moving submodels are handled as moving as well
|
||||
@@ -979,10 +1044,8 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
|
||||
// check each submodel in turn
|
||||
for (auto submodel: submodel_vector) {
|
||||
auto smi = &pmi->submodel[submodel];
|
||||
|
||||
// turn on just one submodel for collision test
|
||||
smi->collision_checked = false;
|
||||
mc.collision_checked[submodel] = false;
|
||||
|
||||
// find the start and end positions of the sphere in submodel RF
|
||||
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
|
||||
@@ -1016,8 +1079,11 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
}
|
||||
|
||||
// Don't look at this submodel again
|
||||
smi->collision_checked = true;
|
||||
mc.collision_checked[submodel] = true;
|
||||
}
|
||||
|
||||
// Clear collision_checked before base model pass so it auto-inits fresh
|
||||
mc.collision_checked.clear();
|
||||
}
|
||||
|
||||
// Now complete base model collision checks that do not take into account rotating submodels.
|
||||
@@ -1162,7 +1228,7 @@ void calc_debris_physics_properties( physics_info *pi, vec3d *mins, vec3d *maxs,
|
||||
*/
|
||||
void debris_render(object * obj, model_draw_list *scene)
|
||||
{
|
||||
int i, num, swapped;
|
||||
int num, swapped;
|
||||
debris *db;
|
||||
|
||||
swapped = -1;
|
||||
@@ -1197,9 +1263,9 @@ void debris_render(object * obj, model_draw_list *scene)
|
||||
|
||||
// Only render electrical arcs if within 500m of the eye (for a 10m piece)
|
||||
if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) {
|
||||
for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
|
||||
if ( db->arc_timestamp[i].isValid() ) {
|
||||
model_instance_add_arc( pm, pmi, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_DAMAGED );
|
||||
for (auto &arc: db->electrical_arcs) {
|
||||
if ( arc.timestamp.isValid() ) {
|
||||
model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, nullptr, MARC_TYPE_DAMAGED );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1240,13 +1306,40 @@ void create_generic_debris(object* ship_objp, const vec3d* pos, float min_num_de
|
||||
if (ship_objp->type != OBJ_SHIP)
|
||||
return;
|
||||
|
||||
// determine if using custom or non-custom generic debris
|
||||
int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;
|
||||
|
||||
// if using non-custom generic debris, then bail early if that kind of debris is not allowed --wookeejedi
|
||||
if ( (model_num < 0) && (Disable_all_noncustom_generic_debris) ) {
|
||||
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);
|
||||
debris_create(ship_objp, model_num, -1, &create_pos, pos, false, speed_mult);
|
||||
}
|
||||
}
|
||||
|
||||
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create)
|
||||
{
|
||||
size_t i = 0;
|
||||
for (auto& ii : db->electrical_arcs)
|
||||
{
|
||||
if (!ii.timestamp.isValid())
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == db->electrical_arcs.size())
|
||||
{
|
||||
if (no_create)
|
||||
return nullptr;
|
||||
db->electrical_arcs.emplace_back();
|
||||
}
|
||||
|
||||
return &db->electrical_arcs[i];
|
||||
}
|
||||
+11
-5
@@ -20,7 +20,7 @@ class object;
|
||||
struct CFILE;
|
||||
class model_draw_list;
|
||||
|
||||
#define MAX_DEBRIS_ARCS 8 // Must be less than MAX_ARC_EFFECTS in model.h
|
||||
#define MAX_DEBRIS_ARCS 8
|
||||
|
||||
FLAG_LIST(Debris_Flags) {
|
||||
Used,
|
||||
@@ -30,6 +30,12 @@ FLAG_LIST(Debris_Flags) {
|
||||
NUM_VALUES
|
||||
};
|
||||
|
||||
struct debris_electrical_arc
|
||||
{
|
||||
vec3d endpoint_1;
|
||||
vec3d endpoint_2;
|
||||
TIMESTAMP timestamp; // When this times out, the spark goes away. Invalid is not used
|
||||
};
|
||||
|
||||
typedef struct debris {
|
||||
flagset<Debris_Flags> flags; // See DEBRIS_??? defines
|
||||
@@ -43,15 +49,15 @@ typedef struct debris {
|
||||
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
|
||||
TIMESTAMP next_fireball; // When to start a fireball
|
||||
TIMESTAMP arc_next_time; // When the next damage/emp arc will be created.
|
||||
bool is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment
|
||||
float max_hull;
|
||||
int species; // What species this is from. -1 if don't care.
|
||||
TIMESTAMP fire_timeout; // timestamp that holds time for fireballs to stop appearing
|
||||
TIMESTAMP arc_timeout; // timestamp that holds time for arcs to stop appearing
|
||||
TIMESTAMP sound_delay; // timestamp to signal when sound should start
|
||||
fix time_started; // time when debris was created
|
||||
|
||||
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
|
||||
TIMESTAMP arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. Invalid is not used
|
||||
SCP_vector<debris_electrical_arc> electrical_arcs;
|
||||
int arc_frequency; // Starts at 1000, gets bigger
|
||||
|
||||
int parent_alt_name;
|
||||
|
||||
@@ -1083,7 +1083,7 @@ void dc_stuff_int(int *i)
|
||||
value_l = dc_parse_long(token.c_str(), DCT_INT);
|
||||
|
||||
if ((value_l < INT_MAX) && (value_l > INT_MIN)) {
|
||||
*i = value_l;
|
||||
*i = static_cast<int>(value_l);
|
||||
|
||||
} else {
|
||||
throw errParse(token.c_str(), DCT_INT);
|
||||
@@ -1101,7 +1101,7 @@ void dc_stuff_uint(uint *i)
|
||||
value_l = dc_parse_long(Cp, DCT_INT);
|
||||
|
||||
if (value_l < UINT_MAX) {
|
||||
*i = value_l;
|
||||
*i = static_cast<uint>(value_l);
|
||||
|
||||
} else {
|
||||
throw errParse(token.c_str(), DCT_INT);
|
||||
|
||||
+127
-94
@@ -181,66 +181,61 @@ void parse_decals_table(const char* filename) {
|
||||
}
|
||||
}
|
||||
|
||||
struct Decal {
|
||||
int definition_handle = -1;
|
||||
object_h object;
|
||||
int orig_obj_type = OBJ_NONE;
|
||||
int submodel = -1;
|
||||
Decal::Decal() {
|
||||
vm_vec_make(&scale, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
float creation_time = -1.0f; //!< The mission time at which this decal was created
|
||||
float lifetime = -1.0f; //!< The time this decal is active. When negative it never expires
|
||||
void Decal::markForDeletion() {
|
||||
orig_obj_type = OBJ_NONE;
|
||||
}
|
||||
|
||||
vec3d position = vmd_zero_vector;
|
||||
vec3d scale;
|
||||
matrix orientation = vmd_identity_matrix;
|
||||
|
||||
Decal() {
|
||||
vm_vec_make(&scale, 1.f, 1.f, 1.f);
|
||||
bool Decal::isValid() const {
|
||||
if (!object.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (object.objp()->flags[Object::Object_Flags::Should_be_dead]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
if (!object.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (object.objp()->flags[Object::Object_Flags::Should_be_dead]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (orig_obj_type != object.objp()->type) {
|
||||
mprintf(("Decal object type for object %d has changed from %s to %s. Please let m!m know about this\n",
|
||||
object.objnum, Object_type_names[orig_obj_type], Object_type_names[object.objp()->type]));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lifetime > 0.0f) {
|
||||
if (f2fl(Missiontime) >= creation_time + lifetime) {
|
||||
// Decal has expired
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto objp = object.objp();
|
||||
if (objp->type == OBJ_SHIP) {
|
||||
auto shipp = &Ships[objp->instance];
|
||||
auto model_instance = model_get_instance(shipp->model_instance_num);
|
||||
|
||||
Assertion(submodel >= 0 && submodel < object_get_model(objp)->n_models,
|
||||
"Invalid submodel number detected!");
|
||||
auto smi = &model_instance->submodel[submodel];
|
||||
|
||||
if (smi->blown_off) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Assertion(false, "Only ships are currently supported for decals!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (orig_obj_type == OBJ_NONE) {
|
||||
//Decal should be cleared
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
SCP_vector<Decal> active_decals;
|
||||
if (orig_obj_type != object.objp()->type) {
|
||||
mprintf(("Decal object type for object %d has changed from %s to %s. Please let m!m know about this\n",
|
||||
object.objnum, Object_type_names[orig_obj_type], Object_type_names[object.objp()->type]));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lifetime > 0.0f) {
|
||||
if (f2fl(Missiontime) >= creation_time + lifetime) {
|
||||
// Decal has expired
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto objp = object.objp();
|
||||
if (objp->type == OBJ_SHIP) {
|
||||
auto shipp = &Ships[objp->instance];
|
||||
auto model_instance = model_get_instance(shipp->model_instance_num);
|
||||
|
||||
Assertion(submodel >= 0 && submodel < object_get_model(objp)->n_models,
|
||||
"Invalid submodel number detected!");
|
||||
auto smi = &model_instance->submodel[submodel];
|
||||
|
||||
if (smi->blown_off) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Assertion(false, "Only ships are currently supported for decals!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SCP_vector<Decal> active_decals, active_single_frame_decals;
|
||||
|
||||
bool required_string_if_new(const char* token, bool new_entry) {
|
||||
if (!new_entry) {
|
||||
@@ -370,7 +365,11 @@ void initializeMission() {
|
||||
active_decals.clear();
|
||||
}
|
||||
|
||||
matrix4 getDecalTransform(Decal& decal) {
|
||||
// Discard any fragments where the angle to the direction to greater than 45°
|
||||
const float DECAL_ANGLE_CUTOFF = fl_radians(45.f);
|
||||
const float DECAL_ANGLE_FADE_START = fl_radians(30.f);
|
||||
|
||||
static matrix4 getDecalTransform(const Decal& decal, float alpha) {
|
||||
Assertion(decal.object.objp()->type == OBJ_SHIP, "Only ships are currently supported for decals!");
|
||||
|
||||
auto objp = decal.object.objp();
|
||||
@@ -405,11 +404,62 @@ matrix4 getDecalTransform(Decal& decal) {
|
||||
matrix4 mat4;
|
||||
vm_matrix4_set_transform(&mat4, &worldOrient, &worldPos);
|
||||
|
||||
// This is currently a constant but in the future this may be configurable by the decals table
|
||||
mat4.a2d[0][3] = DECAL_ANGLE_CUTOFF;
|
||||
mat4.a2d[1][3] = DECAL_ANGLE_FADE_START;
|
||||
|
||||
mat4.a2d[2][3] = alpha;
|
||||
|
||||
return mat4;
|
||||
}
|
||||
|
||||
inline static void renderDecal(graphics::decal_draw_list& draw_list, const Decal& decal) {
|
||||
auto mission_time = f2fl(Missiontime);
|
||||
|
||||
int diffuse_bm = -1;
|
||||
int glow_bm = -1;
|
||||
int normal_bm = -1;
|
||||
|
||||
auto decal_time = mission_time - decal.creation_time;
|
||||
auto progress = decal_time / decal.lifetime;
|
||||
|
||||
float alpha = 1.0f;
|
||||
if (progress > 0.8) {
|
||||
// Fade the decal out for the last 20% of its lifetime
|
||||
alpha = 1.0f - smoothstep(0.8f, 1.0f, progress);
|
||||
}
|
||||
|
||||
if (std::holds_alternative<int>(decal.definition_handle)) {
|
||||
int definition_handle = std::get<int>(decal.definition_handle);
|
||||
Assertion(definition_handle >= 0 && definition_handle < (int) DecalDefinitions.size(),
|
||||
"Invalid decal handle detected!");
|
||||
auto &decalDef = DecalDefinitions[definition_handle];
|
||||
|
||||
if (decalDef.getDiffuseBitmap() >= 0) {
|
||||
diffuse_bm = decalDef.getDiffuseBitmap()
|
||||
+
|
||||
bm_get_anim_frame(decalDef.getDiffuseBitmap(), decal_time, 0.0f, decalDef.isDiffuseLooping());
|
||||
}
|
||||
|
||||
if (decalDef.getGlowBitmap() >= 0) {
|
||||
glow_bm = decalDef.getGlowBitmap()
|
||||
+ bm_get_anim_frame(decalDef.getGlowBitmap(), decal_time, 0.0f, decalDef.isGlowLooping());
|
||||
}
|
||||
|
||||
if (decalDef.getNormalBitmap() >= 0) {
|
||||
normal_bm = decalDef.getNormalBitmap()
|
||||
+ bm_get_anim_frame(decalDef.getNormalBitmap(), decal_time, 0.0f, decalDef.isNormalLooping());
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::tie(diffuse_bm, glow_bm, normal_bm) = std::get<std::tuple<int, int, int>>(decal.definition_handle);
|
||||
}
|
||||
|
||||
draw_list.add_decal(diffuse_bm, glow_bm, normal_bm, decal_time, getDecalTransform(decal, alpha));
|
||||
}
|
||||
|
||||
void renderAll() {
|
||||
if (!Decal_system_active || !Decal_option_active) {
|
||||
if (!Decal_system_active || !Decal_option_active || !gr_is_capable(gr_capability::CAPABILITY_INSTANCED_RENDERING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -432,51 +482,21 @@ void renderAll() {
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (active_decals.empty()) {
|
||||
if (active_decals.empty() && active_single_frame_decals.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto mission_time = f2fl(Missiontime);
|
||||
|
||||
graphics::decal_draw_list draw_list(active_decals.size());
|
||||
for (auto& decal : active_decals) {
|
||||
|
||||
Assertion(decal.definition_handle >= 0 && decal.definition_handle < (int)DecalDefinitions.size(),
|
||||
"Invalid decal handle detected!");
|
||||
auto& decalDef = DecalDefinitions[decal.definition_handle];
|
||||
|
||||
int diffuse_bm = -1;
|
||||
int glow_bm = -1;
|
||||
int normal_bm = -1;
|
||||
|
||||
auto decal_time = mission_time - decal.creation_time;
|
||||
auto progress = decal_time / decal.lifetime;
|
||||
|
||||
float alpha = 1.0f;
|
||||
if (progress > 0.8) {
|
||||
// Fade the decal out for the last 20% of its lifetime
|
||||
alpha = 1.0f - smoothstep(0.8f, 1.0f, progress);
|
||||
}
|
||||
|
||||
if (decalDef.getDiffuseBitmap() >= 0) {
|
||||
diffuse_bm = decalDef.getDiffuseBitmap()
|
||||
+ bm_get_anim_frame(decalDef.getDiffuseBitmap(), decal_time, 0.0f, decalDef.isDiffuseLooping());
|
||||
}
|
||||
|
||||
if (decalDef.getGlowBitmap() >= 0) {
|
||||
glow_bm = decalDef.getGlowBitmap()
|
||||
+ bm_get_anim_frame(decalDef.getGlowBitmap(), decal_time, 0.0f, decalDef.isGlowLooping());
|
||||
}
|
||||
|
||||
if (decalDef.getNormalBitmap() >= 0) {
|
||||
normal_bm = decalDef.getNormalBitmap()
|
||||
+ bm_get_anim_frame(decalDef.getNormalBitmap(), decal_time, 0.0f, decalDef.isNormalLooping());
|
||||
}
|
||||
|
||||
draw_list.add_decal(diffuse_bm, glow_bm, normal_bm, decal_time, getDecalTransform(decal), alpha);
|
||||
}
|
||||
graphics::decal_draw_list draw_list;
|
||||
for (auto& decal : active_decals)
|
||||
renderDecal(draw_list, decal);
|
||||
for (auto& decal : active_single_frame_decals)
|
||||
renderDecal(draw_list, decal);
|
||||
|
||||
draw_list.render();
|
||||
|
||||
active_single_frame_decals.clear();
|
||||
}
|
||||
|
||||
void addDecal(creation_info& info, const object* host, int submodel, const vec3d& local_pos, const matrix& local_orient) {
|
||||
@@ -521,4 +541,17 @@ void addDecal(creation_info& info, const object* host, int submodel, const vec3d
|
||||
active_decals.push_back(newDecal);
|
||||
}
|
||||
|
||||
void addSingleFrameDecal(Decal&& info) {
|
||||
active_single_frame_decals.push_back(info);
|
||||
}
|
||||
|
||||
void invalidateForShip(const ship* shipp) {
|
||||
int objnum = shipp->objnum;
|
||||
for (Decal& decal : active_decals) {
|
||||
if (decal.object.objnum == objnum) {
|
||||
decal.markForDeletion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,27 @@ class DecalDefinition {
|
||||
bool isNormalLooping() const;
|
||||
};
|
||||
|
||||
struct Decal {
|
||||
//DecalDefinition idx vs immediate diffuse/glow/normal
|
||||
std::variant<int, std::tuple<int, int, int>> definition_handle = -1;
|
||||
object_h object;
|
||||
int orig_obj_type = OBJ_NONE;
|
||||
int submodel = -1;
|
||||
|
||||
float creation_time = -1.0f; //!< The mission time at which this decal was created
|
||||
float lifetime = -1.0f; //!< The time this decal is active. When negative it never expires
|
||||
|
||||
vec3d position = vmd_zero_vector;
|
||||
vec3d scale;
|
||||
matrix orientation = vmd_identity_matrix;
|
||||
|
||||
Decal();
|
||||
|
||||
void markForDeletion();
|
||||
|
||||
bool isValid() const;
|
||||
};
|
||||
|
||||
extern SCP_vector<DecalDefinition> DecalDefinitions;
|
||||
extern bool Decal_system_active;
|
||||
extern bool Decal_option_active;
|
||||
@@ -139,4 +160,8 @@ void addDecal(creation_info& info,
|
||||
const vec3d& local_pos,
|
||||
const matrix& local_orient);
|
||||
|
||||
void addSingleFrameDecal(Decal&& info);
|
||||
|
||||
void invalidateForShip(const ship* shipp);
|
||||
|
||||
}
|
||||
@@ -65,7 +65,7 @@ $Max Player Attackers: 2, 3, 4, 5, 99
|
||||
|
||||
;; maximum number of active (i.e. 'thrown') asteroids that can be
|
||||
;; heading toward a friendly ship at any given time
|
||||
$Max Incoming Asteroids: 3, 4, 5, 7, 10
|
||||
$Max Incoming Asteroids: 3, 4, 5, 7, 10
|
||||
|
||||
;; factor applied to damage suffered by the player
|
||||
$Player Damage Factor: 0.25, 0.5, 0.65, 0.85, 1
|
||||
@@ -181,9 +181,8 @@ $Player Autoaim FOV: 0, 0, 0, 0, 0
|
||||
$Detail Distance Multiplier: 0.125, 0.25, 1.0, 4.0, 8.0
|
||||
|
||||
;; General AI-related flags. These were previously all lumped together
|
||||
;; under the New AI mission flag.
|
||||
|
||||
|
||||
;; under the New AI mission flag.
|
||||
|
||||
;; if set, big ships can attack a beam turret that's firing on them
|
||||
;; from a ship that they don't currently have targeted.
|
||||
$big ships can attack beam turrets on untargeted ships: NO
|
||||
@@ -354,6 +353,10 @@ $no warp camera: NO
|
||||
|
||||
;; If set, this flag overrides the retail behavior whereby a ship
|
||||
;; assigned to guard a ship in a wing will instead guard the entire wing
|
||||
$ai guards specific ship in wing: NO
|
||||
$ai guards specific ship in wing: NO
|
||||
|
||||
;; maximum number of allies allowed to be rearming at the same time
|
||||
;; before the AI will hold off on requesting support
|
||||
$max allies rearming threshold: 2
|
||||
|
||||
#End
|
||||
@@ -10,6 +10,12 @@ out vec4 fragOut0; // Diffuse buffer
|
||||
out vec4 fragOut1; // Normal buffer
|
||||
out vec4 fragOut2; // Emissive buffer
|
||||
|
||||
flat in mat4 invModelMatrix;
|
||||
flat in vec3 decalDirection;
|
||||
flat in float normal_angle_cutoff;
|
||||
flat in float angle_fade_start;
|
||||
flat in float alpha_scale;
|
||||
|
||||
uniform sampler2D gDepthBuffer;
|
||||
uniform sampler2D gNormalBuffer;
|
||||
|
||||
@@ -23,24 +29,15 @@ layout (std140) uniform decalGlobalData {
|
||||
mat4 invViewMatrix;
|
||||
mat4 invProjMatrix;
|
||||
|
||||
vec3 ambientLight;
|
||||
|
||||
vec2 viewportSize;
|
||||
};
|
||||
|
||||
layout (std140) uniform decalInfoData {
|
||||
mat4 model_matrix;
|
||||
mat4 inv_model_matrix;
|
||||
|
||||
vec3 decal_direction;
|
||||
float normal_angle_cutoff;
|
||||
|
||||
int diffuse_index;
|
||||
int glow_index;
|
||||
int normal_index;
|
||||
float angle_fade_start;
|
||||
|
||||
float alpha_scale;
|
||||
int diffuse_blend_mode;
|
||||
|
||||
int glow_blend_mode;
|
||||
};
|
||||
|
||||
@@ -77,7 +74,7 @@ vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out v
|
||||
#endif
|
||||
|
||||
//Calculate angle between surface normal and decal direction
|
||||
float angle = acos(dot(normal, decal_direction));
|
||||
float angle = acos(dot(normal, decalDirection));
|
||||
|
||||
if (angle > normal_angle_cutoff) {
|
||||
// The angle between surface normal and decal direction is too big
|
||||
@@ -91,7 +88,7 @@ vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out v
|
||||
}
|
||||
|
||||
vec2 getDecalTexCoord(vec3 view_pos, inout float alpha) {
|
||||
vec4 object_pos = inv_model_matrix * invViewMatrix * vec4(view_pos, 1.0);
|
||||
vec4 object_pos = invModelMatrix * invViewMatrix * vec4(view_pos, 1.0);
|
||||
|
||||
bvec3 invalidComponents = greaterThan(abs(object_pos.xyz), vec3(0.5));
|
||||
bvec4 nanComponents = isnan(object_pos); // nan can happen some times if we have an infinite depth value
|
||||
@@ -111,6 +108,7 @@ void main() {
|
||||
vec3 frag_position = computeViewPosition(gl_FragCoord.xy);
|
||||
|
||||
float alpha = alpha_scale;
|
||||
|
||||
vec2 tex_coord = getDecalTexCoord(frag_position, alpha);
|
||||
|
||||
vec3 binormal;
|
||||
@@ -134,16 +132,6 @@ void main() {
|
||||
// Additive blending
|
||||
diffuse_out = vec4(color.rgb * alpha, 1.0);
|
||||
}
|
||||
|
||||
// The main model shader applies ambient lighting by drawing the ambient part of the texture into the emissive
|
||||
// texture. We do the same here to make sure the decal material is applied correctly
|
||||
if (glow_blend_mode == 0) {
|
||||
// Normal alpha blending
|
||||
emissive_out = vec4(color.rgb * ambientLight, color.a * alpha);
|
||||
} else {
|
||||
// Additive blending
|
||||
emissive_out = vec4(alpha * color.rgb * ambientLight, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (glow_index >= 0) {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
|
||||
in vec4 vertPosition;
|
||||
in mat4 vertModelMatrix;
|
||||
|
||||
flat out mat4 invModelMatrix;
|
||||
flat out vec3 decalDirection;
|
||||
flat out float normal_angle_cutoff;
|
||||
flat out float angle_fade_start;
|
||||
flat out float alpha_scale;
|
||||
|
||||
layout (std140) uniform decalGlobalData {
|
||||
mat4 viewMatrix;
|
||||
@@ -7,27 +14,29 @@ layout (std140) uniform decalGlobalData {
|
||||
mat4 invViewMatrix;
|
||||
mat4 invProjMatrix;
|
||||
|
||||
vec3 ambientLight;
|
||||
|
||||
vec2 viewportSize;
|
||||
};
|
||||
|
||||
layout (std140) uniform decalInfoData {
|
||||
mat4 model_matrix;
|
||||
mat4 inv_model_matrix;
|
||||
|
||||
vec3 decal_direction;
|
||||
float normal_angle_cutoff;
|
||||
|
||||
int diffuse_index;
|
||||
int glow_index;
|
||||
int normal_index;
|
||||
float angle_fade_start;
|
||||
|
||||
float alpha_scale;
|
||||
int diffuse_blend_mode;
|
||||
|
||||
int glow_blend_mode;
|
||||
};
|
||||
|
||||
void main() {
|
||||
gl_Position = projMatrix * viewMatrix * model_matrix * vertPosition;
|
||||
normal_angle_cutoff = vertModelMatrix[0][3];
|
||||
angle_fade_start = vertModelMatrix[1][3];
|
||||
alpha_scale = vertModelMatrix[2][3];
|
||||
|
||||
mat4 modelMatrix = vertModelMatrix;
|
||||
modelMatrix[0][3] = 0.0;
|
||||
modelMatrix[1][3] = 0.0;
|
||||
modelMatrix[2][3] = 0.0;
|
||||
|
||||
invModelMatrix = inverse(modelMatrix);
|
||||
decalDirection = mat3(viewMatrix) * modelMatrix[2].xyz;
|
||||
gl_Position = projMatrix * viewMatrix * modelMatrix * vertPosition;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ out vec4 fragOut5;
|
||||
void main()
|
||||
{
|
||||
fragOut0 = vec4(0.0, 0.0, 0.0, 1.0); // color
|
||||
fragOut1 = vec4(0.0, 0.0, -1000000.0, 1.0); // position
|
||||
fragOut1 = vec4(0.0, 0.0, -1000000.0, 0.0); // position
|
||||
fragOut2 = vec4(0.0, 0.0, 0.0, 1.0); // normal
|
||||
fragOut3 = vec4(0.0, 0.0, 0.0, 0.0); // specular
|
||||
fragOut4 = vec4(0.0, 0.0, 0.0, 1.0); // emissive
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//? #version 150
|
||||
#include "lighting.sdr" //! #include "lighting.sdr"
|
||||
|
||||
#include "gamma.sdr" //! #include "gamma.sdr"
|
||||
#include "shadows.sdr" //! #include "shadows.sdr"
|
||||
|
||||
out vec4 fragOut0;
|
||||
@@ -11,6 +11,11 @@ uniform sampler2D PositionBuffer;
|
||||
uniform sampler2D SpecBuffer;
|
||||
uniform sampler2DArray shadow_map;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
uniform samplerCube sEnvmap;
|
||||
uniform samplerCube sIrrmap;
|
||||
#endif
|
||||
|
||||
layout (std140) uniform globalDeferredData {
|
||||
mat4 shadow_mv_matrix;
|
||||
mat4 shadow_proj_matrix[4];
|
||||
@@ -97,7 +102,7 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
|
||||
discard;
|
||||
}
|
||||
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else if (lightType == LT_TUBE) { // Tube light
|
||||
vec3 beamVec = vec3(modelViewMatrix * vec4(0.0, 0.0, -scale.z, 0.0));
|
||||
vec3 beamDir = normalize(beamVec);
|
||||
@@ -138,7 +143,7 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
|
||||
discard;
|
||||
}
|
||||
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else if (lightType == LT_CONE) {
|
||||
lightDirOut = lightPosition - position.xyz;
|
||||
float coneDot = dot(normalize(-lightDirOut), coneDir);
|
||||
@@ -165,46 +170,108 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENV_MAP
|
||||
void ComputeEnvLight(float alpha, float ao, vec3 light_dir, vec3 eyeDir, vec3 normal, vec4 baseColor, vec4 specColor, out vec3 envLight) {
|
||||
// Bit of a hack here, pretend we're doing blinn-phong shading and use a (modified version of) the derivation from
|
||||
// Plausible Blinn-Phong Reflection of Standard Cube MIP-Maps - McGuire et al.
|
||||
// to determine the mip bias.
|
||||
// We use the specular term listed at http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
|
||||
// 1/(pi*alpha^2) * NdotM^((2/alpha^2)-2)
|
||||
// so let s = (2/alpha^2 - 2)
|
||||
// 1/2 log(s + 1) = 1/2 log(2/alpha^2 -1)
|
||||
|
||||
const float ENV_REZ = 512; // Ideally this would be #define'd and shader recompiled with envmap rez changes
|
||||
const float REZ_BIAS = log2(ENV_REZ * sqrt(3));
|
||||
|
||||
float alphaSqr = alpha * alpha;
|
||||
float rough_bias = 0.5 * log2(2/alphaSqr - 1);
|
||||
float mip_bias = REZ_BIAS - rough_bias;
|
||||
|
||||
// Sample light, using mip bias to blur it.
|
||||
vec3 env_light_dir = vec3(modelViewMatrix * vec4(light_dir, 0.0));
|
||||
vec4 specEnvColour = srgb_to_linear(textureLod(sEnvmap, env_light_dir, mip_bias));
|
||||
|
||||
vec3 halfVec = normal;
|
||||
|
||||
// Lots of hacks here to get things to look right. We aren't loading a BRDF split-sum integral texture
|
||||
// or properly calculating IBL levels.
|
||||
// Fresnel calculation as with standard lights.
|
||||
|
||||
vec3 fresnel = mix(specColor.rgb, FresnelSchlick(halfVec, eyeDir, specColor.rgb), specColor.a);
|
||||
|
||||
// Pseudo-IBL, so use k_IBL
|
||||
float k = alpha * alpha/ 2.0f;
|
||||
|
||||
float NdotL = max(dot(light_dir, normal),0);
|
||||
|
||||
float g1vNL = GeometrySchlickGGX(NdotL, k);
|
||||
vec3 specEnvLighting = specEnvColour.rgb * fresnel * g1vNL;
|
||||
|
||||
vec3 kD = vec3(1.0)-fresnel;
|
||||
kD *= (vec3(1.0) - specColor.rgb);
|
||||
vec3 diffEnvColor = srgb_to_linear(texture(sIrrmap, vec3(modelViewMatrix * vec4(normal, 0.0))).rgb);
|
||||
vec3 diffEnvLighting = kD * baseColor.rgb * diffEnvColor * ao;
|
||||
envLight = (specEnvLighting + diffEnvLighting) * baseColor.a;
|
||||
}
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 screenPos = gl_FragCoord.xy * vec2(invScreenWidth, invScreenHeight);
|
||||
vec3 position = texture(PositionBuffer, screenPos).xyz;
|
||||
vec4 position_buffer = texture(PositionBuffer, screenPos);
|
||||
vec3 position = position_buffer.xyz;
|
||||
|
||||
if(abs(dot(position, position)) < nearPlane * nearPlane)
|
||||
discard;
|
||||
|
||||
vec3 diffColor = texture(ColorBuffer, screenPos).rgb;
|
||||
vec4 diffuse = texture(ColorBuffer, screenPos);
|
||||
vec3 diffColor = diffuse.rgb;
|
||||
vec4 normalData = texture(NormalBuffer, screenPos);
|
||||
vec4 specColor = texture(SpecBuffer, screenPos);
|
||||
// The vector in the normal buffer could be longer than the unit vector since decal rendering only adds to the normal buffer
|
||||
vec3 normal = normalize(normalData.xyz);
|
||||
float gloss = normalData.a;
|
||||
float roughness = clamp(1.0f - gloss, 0.0f, 1.0f);
|
||||
float alpha = roughness * roughness;
|
||||
float fresnel = specColor.a;
|
||||
vec3 eyeDir = normalize(-position);
|
||||
|
||||
vec3 lightDir;
|
||||
float attenuation;
|
||||
float area_normalisation;
|
||||
vec3 reflectDir = reflect(-eyeDir, normal);
|
||||
GetLightInfo(position, alpha, reflectDir, lightDir, attenuation, area_normalisation);
|
||||
vec4 specColor = texture(SpecBuffer, screenPos);
|
||||
|
||||
if (enable_shadows) {
|
||||
vec4 fragShadowPos = shadow_mv_matrix * inv_view_matrix * vec4(position, 1.0);
|
||||
vec4 fragShadowUV[4];
|
||||
fragShadowUV[0] = transformToShadowMap(shadow_proj_matrix[0], 0, fragShadowPos);
|
||||
fragShadowUV[1] = transformToShadowMap(shadow_proj_matrix[1], 1, fragShadowPos);
|
||||
fragShadowUV[2] = transformToShadowMap(shadow_proj_matrix[2], 2, fragShadowPos);
|
||||
fragShadowUV[3] = transformToShadowMap(shadow_proj_matrix[3], 3, fragShadowPos);
|
||||
vec4 fragmentColor = vec4(1.0);
|
||||
|
||||
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowPos.z, fragShadowUV, fardist, middist,
|
||||
neardist, veryneardist);
|
||||
if (lightType == LT_AMBIENT) {
|
||||
float ao = position_buffer.w;
|
||||
fragmentColor.rgb = diffuseLightColor * diffColor * ao;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
vec3 envLight;
|
||||
ComputeEnvLight(alpha, ao, reflectDir, eyeDir, normal, diffuse, specColor, envLight);
|
||||
fragmentColor.rgb += envLight;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
float fresnel = specColor.a;
|
||||
|
||||
vec3 lightDir;
|
||||
float attenuation;
|
||||
float area_normalisation;
|
||||
GetLightInfo(position, alpha, reflectDir, lightDir, attenuation, area_normalisation);
|
||||
|
||||
if (enable_shadows) {
|
||||
vec4 fragShadowPos = shadow_mv_matrix * inv_view_matrix * vec4(position, 1.0);
|
||||
vec4 fragShadowUV[4];
|
||||
fragShadowUV[0] = transformToShadowMap(shadow_proj_matrix[0], 0, fragShadowPos);
|
||||
fragShadowUV[1] = transformToShadowMap(shadow_proj_matrix[1], 1, fragShadowPos);
|
||||
fragShadowUV[2] = transformToShadowMap(shadow_proj_matrix[2], 2, fragShadowPos);
|
||||
fragShadowUV[3] = transformToShadowMap(shadow_proj_matrix[3], 3, fragShadowPos);
|
||||
|
||||
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowPos.z, fragShadowUV, fardist, middist,
|
||||
neardist, veryneardist);
|
||||
}
|
||||
|
||||
vec3 halfVec = normalize(lightDir + eyeDir);
|
||||
float NdotL = clamp(dot(normal, lightDir), 0.0, 1.0);
|
||||
fragmentColor.rgb = computeLighting(specColor.rgb, diffColor, lightDir, normal.xyz, halfVec, eyeDir, roughness, fresnel, NdotL).rgb * diffuseLightColor * attenuation * area_normalisation;
|
||||
}
|
||||
|
||||
vec3 halfVec = normalize(lightDir + eyeDir);
|
||||
float NdotL = clamp(dot(normal, lightDir), 0.0, 1.0);
|
||||
vec4 fragmentColor = vec4(1.0);
|
||||
fragmentColor.rgb = computeLighting(specColor.rgb, diffColor, lightDir, normal.xyz, halfVec, eyeDir, roughness, fresnel, NdotL).rgb * diffuseLightColor * attenuation * area_normalisation;
|
||||
fragOut0 = max(fragmentColor, vec4(0.0));
|
||||
}
|
||||
@@ -29,7 +29,7 @@ layout (std140) uniform lightData {
|
||||
|
||||
void main()
|
||||
{
|
||||
if (lightType == LT_DIRECTIONAL) {
|
||||
if (lightType == LT_DIRECTIONAL || lightType == LT_AMBIENT) {
|
||||
gl_Position = vec4(vertPosition.xyz, 1.0);
|
||||
} else {
|
||||
gl_Position = projMatrix * modelViewMatrix * vec4(vertPosition.xyz * scale, 1.0);
|
||||
|
||||
@@ -3,6 +3,7 @@ const int LT_DIRECTIONAL = 0; // A light like a sun
|
||||
const int LT_POINT = 1; // A point light, like an explosion
|
||||
const int LT_TUBE = 2; // A tube light, like a fluorescent light
|
||||
const int LT_CONE = 3; // A cone light, like a flood light
|
||||
const int LT_AMBIENT = 4; // Directionless ambient light
|
||||
|
||||
const float SPEC_FACTOR_NO_SPEC_MAP = 0.1;
|
||||
const float GLOW_MAP_INTENSITY = 1.5;
|
||||
|
||||
@@ -32,7 +32,6 @@ layout (std140) uniform modelData {
|
||||
mat4 textureMatrix;
|
||||
mat4 shadow_mv_matrix;
|
||||
mat4 shadow_proj_matrix[4];
|
||||
mat4 envMatrix;
|
||||
|
||||
vec4 color;
|
||||
|
||||
@@ -50,6 +49,7 @@ layout (std140) uniform modelData {
|
||||
int n_lights;
|
||||
float defaultGloss;
|
||||
|
||||
//EXCLUSIVELY used for non-deferred rendering
|
||||
vec3 ambientFactor;
|
||||
int desaturate;
|
||||
|
||||
@@ -85,22 +85,16 @@ layout (std140) uniform modelData {
|
||||
float fardist;
|
||||
|
||||
int sGlowmapIndex;
|
||||
|
||||
int sSpecmapIndex;
|
||||
int sNormalmapIndex;
|
||||
int sAmbientmapIndex;
|
||||
|
||||
int sMiscmapIndex;
|
||||
|
||||
float alphaMult;
|
||||
|
||||
int flags;
|
||||
};
|
||||
|
||||
in VertexOutput {
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
vec3 envReflect;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
|
||||
mat3 tangentMatrix;
|
||||
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
|
||||
@@ -126,10 +120,6 @@ uniform sampler2DArray sGlowmap;
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_SPEC
|
||||
uniform sampler2DArray sSpecmap;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SPEC
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
uniform samplerCube sEnvmap;
|
||||
uniform samplerCube sIrrmap;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_NORMAL
|
||||
uniform sampler2DArray sNormalmap;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_NORMAL
|
||||
@@ -187,7 +177,7 @@ void GetLightInfo(int i, out vec3 lightDir, out float attenuation)
|
||||
vec3 CalculateLighting(vec3 normal, vec3 diffuseMaterial, vec3 specularMaterial, float gloss, float fresnel, float shadow, float aoFactor)
|
||||
{
|
||||
vec3 eyeDir = vec3(normalize(-vertIn.position).xyz);
|
||||
vec3 lightAmbient = (emissionFactor + ambientFactor * ambientFactor) * aoFactor; // ambientFactor^2 due to legacy OpenGL compatibility behavior
|
||||
vec3 lightAmbient = ambientFactor * aoFactor;
|
||||
vec3 lightDiffuse = vec3(0.0, 0.0, 0.0);
|
||||
vec3 lightSpecular = vec3(0.0, 0.0, 0.0);
|
||||
#pragma optionNV unroll all
|
||||
@@ -342,19 +332,7 @@ void main()
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_MISC
|
||||
|
||||
// Lights aren't applied when we are rendering to the G-buffers since that gets handled later
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_LIGHT
|
||||
// Ambient lighting still needs to be done since that counts as an "emissive" color
|
||||
vec3 lightAmbient = (emissionFactor + ambientFactor * ambientFactor) * aoFactors.x; // ambientFactor^2 due to legacy OpenGL compatibility behavior
|
||||
emissiveColor.rgb += baseColor.rgb * lightAmbient;
|
||||
#prereplace ELSE_FLAG //MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_SPEC
|
||||
baseColor.rgb += pow(1.0 - clamp(dot(eyeDir, normal), 0.0, 1.0), 5.0 * clamp(glossData, 0.01, 1.0)) * specColor.rgb;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SPEC
|
||||
// If there is no lighting then we copy the color data so far into the
|
||||
emissiveColor.rgb += baseColor.rgb;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace ELSE_FLAG //MODEL_SDR_FLAG_DEFERRED
|
||||
#prereplace IF_NOT_FLAG MODEL_SDR_FLAG_DEFERRED
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_LIGHT
|
||||
float shadow = 1.0;
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_SHADOWS
|
||||
@@ -368,54 +346,6 @@ void main()
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_LIGHT
|
||||
// Bit of a hack here, pretend we're doing blinn-phong shading and use a (modified version of) the derivation from
|
||||
// Plausible Blinn-Phong Reflection of Standard Cube MIP-Maps - McGuire et al.
|
||||
// to determine the mip bias.
|
||||
// We use the specular term listed at http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
|
||||
// 1/(pi*alpha^2) * NdotM^((2/alpha^2)-2)
|
||||
// so let s = (2/alpha^2 - 2)
|
||||
// 1/2 log(s + 1) = 1/2 log(2/alpha^2 -1)
|
||||
|
||||
const float ENV_REZ = 512; // Ideally this would be #define'd and shader recompiled with envmap rez changes
|
||||
const float REZ_BIAS = log2(ENV_REZ * sqrt(3));
|
||||
|
||||
float roughness = clamp(1.0f - glossData, 0.0f, 1.0f);
|
||||
float alpha = roughness * roughness;
|
||||
float alphaSqr = alpha * alpha;
|
||||
float rough_bias = 0.5 * log2(2/alphaSqr - 1);
|
||||
float mip_bias = REZ_BIAS - rough_bias;
|
||||
|
||||
// Sample light, using mip bias to blur it.
|
||||
vec3 light_dir = reflect(-eyeDir, normal);
|
||||
vec3 env_light_dir = vec3(envMatrix * vec4(light_dir, 0.0));
|
||||
vec4 specEnvColour = srgb_to_linear(textureLod(sEnvmap, env_light_dir, mip_bias));
|
||||
|
||||
vec3 halfVec = normal;
|
||||
|
||||
// Lots of hacks here to get things to look right. We aren't loading a BRDF split-sum integral texture
|
||||
// or properly calculating IBL levels.
|
||||
// Fresnel calculation as with standard lights.
|
||||
|
||||
vec3 fresnel = mix(specColor.rgb, FresnelSchlick(halfVec, eyeDir, specColor.rgb), specColor.a);
|
||||
|
||||
// Pseudo-IBL, so use k_IBL
|
||||
float k = alpha * alpha/ 2.0f;
|
||||
|
||||
float NdotL = max(dot(light_dir, normal),0);
|
||||
|
||||
float g1vNL = GeometrySchlickGGX(NdotL, k);
|
||||
vec3 specEnvLighting = specEnvColour.rgb * fresnel * g1vNL;
|
||||
|
||||
vec3 kD = vec3(1.0)-fresnel;
|
||||
kD *= (vec3(1.0) - specColor.rgb);
|
||||
vec3 diffEnvColor = srgb_to_linear(texture(sIrrmap, vec3(envMatrix * vec4(normal, 0.0))).rgb);
|
||||
vec3 diffEnvLighting = kD * baseColor.rgb * diffEnvColor * aoFactors.x;
|
||||
emissiveColor.rgb += (specEnvLighting + diffEnvLighting) * baseColor.a;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_GLOW
|
||||
vec3 glowColor = texture(sGlowmap, vec3(texCoord, float(sGlowmapIndex))).rgb;
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_MISC
|
||||
@@ -483,10 +413,22 @@ void main()
|
||||
baseColor.rgb += emissiveColor.rgb;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
|
||||
#prereplace IF_NOT_FLAG MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_SPEC
|
||||
baseColor.rgb += pow(1.0 - clamp(dot(eyeDir, normal), 0.0, 1.0), 5.0 * clamp(glossData, 0.01, 1.0)) * specColor.rgb;
|
||||
glossData = 0;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SPEC
|
||||
// If there is no lighting then we copy the color data so far into the emissive.
|
||||
emissiveColor.rgb += baseColor.rgb;
|
||||
aoFactors.x = 0;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
|
||||
|
||||
fragOut0 = baseColor;
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
|
||||
fragOut1 = vec4(vertIn.position.xyz, 1.0);
|
||||
fragOut1 = vec4(vertIn.position.xyz, aoFactors.x);
|
||||
fragOut2 = vec4(normal, glossData);
|
||||
fragOut3 = vec4(specColor.rgb, fresnelFactor);
|
||||
fragOut4 = emissiveColor;
|
||||
|
||||
@@ -39,7 +39,6 @@ layout (std140) uniform modelData {
|
||||
mat4 textureMatrix;
|
||||
mat4 shadow_mv_matrix;
|
||||
mat4 shadow_proj_matrix[4];
|
||||
mat4 envMatrix;
|
||||
|
||||
vec4 color;
|
||||
|
||||
@@ -103,10 +102,6 @@ layout (std140) uniform modelData {
|
||||
};
|
||||
|
||||
in VertexOutput {
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
vec3 envReflect;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
|
||||
mat3 tangentMatrix;
|
||||
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
|
||||
@@ -131,10 +126,6 @@ in VertexOutput {
|
||||
} vertIn[];
|
||||
|
||||
out VertexOutput {
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
vec3 envReflect;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
|
||||
mat3 tangentMatrix;
|
||||
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
|
||||
@@ -181,10 +172,6 @@ out VertexOutput {
|
||||
|
||||
gl_Layer = instanceID;
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
|
||||
vertOut.envReflect = vertIn[vert].envReflect;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_NORMAL
|
||||
vertOut.tangentMatrix = vertIn[vert].tangentMatrix;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_NORMAL
|
||||
@@ -239,10 +226,6 @@ out VertexOutput {
|
||||
vertOut.normal = vertIn[vert].normal;
|
||||
vertOut.texCoord = vertIn[vert].texCoord;
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
|
||||
vertOut.envReflect = vertIn[vert].envReflect;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_NORMAL
|
||||
vertOut.tangentMatrix = vertIn[vert].tangentMatrix;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_NORMAL
|
||||
|
||||
@@ -34,7 +34,6 @@ layout (std140) uniform modelData {
|
||||
mat4 textureMatrix;
|
||||
mat4 shadow_mv_matrix;
|
||||
mat4 shadow_proj_matrix[4];
|
||||
mat4 envMatrix;
|
||||
|
||||
vec4 color;
|
||||
|
||||
@@ -102,10 +101,6 @@ uniform samplerBuffer transform_tex;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_TRANSFORM
|
||||
|
||||
out VertexOutput {
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
vec3 envReflect;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
|
||||
|
||||
mat3 tangentMatrix;
|
||||
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
|
||||
@@ -193,12 +188,6 @@ void main()
|
||||
vec3 b = cross(normal, t) * vertTangent.w;
|
||||
vertOut.tangentMatrix = mat3(t, b, normal);
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
|
||||
// Environment mapping reflection vector.
|
||||
vec3 envReflect = reflect(normalize(position.xyz), normal);
|
||||
vertOut.envReflect = vec3(envMatrix * vec4(envReflect, 0.0));
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_FOG
|
||||
vertOut.fogDist = clamp((gl_Position.z - fogStart) * 0.75 * fogScale, 0.0, 1.0);
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_FOG
|
||||
|
||||
@@ -14,22 +14,21 @@ SDR_FLAG(MODEL_SDR_FLAG_HDR , (1 << 2) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_DIFFUSE , (1 << 3) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_GLOW , (1 << 4) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_SPEC , (1 << 5) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_ENV , (1 << 6) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_NORMAL , (1 << 7) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_AMBIENT , (1 << 8) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_MISC , (1 << 9) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_TEAMCOLOR , (1 << 10), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_FOG , (1 << 11), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_TRANSFORM , (1 << 12), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_SHADOWS , (1 << 13), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_THRUSTER , (1 << 14), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_ALPHA_MULT , (1 << 15), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_NORMAL , (1 << 6) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_AMBIENT , (1 << 7) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_MISC , (1 << 8) , false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_TEAMCOLOR , (1 << 9), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_FOG , (1 << 10), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_TRANSFORM , (1 << 11), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_SHADOWS , (1 << 12), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_THRUSTER , (1 << 13), false)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_ALPHA_MULT , (1 << 14), false)
|
||||
|
||||
#ifndef MODEL_SDR_FLAG_MODE_GLSL
|
||||
//The following ones are used ONLY as compile-time flags, but they still need to be defined here to ensure no conflict occurs
|
||||
//But since these are checked with ifdefs even for the large shader, they must never be available in GLSL mode
|
||||
|
||||
SDR_FLAG(MODEL_SDR_FLAG_SHADOW_MAP , (1 << 16), true)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_THICK_OUTLINES, (1 << 17), true)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_SHADOW_MAP , (1 << 15), true)
|
||||
SDR_FLAG(MODEL_SDR_FLAG_THICK_OUTLINES, (1 << 16), true)
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,14 @@ layout (std140) uniform genericData {
|
||||
|
||||
vec3 tint;
|
||||
float dither;
|
||||
|
||||
// these are blank, valid slots for modders to create custom effects
|
||||
// that can be defined in post_processing.tbl and coded below
|
||||
vec3 custom_effect_vec3_a;
|
||||
float custom_effect_float_a;
|
||||
|
||||
vec3 custom_effect_vec3_b;
|
||||
float custom_effect_float_b;
|
||||
};
|
||||
|
||||
void main()
|
||||
|
||||
@@ -26,48 +26,59 @@ float sampleNoPCF(sampler2DArray shadow_map, float shadowDepth, int cascade, vec
|
||||
return computeShadowFactor(shadowDepth, sampleShadowMap(shadow_map, shadowUV[cascade].xy, vec2(0.0, 0.0), cascade, 1.0/1024.0), 0.05);
|
||||
}
|
||||
|
||||
float samplePoissonPCF(sampler2DArray shadow_map, float shadowDepth, int cascade, vec4 shadowUV[4])
|
||||
float samplePoissonPCF(sampler2DArray shadow_map, float shadowDepth, int cascade, vec4 shadowUV[4], bool use_simple_pass)
|
||||
{
|
||||
if(cascade > 3 || cascade < 0) return 1.0;
|
||||
vec2 poissonDisc[16];
|
||||
poissonDisc[0] = vec2(-0.76275, -0.3432573);
|
||||
poissonDisc[1] = vec2(-0.5226235, -0.8277544);
|
||||
poissonDisc[2] = vec2(-0.3780261, 0.01528688);
|
||||
poissonDisc[3] = vec2(-0.7742821, 0.4245702);
|
||||
poissonDisc[4] = vec2(0.04196143, -0.02622231);
|
||||
poissonDisc[5] = vec2(-0.2974772, -0.4722782);
|
||||
poissonDisc[6] = vec2(-0.516093, 0.71495);
|
||||
poissonDisc[7] = vec2(-0.3257416, 0.3910343);
|
||||
poissonDisc[8] = vec2(0.2705966, 0.6670476);
|
||||
poissonDisc[9] = vec2(0.4918377, 0.1853267);
|
||||
poissonDisc[10] = vec2(0.4428544, -0.6251478);
|
||||
poissonDisc[11] = vec2(-0.09204347, 0.9267113);
|
||||
poissonDisc[12] = vec2(0.391505, -0.2558275);
|
||||
poissonDisc[13] = vec2(0.05605913, -0.7570801);
|
||||
poissonDisc[14] = vec2(0.81772, -0.02475523);
|
||||
poissonDisc[15] = vec2(0.6890262, 0.5191521);
|
||||
|
||||
vec2 poissonDisc[16] = vec2[](
|
||||
vec2(-0.76275, -0.3432573),
|
||||
vec2(-0.5226235, -0.8277544),
|
||||
vec2(-0.3780261, 0.01528688),
|
||||
vec2(-0.7742821, 0.4245702),
|
||||
vec2(0.04196143, -0.02622231),
|
||||
vec2(-0.2974772, -0.4722782),
|
||||
vec2(-0.516093, 0.71495),
|
||||
vec2(-0.3257416, 0.3910343),
|
||||
vec2(0.2705966, 0.6670476),
|
||||
vec2(0.4918377, 0.1853267),
|
||||
vec2(0.4428544, -0.6251478),
|
||||
vec2(-0.09204347, 0.9267113),
|
||||
vec2(0.391505, -0.2558275),
|
||||
vec2(0.05605913, -0.7570801),
|
||||
vec2(0.81772, -0.02475523),
|
||||
vec2(0.6890262, 0.5191521)
|
||||
);
|
||||
|
||||
float maxUVOffset[4];
|
||||
maxUVOffset[0] = 1.0/300.0;
|
||||
maxUVOffset[1] = 1.0/250.0;
|
||||
maxUVOffset[2] = 1.0/200.0;
|
||||
maxUVOffset[3] = 1.0/200.0;
|
||||
vec2 sum = sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[0], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[1], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[2], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[3], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[4], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[5], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[6], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[7], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[8], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[9], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[10], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[11], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[12], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[13], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[14], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[15], cascade, maxUVOffset[cascade])*(1.0/16.0);
|
||||
return computeShadowFactor(shadowDepth, sum, 0.1);
|
||||
|
||||
if (use_simple_pass) {
|
||||
// internal cockpit shadows
|
||||
// bias is a bit smaller to pick up on close/sharp cockpit geometry
|
||||
// favors shadow over light to avoid complex geometry halos
|
||||
float visibility = 1.0f;
|
||||
for (int i=0; i<16; i++) {
|
||||
vec2 shadow_sample = sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[i], cascade, maxUVOffset[cascade]);
|
||||
// extra bias value tuned for this distance
|
||||
if( ((shadow_sample.x - 0.002f) > shadowDepth) ) {
|
||||
visibility -= (1.0f/16.0f);
|
||||
}
|
||||
}
|
||||
return visibility;
|
||||
} else {
|
||||
// default external shadows
|
||||
// bias is a bit larger to only pick up larger geometry
|
||||
// favors light over shadow to avoid flickering/spotty shadows
|
||||
vec2 sum = vec2(0.0f);
|
||||
for (int i=0; i<16; i++) {
|
||||
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[i], cascade, maxUVOffset[cascade]);
|
||||
}
|
||||
return computeShadowFactor(shadowDepth, sum*(1.0f/16.0f), 0.1f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
float getShadowValue(sampler2DArray shadow_map, float depth, float shadowDepth, vec4 shadowUV[4], float fardist,
|
||||
@@ -86,10 +97,20 @@ float getShadowValue(sampler2DArray shadow_map, float depth, float shadowDepth,
|
||||
cascade_start_dist[3] = middist;
|
||||
cascade_start_dist[4] = fardist;
|
||||
if(cascade > 3 || cascade < 0) return 1.0;
|
||||
|
||||
bool use_simple_pass;
|
||||
if (fardist < 50.0f) {
|
||||
// internal cockpit shadows
|
||||
use_simple_pass = true;
|
||||
} else {
|
||||
// default external shadows
|
||||
use_simple_pass = false;
|
||||
}
|
||||
|
||||
float dist_threshold = (cascade_start_dist[cascade+1] - cascade_start_dist[cascade])*0.2;
|
||||
if(cascade_start_dist[cascade+1] - dist_threshold > depth)
|
||||
return samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV);
|
||||
return mix(samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV), samplePoissonPCF(shadow_map, shadowDepth, cascade+1, shadowUV),
|
||||
return samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV, use_simple_pass);
|
||||
return mix(samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV, use_simple_pass), samplePoissonPCF(shadow_map, shadowDepth, cascade+1, shadowUV, use_simple_pass),
|
||||
smoothstep(cascade_start_dist[cascade+1] - dist_threshold, cascade_start_dist[cascade+1], depth));
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,6 @@ void main()
|
||||
vec3 sampleposition = position / nebSize + 0.5;
|
||||
vec4 volume_sample = textureGrad(volume_tex, sampleposition, gradX, gradY);
|
||||
|
||||
float stepsize_current = min(max(stepsize, volume_sample.x * udfScale), mintMax - stept);
|
||||
|
||||
#ifdef DO_EDGE_SMOOTHING
|
||||
//Average 3D texel with texels on corner, in an attempt to reduce jagged edges.
|
||||
float stepcolor_alpha = volume_sample.a;
|
||||
@@ -121,6 +119,8 @@ void main()
|
||||
float stepcolor_alpha = volume_sample.a;
|
||||
#endif
|
||||
|
||||
float stepsize_current = min(max(stepsize, step(stepcolor_alpha, 0.01) * volume_sample.x * udfScale), mintMax - stept);
|
||||
|
||||
float stepalpha = -(pow(alphalim, 1.0 / (opacitydistance / stepsize_current)) - 1.0f) * stepcolor_alpha;
|
||||
//All the following computations are just required if we have a stepcoloralpha that is non-zero.
|
||||
if(stepcolor_alpha > 0.01)
|
||||
@@ -151,7 +151,7 @@ void main()
|
||||
//Emissive
|
||||
cumnebdist += stepcolor_alpha * stepsize_current;
|
||||
vec3 emissive_lod = textureLod(emissive, fragTexCoord.xy, clamp(cumnebdist * emissiveSpreadFactor, 0, float(textureQueryLevels(emissive) - 1))).rgb;
|
||||
vec3 stepcolor_emissive = emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((depth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity;
|
||||
vec3 stepcolor_emissive = clamp(emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((depth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity, 0, 1);
|
||||
|
||||
//Step finish
|
||||
vec3 stepcolor = clamp(stepcolor_diffuse + stepcolor_emissive, 0, 1);
|
||||
@@ -165,7 +165,5 @@ void main()
|
||||
break;
|
||||
}
|
||||
|
||||
cumcolor += cumOMAlpha * color_in.rgb;
|
||||
|
||||
fragOut0 = vec4(cumcolor, 1);
|
||||
fragOut0 = vec4(cumOMAlpha * color_in.rgb + ((1.0 - cumOMAlpha) * cumcolor), 1);
|
||||
}
|
||||
@@ -110,9 +110,9 @@ $Fog:
|
||||
+Start dist: 10.0
|
||||
+Compl dist: 500.0
|
||||
$AI:
|
||||
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing")
|
||||
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "attack ship type" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing")
|
||||
+Accept Player Orders: YES
|
||||
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" )
|
||||
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "attack ship type" "depart" "disable subsys" )
|
||||
+Auto attacks: YES
|
||||
+Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "fighter" "bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Guards attack this: YES
|
||||
@@ -138,9 +138,9 @@ $Fog:
|
||||
+Start dist: 10.0
|
||||
+Compl dist: 500.0
|
||||
$AI:
|
||||
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" )
|
||||
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "attack ship type" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" )
|
||||
+Accept Player Orders: YES
|
||||
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" )
|
||||
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "attack ship type" "depart" "disable subsys" )
|
||||
+Auto attacks: YES
|
||||
+Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "fighter" "bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
|
||||
+Guards attack this: YES
|
||||
@@ -156,6 +156,7 @@ $Praise Destruction: YES
|
||||
$On Hotkey List: YES
|
||||
$Target as Threat: YES
|
||||
$Show Attack Direction: YES
|
||||
$Scannable: YES
|
||||
$Max Debris Speed: 150
|
||||
$FF Multiplier: 1.0
|
||||
$EMP Multiplier: 2.0
|
||||
@@ -186,7 +187,6 @@ $Praise Destruction: YES
|
||||
$On Hotkey List: YES
|
||||
$Target as Threat: YES
|
||||
$Show Attack Direction: YES
|
||||
$Scannable: YES
|
||||
$Max Debris Speed: 150
|
||||
$FF Multiplier: 1.0
|
||||
$EMP Multiplier: 1.75
|
||||
|
||||
@@ -63,5 +63,14 @@ $AlwaysOn: false
|
||||
$Default: 0.0
|
||||
$Div: 50
|
||||
$Add: 0
|
||||
|
||||
|
||||
$Name: tint
|
||||
$Uniform: tint
|
||||
$Define: FLAG_TINT
|
||||
$AlwaysOn: false
|
||||
$Default: 0.0
|
||||
$Div: 1
|
||||
$Add: 0
|
||||
$RGB: 0.2,0.2,0.2
|
||||
|
||||
#End
|
||||
+44
-43
@@ -437,48 +437,49 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
default:
|
||||
error_display(0, "Invalid warp model style. Must be classic or cinematic.");
|
||||
}
|
||||
|
||||
// Set warp_model_style options if cinematic style is chosen
|
||||
if (fi->warp_model_style == warp_style::CINEMATIC) {
|
||||
if (optional_string("+Warp size ratio:")) {
|
||||
stuff_float(&fi->warp_size_ratio);
|
||||
} else {
|
||||
fi->warp_size_ratio = 1.6f;
|
||||
}
|
||||
|
||||
// The first two values need to be implied multiples of PI
|
||||
// for convenience. These shouldn't need to be faster than a full
|
||||
// rotation per second, which is already ridiculous.
|
||||
if (optional_string("+Rotation anim:")) {
|
||||
stuff_float_list(fi->rot_anim, 3);
|
||||
|
||||
CLAMP(fi->rot_anim[0], 0.0f, 2.0f);
|
||||
CLAMP(fi->rot_anim[1], 0.0f, 2.0f);
|
||||
fi->rot_anim[2] = MAX(0.0f, fi->rot_anim[2]);
|
||||
} else {
|
||||
// PI / 2.75f, PI / 10.0f, 2.0f
|
||||
fi->rot_anim[0] = 0.365f;
|
||||
fi->rot_anim[1] = 0.083f;
|
||||
fi->rot_anim[2] = 2.0f;
|
||||
}
|
||||
|
||||
// Variable frame rate for faster propagation of ripples
|
||||
if (optional_string("+Frame anim:")) {
|
||||
stuff_float_list(fi->frame_anim, 3);
|
||||
|
||||
// A frame rate that is 4 times the normal speed is ridiculous
|
||||
CLAMP(fi->frame_anim[0], 0.0f, 4.0f);
|
||||
CLAMP(fi->frame_anim[1], 1.0f, 4.0f);
|
||||
fi->frame_anim[2] = MAX(0.0f, fi->frame_anim[2]);
|
||||
} else {
|
||||
fi->frame_anim[0] = 1.0f;
|
||||
fi->frame_anim[1] = 1.0f;
|
||||
fi->frame_anim[2] = 3.0f;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (first_time) {
|
||||
fi->warp_model_style = warp_style::CLASSIC;
|
||||
}
|
||||
|
||||
// Set warp_model_style options if cinematic style is chosen
|
||||
if (fi->warp_model_style == warp_style::CINEMATIC) {
|
||||
if (optional_string("+Warp size ratio:")) {
|
||||
stuff_float(&fi->warp_size_ratio);
|
||||
} else {
|
||||
fi->warp_size_ratio = 1.6f;
|
||||
}
|
||||
|
||||
// The first two values need to be implied multiples of PI
|
||||
// for convenience. These shouldn't need to be faster than a full
|
||||
// rotation per second, which is already ridiculous.
|
||||
if (optional_string("+Rotation anim:")) {
|
||||
stuff_float_list(fi->rot_anim, 3);
|
||||
|
||||
CLAMP(fi->rot_anim[0], 0.0f, 2.0f);
|
||||
CLAMP(fi->rot_anim[1], 0.0f, 2.0f);
|
||||
fi->rot_anim[2] = MAX(0.0f, fi->rot_anim[2]);
|
||||
} else {
|
||||
// PI / 2.75f, PI / 10.0f, 2.0f
|
||||
fi->rot_anim[0] = 0.365f;
|
||||
fi->rot_anim[1] = 0.083f;
|
||||
fi->rot_anim[2] = 2.0f;
|
||||
}
|
||||
|
||||
// Variable frame rate for faster propagation of ripples
|
||||
if (optional_string("+Frame anim:")) {
|
||||
stuff_float_list(fi->frame_anim, 3);
|
||||
|
||||
// A frame rate that is 4 times the normal speed is ridiculous
|
||||
CLAMP(fi->frame_anim[0], 0.0f, 4.0f);
|
||||
CLAMP(fi->frame_anim[1], 1.0f, 4.0f);
|
||||
fi->frame_anim[2] = MAX(0.0f, fi->frame_anim[2]);
|
||||
} else {
|
||||
fi->frame_anim[0] = 1.0f;
|
||||
fi->frame_anim[1] = 1.0f;
|
||||
fi->frame_anim[2] = 3.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required_string("#End");
|
||||
@@ -1150,12 +1151,12 @@ static float cutscene_wormhole(float t) {
|
||||
float fireball_wormhole_intensity(fireball *fb)
|
||||
{
|
||||
float t = fb->time_elapsed;
|
||||
|
||||
float rad = cutscene_wormhole(t / fb->warp_open_duration);
|
||||
float rad;
|
||||
|
||||
fireball_info* fi = &Fireball_info[fb->fireball_info_index];
|
||||
|
||||
if (fi->warp_model_style == warp_style::CINEMATIC) {
|
||||
rad = cutscene_wormhole(t / fb->warp_open_duration);
|
||||
rad *= cutscene_wormhole((fb->total_time - t) / fb->warp_close_duration);
|
||||
rad /= cutscene_wormhole(fb->total_time / (2.0f * fb->warp_open_duration));
|
||||
rad /= cutscene_wormhole(fb->total_time / (2.0f * fb->warp_close_duration));
|
||||
@@ -1268,7 +1269,7 @@ void fireball_render(object* obj, model_draw_list *scene)
|
||||
flare_rad,
|
||||
fi->flicker_magnitude,
|
||||
obj->radius,
|
||||
fi->use_3d_warp || (fb->flags & FBF_WARP_3D) != 0,
|
||||
!Is_standalone && (fi->use_3d_warp || (fb->flags & FBF_WARP_3D) != 0),
|
||||
fi->warp_glow_bitmap,
|
||||
fi->warp_ball_bitmap,
|
||||
fi->warp_model_id,
|
||||
@@ -1295,7 +1296,7 @@ int fireball_get_count()
|
||||
|
||||
void stuff_fireball_index_list(SCP_vector<int> &list, const char *name)
|
||||
{
|
||||
stuff_int_list(list, RAW_INTEGER_TYPE);
|
||||
stuff_int_list(list, ParseLookupType::FIREBALL_INFO_TYPE);
|
||||
|
||||
list.erase(std::remove_if(list.begin(), list.end(), [&](int index) {
|
||||
if (!SCP_vector_inbounds(Fireball_info, index)) {
|
||||
|
||||
@@ -324,7 +324,7 @@ void parse_colors(const char *filename)
|
||||
// if so, get its rgba values and initialise it using them
|
||||
mprintf(("'%s' has been redefined.\n", color_names[i]));
|
||||
//if (check_for_string("(")) {
|
||||
stuff_int_list(rgba, 4, RAW_INTEGER_TYPE);
|
||||
stuff_int_list(rgba, 4, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
for (j = 0; j < 4; j++) {
|
||||
if (rgba[j] < 0) {
|
||||
Warning(LOCATION, "RGBA value for '%s' in %s too low (%d), capping to 0.\n", color_names[i], filename, rgba[j]);
|
||||
@@ -388,9 +388,9 @@ void parse_everything_else(const char *filename)
|
||||
Warning(LOCATION, "Team color in '%s' defined with a name of '%s'; this won't be usable due to 'None' being used for a lack of a team color by the engine.\n", filename, temp2);
|
||||
}
|
||||
|
||||
if (required_string("$Team Stripe Color:")) {
|
||||
if (required_string_either("$Team Stripe Color:", "$Team Stripe Colour:", true) >= 0) {
|
||||
int rgb[3];
|
||||
stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
|
||||
stuff_int_list(rgb, 3, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
for (i = 0; i < 3; i++) {
|
||||
CLAMP(rgb[i], 0, 255);
|
||||
}
|
||||
@@ -400,9 +400,9 @@ void parse_everything_else(const char *filename)
|
||||
temp_color.stripe.b = rgb[2] / 255.0f;
|
||||
}
|
||||
|
||||
if (required_string("$Team Base Color:")) {
|
||||
if (required_string_either("$Team Base Color:", "$Team Base Colour:", true) >= 0) {
|
||||
int rgb[3];
|
||||
stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
|
||||
stuff_int_list(rgb, 3, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
for (i = 0; i < 3; i++) {
|
||||
CLAMP(rgb[i], 0, 255);
|
||||
}
|
||||
@@ -445,7 +445,7 @@ void parse_everything_else(const char *filename)
|
||||
mprintf(("'%s' has been redefined.\n", color_names[i]));
|
||||
if (check_for_string("(")) {
|
||||
// If we have a list of integers, use them.
|
||||
stuff_int_list(rgba, 4, RAW_INTEGER_TYPE);
|
||||
stuff_int_list(rgba, 4, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
for (j = 0; j < 4; j++) {
|
||||
if (rgba[j] < 0)
|
||||
{
|
||||
@@ -523,7 +523,7 @@ void parse_everything_else(const char *filename)
|
||||
rgba[3] = 255; // Odds are pretty high you want it to have full alpha...
|
||||
|
||||
if (check_for_string("(")) {
|
||||
stuff_int_list(rgba, 4, RAW_INTEGER_TYPE);
|
||||
stuff_int_list(rgba, 4, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
for (j = 0; j < 4; j++) {
|
||||
if (rgba[j] < 0)
|
||||
{
|
||||
|
||||
@@ -105,7 +105,7 @@ BOOL SCP_mspdbcs_ResolveSymbol( HANDLE hProcess, UINT_PTR dwAddress, SCP_mspdbcs
|
||||
|
||||
if ( siSymbol.dwOffset != 0 )
|
||||
{
|
||||
sprintf_s( szWithOffset, SCP_MSPDBCS_MAX_SYMBOL_LENGTH, "%s + %lld bytes", pszSymbol, siSymbol.dwOffset );
|
||||
snprintf( szWithOffset, SCP_MSPDBCS_MAX_SYMBOL_LENGTH, "%s + %lld bytes", pszSymbol, siSymbol.dwOffset );
|
||||
szWithOffset[ SCP_MSPDBCS_MAX_SYMBOL_LENGTH - 1 ] = '\0'; /* Because sprintf doesn't guarantee NULL terminating */
|
||||
pszSymbol = szWithOffset;
|
||||
}
|
||||
|
||||
@@ -308,6 +308,20 @@ constexpr bool LoggingEnabled = false;
|
||||
ASSUME( expr );\
|
||||
} while (false)
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
bool CallAssert(bool val, const char *msg, const char *filename, int linenum, T assertMsgFunc)
|
||||
{
|
||||
if (!val)
|
||||
assertMsgFunc(msg, filename, linenum, nullptr);
|
||||
ASSUME(val);
|
||||
return true;
|
||||
}
|
||||
#if defined(NDEBUG)
|
||||
# define AssertExpr(expr) (true)
|
||||
#else
|
||||
# define AssertExpr(expr) CallAssert(expr, #expr, __FILE__, __LINE__, os::dialogs::AssertMessage)
|
||||
#endif
|
||||
/*******************NEVER COMMENT Assert ************************************************/
|
||||
|
||||
// Goober5000 - define Verify for use in both release and debug mode
|
||||
@@ -397,15 +411,6 @@ const size_t INVALID_SIZE = static_cast<size_t>(-1);
|
||||
// the trailing underscores are to avoid conflicts with previously #define'd tokens
|
||||
enum class TriStateBool : int { FALSE_ = 0, TRUE_ = 1, UNKNOWN_ = -1 };
|
||||
|
||||
|
||||
// lod checker for (modular) table parsing
|
||||
typedef struct lod_checker {
|
||||
char filename[MAX_FILENAME_LEN];
|
||||
int num_lods;
|
||||
int override;
|
||||
} lod_checker;
|
||||
|
||||
|
||||
// Callback Loading function.
|
||||
// If you pass a function to this, that function will get called
|
||||
// around 10x per second, so you can update the screen.
|
||||
|
||||
@@ -242,10 +242,11 @@ const SCP_vector<std::pair<int, std::pair<const char*, int>>> DetailLevelValues
|
||||
|
||||
static void parse_model_detail_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
@@ -272,10 +273,11 @@ const auto ModelDetailOption __UNUSED = options::OptionBuilder<int>("Graphics.De
|
||||
|
||||
static void parse_texture_detail_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
@@ -302,10 +304,11 @@ const auto TexturesOption __UNUSED = options::OptionBuilder<int>("Graphics.Textu
|
||||
|
||||
static void parse_particles_detail_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
@@ -332,10 +335,11 @@ const auto ParticlesOption __UNUSED = options::OptionBuilder<int>("Graphics.Part
|
||||
|
||||
static void parse_debris_detail_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
@@ -362,10 +366,11 @@ const auto SmallDebrisOption __UNUSED = options::OptionBuilder<int>("Graphics.Sm
|
||||
|
||||
static void parse_shield_detail_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
@@ -392,10 +397,11 @@ const auto ShieldEffectsOption __UNUSED = options::OptionBuilder<int>("Graphics.
|
||||
|
||||
static void parse_stars_detail_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
|
||||
@@ -46,6 +46,30 @@ struct is_dereferenceable_pointer<T, typename std::enable_if_t<std::is_pointer_v
|
||||
template<typename T>
|
||||
inline constexpr bool is_dereferenceable_pointer_v = is_dereferenceable_pointer<T>::value;
|
||||
|
||||
template<typename T, typename Enable = void>
|
||||
struct is_smart_pointer : std::false_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_pointer<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::shared_ptr<typename T::element_type>>>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_pointer<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::shared_ptr<typename T::element_type[]>>>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_pointer<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::unique_ptr<typename T::element_type>>>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_pointer<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::unique_ptr<typename T::element_type[]>>>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_pointer<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::weak_ptr<typename T::element_type>>>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_pointer<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::weak_ptr<typename T::element_type[]>>>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
inline constexpr bool is_smart_pointer_v = is_smart_pointer<T>::value;
|
||||
|
||||
template<class T, template<class...> class U>
|
||||
inline constexpr bool is_instance_of_v = std::false_type{};
|
||||
|
||||
|
||||
@@ -80,6 +80,21 @@ bool SCP_truncate(SCP_string &str, size_t len)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SCP_trim(SCP_string& str)
|
||||
{
|
||||
auto start = str.find_first_not_of(" \t\r\n");
|
||||
auto end = str.find_last_not_of(" \t\r\n");
|
||||
if (start == SCP_string::npos) {
|
||||
str.clear();
|
||||
return true;
|
||||
}
|
||||
if (start > 0 || end < str.length() - 1) {
|
||||
str = str.substr(start, end - start + 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lcase_equal(const SCP_string& _Left, const SCP_string& _Right)
|
||||
{
|
||||
if (_Left.size() != _Right.size())
|
||||
|
||||
@@ -91,6 +91,7 @@ extern void SCP_toupper(SCP_string &str);
|
||||
extern void SCP_totitle(SCP_string &str);
|
||||
|
||||
extern bool SCP_truncate(SCP_string &str, size_t len);
|
||||
extern bool SCP_trim(SCP_string& str);
|
||||
|
||||
extern bool lcase_equal(const SCP_string& _Left, const SCP_string& _Right);
|
||||
extern bool lcase_lessthan(const SCP_string& _Left, const SCP_string& _Right);
|
||||
|
||||
+15
-10
@@ -79,7 +79,8 @@ gr_capability_def gr_capabilities[] = {
|
||||
GR_CAPABILITY_ENTRY(SEPARATE_BLEND_FUNCTIONS),
|
||||
GR_CAPABILITY_ENTRY(PERSISTENT_BUFFER_MAPPING),
|
||||
gr_capability_def {gr_capability::CAPABILITY_BPTC, "BPTC Texture Compression"}, //This one had a different parse string already!
|
||||
GR_CAPABILITY_ENTRY(LARGE_SHADER)
|
||||
GR_CAPABILITY_ENTRY(LARGE_SHADER),
|
||||
GR_CAPABILITY_ENTRY(INSTANCED_RENDERING),
|
||||
};
|
||||
|
||||
const size_t gr_capabilities_num = sizeof(gr_capabilities) / sizeof(gr_capabilities[0]);
|
||||
@@ -177,10 +178,11 @@ static auto GammaOption __UNUSED = options::OptionBuilder<float>("Graphics.Gamma
|
||||
|
||||
static void parse_lighting_func()
|
||||
{
|
||||
int value[static_cast<int>(DefaultDetailPreset::Num_detail_presets)];
|
||||
stuff_int_list(value, static_cast<int>(DefaultDetailPreset::Num_detail_presets), RAW_INTEGER_TYPE);
|
||||
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
||||
int value[num_detail_presets];
|
||||
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(DefaultDetailPreset::Num_detail_presets); i++) {
|
||||
for (int i = 0; i < num_detail_presets; i++) {
|
||||
|
||||
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
||||
error_display(0, "%i is an invalid detail level value!", value[i]);
|
||||
@@ -1844,7 +1846,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int d_mode,
|
||||
}
|
||||
}
|
||||
|
||||
if (!gr_is_viewport_window() && !Cmdline_window_res.has_value()) {
|
||||
if (!Fred_running && !Cmdline_window_res.has_value()) {
|
||||
// For whatever reason, it seems that a combination of Win 11 and presumably NVidia GPU's causes weird artifacts.
|
||||
// These artifacts do not appear in windowed mode, or with an attached renderdoc / nvidia nsight.
|
||||
// Similarly using the -window_res command line parameter prevents this.
|
||||
@@ -1852,6 +1854,8 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int d_mode,
|
||||
// (be that a window, a render overlay from nsight, or an FSO-internal buffer) instead of directly rendering to the OS-provided direct screen backbuffer.
|
||||
// As the cost of -window_res is one single blit of a fullscreen buffer, it's probably an acceptable compromise to get rid of render artifacts.
|
||||
// As such, forcibly enable -window_res at the screen resolution here, if we're in fullscreen.
|
||||
|
||||
// Additionally, SDL3+ doesn't work when reading from the GL_FRONT buffers, so we need our own intermediate buffers.
|
||||
Cmdline_window_res.emplace(static_cast<uint16_t>(width), static_cast<uint16_t>(height));
|
||||
}
|
||||
|
||||
@@ -3070,7 +3074,7 @@ size_t hash<vertex_layout>::operator()(const vertex_layout& data) const {
|
||||
bool vertex_layout::resident_vertex_format(vertex_format_data::vertex_format format_type) const {
|
||||
return ( Vertex_mask & vertex_format_data::mask(format_type) ) ? true : false;
|
||||
}
|
||||
void vertex_layout::add_vertex_component(vertex_format_data::vertex_format format_type, size_t stride, size_t offset) {
|
||||
void vertex_layout::add_vertex_component(vertex_format_data::vertex_format format_type, size_t stride, size_t offset, size_t divisor, size_t buffer_number ) {
|
||||
// A stride value of 0 is not handled consistently by the graphics API so we must enforce that that does not happen
|
||||
Assertion(stride != 0, "The stride of a vertex component may not be zero!");
|
||||
|
||||
@@ -3079,15 +3083,16 @@ void vertex_layout::add_vertex_component(vertex_format_data::vertex_format forma
|
||||
return;
|
||||
}
|
||||
|
||||
if (Vertex_mask == 0) {
|
||||
auto stride_it = Vertex_stride.find(buffer_number);
|
||||
if (stride_it == Vertex_stride.end()) {
|
||||
// This is the first element so we need to initialize the global stride here
|
||||
Vertex_stride = stride;
|
||||
stride_it = Vertex_stride.emplace(buffer_number, stride).first;
|
||||
}
|
||||
|
||||
Assertion(Vertex_stride == stride, "The strides of all elements must be the same in a vertex layout!");
|
||||
Assertion(stride_it->second == stride, "The strides of all elements must be the same in a vertex layout!");
|
||||
|
||||
Vertex_mask |= (1 << format_type);
|
||||
Vertex_components.push_back(vertex_format_data(format_type, stride, offset));
|
||||
Vertex_components.emplace_back(format_type, stride, offset, divisor, buffer_number);
|
||||
}
|
||||
bool vertex_layout::operator==(const vertex_layout& other) const {
|
||||
if (Vertex_mask != other.Vertex_mask) {
|
||||
|
||||
+17
-10
@@ -239,6 +239,8 @@ enum shader_type {
|
||||
|
||||
#define SDR_FLAG_TONEMAPPING_LINEAR_OUT (1 << 0)
|
||||
|
||||
#define SDR_FLAG_ENV_MAP (1 << 0)
|
||||
|
||||
|
||||
enum class uniform_block_type {
|
||||
Lights = 0,
|
||||
@@ -271,14 +273,17 @@ struct vertex_format_data
|
||||
MODEL_ID,
|
||||
RADIUS,
|
||||
UVEC,
|
||||
MATRIX4,
|
||||
};
|
||||
|
||||
vertex_format format_type;
|
||||
size_t stride;
|
||||
size_t offset;
|
||||
size_t divisor;
|
||||
size_t buffer_number;
|
||||
|
||||
vertex_format_data(vertex_format i_format_type, size_t i_stride, size_t i_offset) :
|
||||
format_type(i_format_type), stride(i_stride), offset(i_offset) {}
|
||||
vertex_format_data(vertex_format i_format_type, size_t i_stride, size_t i_offset, size_t i_divisor, size_t i_buffer_number) :
|
||||
format_type(i_format_type), stride(i_stride), offset(i_offset), divisor(i_divisor), buffer_number(i_buffer_number) {}
|
||||
|
||||
static inline uint mask(vertex_format v_format) { return 1 << v_format; }
|
||||
|
||||
@@ -291,7 +296,7 @@ class vertex_layout
|
||||
SCP_vector<vertex_format_data> Vertex_components;
|
||||
|
||||
uint Vertex_mask = 0;
|
||||
size_t Vertex_stride = 0;
|
||||
SCP_unordered_map<size_t, size_t> Vertex_stride;
|
||||
public:
|
||||
vertex_layout() {}
|
||||
|
||||
@@ -301,9 +306,9 @@ public:
|
||||
|
||||
bool resident_vertex_format(vertex_format_data::vertex_format format_type) const;
|
||||
|
||||
void add_vertex_component(vertex_format_data::vertex_format format_type, size_t stride, size_t offset);
|
||||
void add_vertex_component(vertex_format_data::vertex_format format_type, size_t stride, size_t offset, size_t divisor = 0, size_t buffer_number = 0);
|
||||
|
||||
size_t get_vertex_stride() const { return Vertex_stride; }
|
||||
size_t get_vertex_stride(size_t buffer_number = 0) const { return Vertex_stride.at(buffer_number); }
|
||||
|
||||
bool operator==(const vertex_layout& other) const;
|
||||
|
||||
@@ -333,7 +338,8 @@ enum class gr_capability {
|
||||
CAPABILITY_SEPARATE_BLEND_FUNCTIONS,
|
||||
CAPABILITY_PERSISTENT_BUFFER_MAPPING,
|
||||
CAPABILITY_BPTC,
|
||||
CAPABILITY_LARGE_SHADER
|
||||
CAPABILITY_LARGE_SHADER,
|
||||
CAPABILITY_INSTANCED_RENDERING
|
||||
};
|
||||
|
||||
struct gr_capability_def {
|
||||
@@ -609,9 +615,8 @@ public:
|
||||
};
|
||||
|
||||
struct indexed_vertex_source {
|
||||
void* Vertex_list = nullptr;
|
||||
void* Index_list = nullptr;
|
||||
|
||||
std::shared_ptr<uint8_t[]> Vertex_list = nullptr;
|
||||
std::shared_ptr<uint8_t[]> Index_list = nullptr;
|
||||
gr_buffer_handle Vbuffer_handle;
|
||||
size_t Vertex_offset = 0;
|
||||
size_t Base_vertex_offset = 0;
|
||||
@@ -891,7 +896,9 @@ typedef struct screen {
|
||||
primitive_type prim_type,
|
||||
vertex_layout* layout,
|
||||
int num_elements,
|
||||
const indexed_vertex_source& buffers)>
|
||||
const indexed_vertex_source& buffers,
|
||||
const gr_buffer_handle& instance_buffer,
|
||||
int num_instances)>
|
||||
gf_render_decals;
|
||||
void (*gf_render_rocket_primitives)(interface_material* material_info,
|
||||
primitive_type prim_type,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "graphics/decal_draw_list.h"
|
||||
|
||||
#include "graphics/util/uniform_structs.h"
|
||||
#include "graphics/matrix.h"
|
||||
|
||||
#include "render/3d.h"
|
||||
@@ -9,10 +8,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// Discard any fragments where the angle to the direction to greater than 45°
|
||||
const float DECAL_ANGLE_CUTOFF = fl_radians(45.f);
|
||||
const float DECAL_ANGLE_FADE_START = fl_radians(30.f);
|
||||
|
||||
vec3d BOX_VERTS[] = {{{{ -0.5f, -0.5f, -0.5f }}},
|
||||
{{{ -0.5f, 0.5f, -0.5f }}},
|
||||
{{{ 0.5f, 0.5f, -0.5f }}},
|
||||
@@ -29,6 +24,7 @@ const size_t BOX_NUM_FACES = sizeof(BOX_FACES) / sizeof(BOX_FACES[0]);
|
||||
|
||||
gr_buffer_handle box_vertex_buffer;
|
||||
gr_buffer_handle box_index_buffer;
|
||||
gr_buffer_handle decal_instance_buffer;
|
||||
|
||||
void init_buffers() {
|
||||
box_vertex_buffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Static);
|
||||
@@ -36,6 +32,8 @@ void init_buffers() {
|
||||
|
||||
box_index_buffer = gr_create_buffer(BufferType::Index, BufferUsageHint::Static);
|
||||
gr_update_buffer_data(box_index_buffer, sizeof(BOX_FACES), BOX_FACES);
|
||||
|
||||
decal_instance_buffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Streaming);
|
||||
}
|
||||
|
||||
bool check_box_in_view(const matrix4& transform) {
|
||||
@@ -56,35 +54,6 @@ bool check_box_in_view(const matrix4& transform) {
|
||||
|
||||
namespace graphics {
|
||||
|
||||
/**
|
||||
* @brief Sorts Decals so that as many decals can be batched together as possible
|
||||
*
|
||||
* This uses the bitmaps in the definitions to determine if two decals can be rendered at the same time. Since we use
|
||||
* texture arrays we can use the base frame for batching which increases the number of draw calls that can be batched together.
|
||||
*
|
||||
* @param left
|
||||
* @param right
|
||||
* @return
|
||||
*/
|
||||
bool decal_draw_list::sort_draws(const decal_draw_info& left, const decal_draw_info& right) {
|
||||
auto left_diffuse_base = bm_get_base_frame(left.draw_mat.get_texture_map(TM_BASE_TYPE));
|
||||
auto right_diffuse_base = bm_get_base_frame(right.draw_mat.get_texture_map(TM_BASE_TYPE));
|
||||
|
||||
if (left_diffuse_base != right_diffuse_base) {
|
||||
return left_diffuse_base < right_diffuse_base;
|
||||
}
|
||||
auto left_glow_base = bm_get_base_frame(left.draw_mat.get_texture_map(TM_GLOW_TYPE));
|
||||
auto right_glow_base = bm_get_base_frame(right.draw_mat.get_texture_map(TM_GLOW_TYPE));
|
||||
|
||||
if (left_glow_base != right_glow_base) {
|
||||
return left_glow_base < right_glow_base;
|
||||
}
|
||||
|
||||
auto left_normal_base = bm_get_base_frame(left.draw_mat.get_texture_map(TM_NORMAL_TYPE));
|
||||
auto right_normal_base = bm_get_base_frame(left.draw_mat.get_texture_map(TM_NORMAL_TYPE));
|
||||
|
||||
return left_normal_base < right_normal_base;
|
||||
}
|
||||
void decal_draw_list::globalInit() {
|
||||
init_buffers();
|
||||
|
||||
@@ -96,9 +65,8 @@ void decal_draw_list::globalShutdown() {
|
||||
gr_delete_buffer(box_index_buffer);
|
||||
}
|
||||
|
||||
decal_draw_list::decal_draw_list(size_t num_decals)
|
||||
{
|
||||
_buffer = gr_get_uniform_buffer(uniform_block_type::DecalInfo, num_decals);
|
||||
void decal_draw_list::prepare_global_data() {
|
||||
_buffer = gr_get_uniform_buffer(uniform_block_type::DecalInfo, _draws.size());
|
||||
auto& aligner = _buffer.aligner();
|
||||
|
||||
// Initialize header data
|
||||
@@ -114,29 +82,34 @@ decal_draw_list::decal_draw_list(size_t num_decals)
|
||||
header->viewportSize.x = (float) gr_screen.max_w;
|
||||
header->viewportSize.y = (float) gr_screen.max_h;
|
||||
|
||||
gr_get_ambient_light(&header->ambientLight);
|
||||
for (auto& [batch_info, draw_info] : _draws) {
|
||||
auto info = aligner.addTypedElement<graphics::decal_info>();
|
||||
info->diffuse_index = batch_info.diffuse < 0 ? -1 : bm_get_array_index(batch_info.diffuse);
|
||||
info->glow_index = batch_info.glow < 0 ? -1 : bm_get_array_index(batch_info.glow);
|
||||
info->normal_index = batch_info.normal < 0 ? -1 : bm_get_array_index(batch_info.normal);
|
||||
|
||||
// Square the ambient part of the light to match the formula used in the main model shader
|
||||
header->ambientLight.xyz.x *= header->ambientLight.xyz.x;
|
||||
header->ambientLight.xyz.y *= header->ambientLight.xyz.y;
|
||||
header->ambientLight.xyz.z *= header->ambientLight.xyz.z;
|
||||
draw_info.first.uniform_offset = _buffer.getBufferOffset(aligner.getCurrentOffset());
|
||||
|
||||
header->ambientLight.xyz.x += gr_light_emission[0];
|
||||
header->ambientLight.xyz.y += gr_light_emission[1];
|
||||
header->ambientLight.xyz.z += gr_light_emission[2];
|
||||
}
|
||||
decal_draw_list::~decal_draw_list() {
|
||||
material_set_decal(&draw_info.first.material,
|
||||
bm_get_base_frame(batch_info.diffuse),
|
||||
bm_get_base_frame(batch_info.glow),
|
||||
bm_get_base_frame(batch_info.normal));
|
||||
info->diffuse_blend_mode = draw_info.first.material.get_blend_mode(0) == ALPHA_BLEND_ADDITIVE ? 1 : 0;
|
||||
info->glow_blend_mode = draw_info.first.material.get_blend_mode(2) == ALPHA_BLEND_ADDITIVE ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void decal_draw_list::render() {
|
||||
GR_DEBUG_SCOPE("Render decals");
|
||||
TRACE_SCOPE(tracing::RenderDecals);
|
||||
|
||||
_buffer.submitData();
|
||||
prepare_global_data();
|
||||
|
||||
std::sort(_draws.begin(), _draws.end(), decal_draw_list::sort_draws);
|
||||
_buffer.submitData();
|
||||
|
||||
vertex_layout layout;
|
||||
layout.add_vertex_component(vertex_format_data::POSITION3, sizeof(vec3d), 0);
|
||||
layout.add_vertex_component(vertex_format_data::MATRIX4, sizeof(matrix4), 0, 1, 1);
|
||||
|
||||
indexed_vertex_source source;
|
||||
source.Vbuffer_handle = box_vertex_buffer;
|
||||
@@ -149,14 +122,13 @@ void decal_draw_list::render() {
|
||||
_buffer.bufferHandle());
|
||||
gr_screen.gf_start_decal_pass();
|
||||
|
||||
for (auto& draw : _draws) {
|
||||
GR_DEBUG_SCOPE("Draw single decal");
|
||||
for (auto& [textures, decal_list] : _draws) {
|
||||
GR_DEBUG_SCOPE("Draw decal type");
|
||||
TRACE_SCOPE(tracing::RenderSingleDecal);
|
||||
|
||||
gr_bind_uniform_buffer(uniform_block_type::DecalInfo, draw.uniform_offset, sizeof(graphics::decal_info),
|
||||
_buffer.bufferHandle());
|
||||
|
||||
gr_screen.gf_render_decals(&draw.draw_mat, PRIM_TYPE_TRIS, &layout, BOX_NUM_FACES, source);
|
||||
gr_update_buffer_data(decal_instance_buffer, sizeof(matrix4) * decal_list.second.size(), decal_list.second.data());
|
||||
gr_bind_uniform_buffer(uniform_block_type::DecalInfo, decal_list.first.uniform_offset, sizeof(graphics::decal_info), _buffer.bufferHandle());
|
||||
gr_screen.gf_render_decals(&decal_list.first.material, PRIM_TYPE_TRIS, &layout, BOX_NUM_FACES, source, decal_instance_buffer, static_cast<int>(decal_list.second.size()));
|
||||
}
|
||||
|
||||
gr_screen.gf_stop_decal_pass();
|
||||
@@ -165,45 +137,13 @@ void decal_draw_list::add_decal(int diffuse_bitmap,
|
||||
int glow_bitmap,
|
||||
int normal_bitmap,
|
||||
float /*decal_timer*/,
|
||||
const matrix4& transform,
|
||||
float base_alpha) {
|
||||
if (!check_box_in_view(transform)) {
|
||||
const matrix4& instancedata) {
|
||||
if (!check_box_in_view(instancedata)) {
|
||||
// The decal box is not in view so we don't need to render it
|
||||
return;
|
||||
}
|
||||
|
||||
auto& aligner = _buffer.aligner();
|
||||
|
||||
auto info = aligner.addTypedElement<graphics::decal_info>();
|
||||
info->model_matrix = transform;
|
||||
// This is currently a constant but in the future this may be configurable by the decals table
|
||||
info->normal_angle_cutoff = DECAL_ANGLE_CUTOFF;
|
||||
info->angle_fade_start = DECAL_ANGLE_FADE_START;
|
||||
info->alpha_scale = base_alpha;
|
||||
|
||||
matrix transform_rot;
|
||||
vm_matrix4_get_orientation(&transform_rot, &transform);
|
||||
|
||||
// The decal shader works in view-space so the direction also has to be transformed into that space
|
||||
vm_vec_transform(&info->decal_direction, &transform_rot.vec.fvec, &gr_view_matrix, false);
|
||||
|
||||
vm_inverse_matrix4(&info->inv_model_matrix, &info->model_matrix);
|
||||
|
||||
info->diffuse_index = diffuse_bitmap < 0 ? -1 : bm_get_array_index(diffuse_bitmap);
|
||||
info->glow_index = glow_bitmap < 0 ? -1 : bm_get_array_index(glow_bitmap);
|
||||
info->normal_index = normal_bitmap < 0 ? -1 : bm_get_array_index(normal_bitmap);
|
||||
|
||||
decal_draw_info current_draw;
|
||||
current_draw.uniform_offset = _buffer.getBufferOffset(aligner.getCurrentOffset());
|
||||
|
||||
material_set_decal(¤t_draw.draw_mat,
|
||||
bm_get_base_frame(diffuse_bitmap),
|
||||
bm_get_base_frame(glow_bitmap),
|
||||
bm_get_base_frame(normal_bitmap));
|
||||
info->diffuse_blend_mode = current_draw.draw_mat.get_blend_mode(0) == ALPHA_BLEND_ADDITIVE ? 1 : 0;
|
||||
info->glow_blend_mode = current_draw.draw_mat.get_blend_mode(2) == ALPHA_BLEND_ADDITIVE ? 1 : 0;
|
||||
|
||||
_draws.push_back(current_draw);
|
||||
_draws[decal_batch_info{diffuse_bitmap, glow_bitmap, normal_bitmap}].second.emplace_back(instancedata);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,36 +3,41 @@
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include "graphics/material.h"
|
||||
#include "graphics/util/uniform_structs.h"
|
||||
#include "graphics/util/UniformBuffer.h"
|
||||
|
||||
namespace graphics {
|
||||
|
||||
class decal_draw_list {
|
||||
struct decal_draw_info {
|
||||
decal_material draw_mat;
|
||||
|
||||
decal_material material;
|
||||
size_t uniform_offset;
|
||||
};
|
||||
SCP_vector<decal_draw_info> _draws;
|
||||
struct decal_batch_info {
|
||||
int diffuse, glow, normal;
|
||||
bool operator< (const decal_batch_info& r) const {
|
||||
return std::tie(diffuse, glow, normal) < std::tie(r.diffuse, r.glow, r.normal);
|
||||
}
|
||||
};
|
||||
|
||||
SCP_map<decal_batch_info, std::pair<decal_draw_info, SCP_vector<matrix4>>> _draws;
|
||||
|
||||
util::UniformBuffer _buffer;
|
||||
|
||||
static bool sort_draws(const decal_draw_info& left, const decal_draw_info& right);
|
||||
|
||||
public:
|
||||
explicit decal_draw_list(size_t num_decals);
|
||||
~decal_draw_list();
|
||||
|
||||
decal_draw_list(const decal_draw_list&) = delete;
|
||||
decal_draw_list& operator=(const decal_draw_list&) = delete;
|
||||
|
||||
void add_decal(int diffuse_bitmap, int glow_bitmap, int normal_bitmap, float decal_timer, const matrix4& transform,
|
||||
float base_alpha);
|
||||
void add_decal(int diffuse_bitmap, int glow_bitmap, int normal_bitmap, float decal_timer, const matrix4& instancedata);
|
||||
|
||||
void render();
|
||||
|
||||
static void globalInit();
|
||||
|
||||
static void globalShutdown();
|
||||
|
||||
decal_draw_list() = default;
|
||||
|
||||
private:
|
||||
void prepare_global_data();
|
||||
};
|
||||
}
|
||||
@@ -307,6 +307,24 @@ void gr_stub_shadow_map_end()
|
||||
{
|
||||
}
|
||||
|
||||
void gr_stub_start_decal_pass()
|
||||
{
|
||||
}
|
||||
|
||||
void gr_stub_stop_decal_pass()
|
||||
{
|
||||
}
|
||||
|
||||
void gr_stub_render_decals(decal_material* /*material_info*/,
|
||||
primitive_type /*prim_type*/,
|
||||
vertex_layout* /*layout*/,
|
||||
int /*num_elements*/,
|
||||
const indexed_vertex_source& /*buffers*/,
|
||||
const gr_buffer_handle& /*instance_buffer*/,
|
||||
int /*num_instances*/)
|
||||
{
|
||||
}
|
||||
|
||||
void gr_stub_render_shield_impact(shield_material* /*material_info*/,
|
||||
primitive_type /*prim_type*/,
|
||||
vertex_layout* /*layout*/,
|
||||
@@ -569,6 +587,10 @@ void gr_stub_init_function_pointers() {
|
||||
gr_screen.gf_shadow_map_start = gr_stub_shadow_map_start;
|
||||
gr_screen.gf_shadow_map_end = gr_stub_shadow_map_end;
|
||||
|
||||
gr_screen.gf_start_decal_pass = gr_stub_start_decal_pass;
|
||||
gr_screen.gf_stop_decal_pass = gr_stub_stop_decal_pass;
|
||||
gr_screen.gf_render_decals = gr_stub_render_decals;
|
||||
|
||||
gr_screen.gf_render_shield_impact = gr_stub_render_shield_impact;
|
||||
|
||||
gr_screen.gf_maybe_create_shader = gr_stub_maybe_create_shader;
|
||||
|
||||
@@ -344,6 +344,16 @@ void gr_get_ambient_light(vec3d* light_vector) {
|
||||
light_vector->xyz.x = over.handle(abv.handle(gr_light_ambient[0]));
|
||||
light_vector->xyz.y = over.handle(abv.handle(gr_light_ambient[1]));
|
||||
light_vector->xyz.z = over.handle(abv.handle(gr_light_ambient[2]));
|
||||
|
||||
//AmbientFactor^2 due to legacy OpenGL behaviour
|
||||
*light_vector *= *light_vector;
|
||||
|
||||
//For some reason, emissive is in here as well...
|
||||
if (Cmdline_emissive) {
|
||||
light_vector->xyz.x += gr_light_emission[0];
|
||||
light_vector->xyz.y += gr_light_emission[1];
|
||||
light_vector->xyz.z += gr_light_emission[2];
|
||||
}
|
||||
}
|
||||
|
||||
void gr_lighting_fill_uniforms(void* data_out, size_t buffer_size) {
|
||||
|
||||
@@ -768,21 +768,19 @@ int model_material::get_shader_runtime_flags() const {
|
||||
flags |= MODEL_SDR_FLAG_DEFERRED;
|
||||
if (is_hdr())
|
||||
flags |= MODEL_SDR_FLAG_HDR;
|
||||
if (get_texture_map(TM_BASE_TYPE) > 0)
|
||||
if (get_texture_map(TM_BASE_TYPE) >= 0)
|
||||
flags |= MODEL_SDR_FLAG_DIFFUSE;
|
||||
if (get_texture_map(TM_GLOW_TYPE) > 0)
|
||||
if (get_texture_map(TM_GLOW_TYPE) >= 0)
|
||||
flags |= MODEL_SDR_FLAG_GLOW;
|
||||
if (get_texture_map(TM_SPECULAR_TYPE) > 0 || get_texture_map(TM_SPEC_GLOSS_TYPE) > 0)
|
||||
if (get_texture_map(TM_SPECULAR_TYPE) >= 0 || get_texture_map(TM_SPEC_GLOSS_TYPE) >= 0)
|
||||
flags |= MODEL_SDR_FLAG_SPEC;
|
||||
if (ENVMAP > 0)
|
||||
flags |= MODEL_SDR_FLAG_ENV;
|
||||
if (get_texture_map(TM_NORMAL_TYPE) > 0)
|
||||
if (get_texture_map(TM_NORMAL_TYPE) >= 0)
|
||||
flags |= MODEL_SDR_FLAG_NORMAL;
|
||||
if (get_texture_map(TM_AMBIENT_TYPE) > 0)
|
||||
if (get_texture_map(TM_AMBIENT_TYPE) >= 0)
|
||||
flags |= MODEL_SDR_FLAG_AMBIENT;
|
||||
if (get_texture_map(TM_MISC_TYPE) > 0)
|
||||
if (get_texture_map(TM_MISC_TYPE) >= 0)
|
||||
flags |= MODEL_SDR_FLAG_MISC;
|
||||
if (get_texture_map(TM_MISC_TYPE) > 0 && is_team_color_set())
|
||||
if (get_texture_map(TM_MISC_TYPE) >= 0 && is_team_color_set())
|
||||
flags |= MODEL_SDR_FLAG_TEAMCOLOR;
|
||||
if (is_fogged())
|
||||
flags |= MODEL_SDR_FLAG_FOG;
|
||||
|
||||
@@ -183,18 +183,16 @@ void opengl::ShaderProgram::linkProgram() {
|
||||
freeCompiledShaders();
|
||||
}
|
||||
|
||||
void opengl::ShaderProgram::initAttribute(const SCP_string& name, opengl_vert_attrib::attrib_id attr_id, const vec4& default_value)
|
||||
void opengl::ShaderProgram::initAttribute(const SCP_string& name, const vec4& default_value)
|
||||
{
|
||||
auto attrib_loc = glGetAttribLocation(_program_id, name.c_str());
|
||||
|
||||
if (attrib_loc == -1)
|
||||
{
|
||||
// Not available, ignore
|
||||
// Not available or optimized out, ignore
|
||||
return;
|
||||
}
|
||||
|
||||
_attribute_locations.insert(std::make_pair(attr_id, attrib_loc));
|
||||
|
||||
// The shader needs to be in use before glVertexAttrib can be used
|
||||
use();
|
||||
glVertexAttrib4f(
|
||||
@@ -205,14 +203,6 @@ void opengl::ShaderProgram::initAttribute(const SCP_string& name, opengl_vert_at
|
||||
default_value.xyzw.w
|
||||
);
|
||||
}
|
||||
GLint opengl::ShaderProgram::getAttributeLocation(opengl_vert_attrib::attrib_id attribute) {
|
||||
auto iter = _attribute_locations.find(attribute);
|
||||
if (iter == _attribute_locations.end()) {
|
||||
return -1;
|
||||
} else {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
opengl::ShaderUniforms::ShaderUniforms(ShaderProgram* shaderProgram) : _program(shaderProgram) {
|
||||
Assertion(shaderProgram != nullptr, "Shader program may not be null!");
|
||||
|
||||
@@ -46,8 +46,6 @@ class ShaderProgram {
|
||||
|
||||
SCP_vector<GLuint> _compiled_shaders;
|
||||
|
||||
SCP_unordered_map<opengl_vert_attrib::attrib_id, GLint> _attribute_locations;
|
||||
|
||||
void freeCompiledShaders();
|
||||
public:
|
||||
explicit ShaderProgram(const SCP_string& program_name);
|
||||
@@ -67,9 +65,7 @@ class ShaderProgram {
|
||||
|
||||
void linkProgram();
|
||||
|
||||
void initAttribute(const SCP_string& name, opengl_vert_attrib::attrib_id attr_id, const vec4& default_value);
|
||||
|
||||
GLint getAttributeLocation(opengl_vert_attrib::attrib_id attribute);
|
||||
void initAttribute(const SCP_string& name, const vec4& default_value);
|
||||
|
||||
GLuint getShaderHandle();
|
||||
};
|
||||
|
||||
@@ -286,12 +286,19 @@ void gr_opengl_print_screen(const char *filename)
|
||||
//Reading from the front buffer here seems to no longer work correctly; that just reads back all zeros
|
||||
glReadBuffer(Cmdline_window_res ? GL_COLOR_ATTACHMENT0 : GL_FRONT);
|
||||
|
||||
// Clamp float values to [0,1] when reading from the GL_RGBA16F back framebuffer.
|
||||
// The default GL_FIXED_ONLY only clamps fixed-point FBOs, leaving float FBO reads
|
||||
// with out-of-range values (HDR > 1.0) producing undefined behavior when converted
|
||||
// to integer types, which manifests as rainbow artifacts on bright areas.
|
||||
glClampColor(GL_CLAMP_READ_COLOR, GL_TRUE);
|
||||
|
||||
// now for the data
|
||||
if (Use_PBOs) {
|
||||
Assert( !pbo );
|
||||
glGenBuffers(1, &pbo);
|
||||
|
||||
if ( !pbo ) {
|
||||
glClampColor(GL_CLAMP_READ_COLOR, GL_FIXED_ONLY);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -306,6 +313,7 @@ void gr_opengl_print_screen(const char *filename)
|
||||
pixels = (GLubyte*) vm_malloc(gr_screen.max_w * gr_screen.max_h * 4, memory::quiet_alloc);
|
||||
|
||||
if (pixels == NULL) {
|
||||
glClampColor(GL_CLAMP_READ_COLOR, GL_FIXED_ONLY);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -313,10 +321,20 @@ void gr_opengl_print_screen(const char *filename)
|
||||
glFlush();
|
||||
}
|
||||
|
||||
glClampColor(GL_CLAMP_READ_COLOR, GL_FIXED_ONLY);
|
||||
|
||||
// Force alpha to fully opaque so screenshots are not saved with transparency.
|
||||
// The framebuffer alpha can be < 255 due to blending operations affecting the alpha
|
||||
// channel as a side effect, even though the rendered image itself is opaque.
|
||||
int num_pixels = gr_screen.max_w * gr_screen.max_h;
|
||||
for (int i = 0; i < num_pixels; i++) {
|
||||
pixels[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
if (!png_write_bitmap(os_get_config_path(tmp).c_str(), gr_screen.max_w, gr_screen.max_h, true, pixels)) {
|
||||
ReleaseWarning(LOCATION, "Failed to write screenshot to \"%s\".", os_get_config_path(tmp).c_str());
|
||||
}
|
||||
|
||||
|
||||
if (pbo) {
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
pixels = NULL;
|
||||
@@ -337,10 +355,21 @@ SCP_string gr_opengl_blob_screen()
|
||||
GLuint pbo = 0;
|
||||
|
||||
GL_state.PushFramebufferState();
|
||||
GL_state.BindFrameBuffer(Cmdline_window_res ? Back_framebuffer : 0, GL_FRAMEBUFFER);
|
||||
|
||||
GLuint source_fbo = 0;
|
||||
|
||||
GLuint render_target = opengl_get_rtt_framebuffer();
|
||||
if (render_target != 0) {
|
||||
source_fbo = render_target;
|
||||
}
|
||||
else if (Cmdline_window_res) {
|
||||
source_fbo = Back_framebuffer;
|
||||
}
|
||||
|
||||
GL_state.BindFrameBuffer(source_fbo, GL_FRAMEBUFFER);
|
||||
|
||||
//Reading from the front buffer here seems to no longer work correctly; that just reads back all zeros
|
||||
glReadBuffer(Cmdline_window_res ? GL_COLOR_ATTACHMENT0 : GL_FRONT);
|
||||
glReadBuffer(source_fbo != 0 ? GL_COLOR_ATTACHMENT0 : GL_FRONT);
|
||||
|
||||
// now for the data
|
||||
if (Use_PBOs) {
|
||||
@@ -1489,8 +1518,11 @@ bool gr_opengl_is_capable(gr_capability capability)
|
||||
return GLAD_GL_ARB_texture_compression_bptc != 0;
|
||||
case gr_capability::CAPABILITY_LARGE_SHADER:
|
||||
return !Cmdline_no_large_shaders;
|
||||
case gr_capability::CAPABILITY_INSTANCED_RENDERING:
|
||||
return GLAD_GL_ARB_vertex_attrib_binding;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "gropengltnl.h"
|
||||
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/light.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "graphics/util/UniformAligner.h"
|
||||
#include "graphics/util/UniformBuffer.h"
|
||||
@@ -189,16 +190,19 @@ static bool override_fog = false;
|
||||
graphics::deferred_light_data*
|
||||
|
||||
// common conversion operations to translate a game light data structure into a render-ready light uniform.
|
||||
prepare_light_uniforms(light& l, graphics::util::UniformAligner& uniformAligner)
|
||||
prepare_light_uniforms(light& l, graphics::util::UniformAligner& uniformAligner, const ltp::profile* lp)
|
||||
{
|
||||
graphics::deferred_light_data* light_data = uniformAligner.addTypedElement<graphics::deferred_light_data>();
|
||||
|
||||
light_data->lightType = static_cast<int>(l.type);
|
||||
|
||||
float intensity =
|
||||
(Lighting_mode == lighting_mode::COCKPIT) ? lp->cockpit_light_intensity_modifier.handle(l.intensity) : l.intensity;
|
||||
|
||||
vec3d diffuse;
|
||||
diffuse.xyz.x = l.r * l.intensity;
|
||||
diffuse.xyz.y = l.g * l.intensity;
|
||||
diffuse.xyz.z = l.b * l.intensity;
|
||||
diffuse.xyz.x = l.r * intensity;
|
||||
diffuse.xyz.y = l.g * intensity;
|
||||
diffuse.xyz.z = l.b * intensity;
|
||||
|
||||
light_data->diffuseLightColor = diffuse;
|
||||
|
||||
@@ -223,7 +227,7 @@ void gr_opengl_deferred_lighting_finish()
|
||||
// GL_state.DepthFunc(GL_GREATER);
|
||||
// GL_state.DepthMask(GL_FALSE);
|
||||
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_LIGHTING, 0));
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_LIGHTING, ENVMAP > 0 ? SDR_FLAG_ENV_MAP : 0));
|
||||
|
||||
// Render on top of the composite buffer texture
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT5);
|
||||
@@ -247,6 +251,16 @@ void gr_opengl_deferred_lighting_finish()
|
||||
GL_state.Texture.Enable(4, GL_TEXTURE_2D_ARRAY, Shadow_map_texture);
|
||||
}
|
||||
|
||||
if (ENVMAP > 0) {
|
||||
Current_shader->program->Uniforms.setTextureUniform("sEnvmap", 5);
|
||||
Current_shader->program->Uniforms.setTextureUniform("sIrrmap", 6);
|
||||
float u_scale, v_scale;
|
||||
uint32_t array_index;
|
||||
gr_opengl_tcache_set(ENVMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, 5);
|
||||
gr_opengl_tcache_set(IRRMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, 6);
|
||||
Assertion(array_index == 0, "Cube map arrays are not supported yet!");
|
||||
}
|
||||
|
||||
// We need to use stable sorting here to make sure that the relative ordering of the same light types is the same as
|
||||
// the rest of the code. Otherwise the shadow mapping would be applied while rendering the wrong light which would
|
||||
// lead to flickering lights in some circumstances
|
||||
@@ -254,7 +268,7 @@ void gr_opengl_deferred_lighting_finish()
|
||||
using namespace graphics;
|
||||
|
||||
// We need to precompute how many elements we are going to need
|
||||
size_t num_data_elements = Lights.size();
|
||||
size_t num_data_elements = Lights.size() + 1;
|
||||
|
||||
// Get a uniform buffer for our data
|
||||
auto light_buffer = gr_get_uniform_buffer(uniform_block_type::Lights, num_data_elements);
|
||||
@@ -286,6 +300,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
case Light_Type::Tube:
|
||||
cylinder_lights.push_back(l);
|
||||
break;
|
||||
case Light_Type::Ambient:
|
||||
UNREACHABLE("Multiple ambient lights are not supported!");
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -312,38 +328,54 @@ void gr_opengl_deferred_lighting_finish()
|
||||
header->invScreenHeight = 1.0f / gr_screen.max_h;
|
||||
header->nearPlane = gr_near_plane;
|
||||
|
||||
{
|
||||
//Prepare ambient light
|
||||
light& l = full_frame_lights.emplace_back();
|
||||
vec3d ambient;
|
||||
gr_get_ambient_light(&ambient);
|
||||
l.r = ambient.xyz.x;
|
||||
l.g = ambient.xyz.y;
|
||||
l.b = ambient.xyz.z;
|
||||
l.type = Light_Type::Ambient;
|
||||
l.intensity = 1.f;
|
||||
l.source_radius = 0.f;
|
||||
}
|
||||
|
||||
// Only the first directional light uses shaders so we need to know when we already saw that light
|
||||
bool first_directional = true;
|
||||
|
||||
for (auto& l : full_frame_lights) {
|
||||
auto light_data = prepare_light_uniforms(l, light_uniform_aligner);
|
||||
if (Shadow_quality != ShadowQuality::Disabled) {
|
||||
light_data->enable_shadows = first_directional ? 1 : 0;
|
||||
auto light_data = prepare_light_uniforms(l, light_uniform_aligner, lp);
|
||||
|
||||
if (l.type == Light_Type::Directional ) {
|
||||
if (Shadow_quality != ShadowQuality::Disabled) {
|
||||
light_data->enable_shadows = first_directional ? 1 : 0;
|
||||
}
|
||||
|
||||
// Global light direction should match shadow light direction
|
||||
if (first_directional) {
|
||||
global_light = &l;
|
||||
global_light_diffuse = light_data->diffuseLightColor;
|
||||
|
||||
first_directional = false;
|
||||
}
|
||||
|
||||
vec4 light_dir;
|
||||
light_dir.xyzw.x = -l.vec.xyz.x;
|
||||
light_dir.xyzw.y = -l.vec.xyz.y;
|
||||
light_dir.xyzw.z = -l.vec.xyz.z;
|
||||
light_dir.xyzw.w = 0.0f;
|
||||
vec4 view_dir;
|
||||
|
||||
vm_vec_transform(&view_dir, &light_dir, &gr_view_matrix);
|
||||
|
||||
light_data->lightDir.xyz.x = view_dir.xyzw.x;
|
||||
light_data->lightDir.xyz.y = view_dir.xyzw.y;
|
||||
light_data->lightDir.xyz.z = view_dir.xyzw.z;
|
||||
}
|
||||
|
||||
// Global light direction should match shadow light direction
|
||||
if (first_directional) {
|
||||
global_light = &l;
|
||||
global_light_diffuse = light_data->diffuseLightColor;
|
||||
}
|
||||
|
||||
vec4 light_dir;
|
||||
light_dir.xyzw.x = -l.vec.xyz.x;
|
||||
light_dir.xyzw.y = -l.vec.xyz.y;
|
||||
light_dir.xyzw.z = -l.vec.xyz.z;
|
||||
light_dir.xyzw.w = 0.0f;
|
||||
vec4 view_dir;
|
||||
|
||||
vm_vec_transform(&view_dir, &light_dir, &gr_view_matrix);
|
||||
|
||||
light_data->lightDir.xyz.x = view_dir.xyzw.x;
|
||||
light_data->lightDir.xyz.y = view_dir.xyzw.y;
|
||||
light_data->lightDir.xyz.z = view_dir.xyzw.z;
|
||||
|
||||
first_directional = false;
|
||||
}
|
||||
for (auto& l : sphere_lights) {
|
||||
auto light_data = prepare_light_uniforms(l, light_uniform_aligner);
|
||||
auto light_data = prepare_light_uniforms(l, light_uniform_aligner, lp);
|
||||
|
||||
if (l.type == Light_Type::Cone) {
|
||||
light_data->dualCone = (l.flags & LF_DUAL_CONE) ? 1.0f : 0.0f;
|
||||
@@ -355,6 +387,7 @@ void gr_opengl_deferred_lighting_finish()
|
||||
? lp->cockpit_light_radius_modifier.handle(MAX(l.rada, l.radb))
|
||||
: MAX(l.rada, l.radb);
|
||||
light_data->lightRadius = rad;
|
||||
|
||||
// A small padding factor is added to guard against potentially clipping the edges of the light with facets
|
||||
// of the volume mesh.
|
||||
light_data->scale.xyz.x = rad * 1.05f;
|
||||
@@ -362,11 +395,12 @@ void gr_opengl_deferred_lighting_finish()
|
||||
light_data->scale.xyz.z = rad * 1.05f;
|
||||
}
|
||||
for (auto& l : cylinder_lights) {
|
||||
auto light_data = prepare_light_uniforms(l, light_uniform_aligner);
|
||||
auto light_data = prepare_light_uniforms(l, light_uniform_aligner, lp);
|
||||
float rad =
|
||||
(Lighting_mode == lighting_mode::COCKPIT) ? lp->cockpit_light_radius_modifier.handle(l.radb) : l.radb;
|
||||
|
||||
light_data->lightRadius = rad;
|
||||
|
||||
light_data->lightType = LT_TUBE;
|
||||
|
||||
vec3d a;
|
||||
@@ -389,7 +423,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
{
|
||||
for (size_t i = 0; i<full_frame_lights.size(); i++) {
|
||||
// just keeping things aligned really.
|
||||
matrix_uniform_aligner.addTypedElement<graphics::matrix_uniforms>();
|
||||
auto matrix_data = matrix_uniform_aligner.addTypedElement<graphics::matrix_uniforms>();
|
||||
matrix_data->modelViewMatrix = gr_env_texture_matrix;
|
||||
}
|
||||
for (auto& l : sphere_lights) {
|
||||
auto matrix_data = matrix_uniform_aligner.addTypedElement<graphics::matrix_uniforms>();
|
||||
@@ -494,7 +529,10 @@ void gr_opengl_deferred_lighting_finish()
|
||||
// Now reset back to drawing into the color buffer
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb2_render_mode != NEB2_RENDER_NONE && !override_fog) {
|
||||
bool bDrawFullNeb = The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb2_render_mode != NEB2_RENDER_NONE && !override_fog;
|
||||
bool bDrawNebVolumetrics = The_mission.volumetrics && The_mission.volumetrics->get_enabled() && !override_fog;
|
||||
|
||||
if (bDrawFullNeb) {
|
||||
GL_state.SetAlphaBlendMode(ALPHA_BLEND_NONE);
|
||||
gr_zbuffer_set(GR_ZBUFF_NONE);
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_SCENE_FOG, 0));
|
||||
@@ -521,8 +559,26 @@ void gr_opengl_deferred_lighting_finish()
|
||||
});
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
}
|
||||
else if (The_mission.volumetrics && The_mission.volumetrics->get_enabled() && !override_fog) {
|
||||
|
||||
if (bDrawNebVolumetrics) {
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT5);
|
||||
glBlitFramebuffer(0,
|
||||
0,
|
||||
gr_screen.max_w,
|
||||
gr_screen.max_h,
|
||||
0,
|
||||
0,
|
||||
gr_screen.max_w,
|
||||
gr_screen.max_h,
|
||||
GL_COLOR_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
}
|
||||
|
||||
}
|
||||
if (bDrawNebVolumetrics) {
|
||||
GR_DEBUG_SCOPE("Volumetric Nebulae");
|
||||
TRACE_SCOPE(tracing::Volumetrics);
|
||||
|
||||
@@ -602,7 +658,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
gr_end_view_matrix();
|
||||
gr_end_proj_matrix();
|
||||
}
|
||||
else {
|
||||
|
||||
if(!bDrawFullNeb && !bDrawNebVolumetrics) {
|
||||
// Transfer the resolved lighting back to the color texture
|
||||
// TODO: Maybe this could be improved so that it doesn't require the copy back operation?
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT5);
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include "graphics/util/UniformAligner.h"
|
||||
#include "graphics/util/uniform_structs.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
namespace ltp = lighting_profiles;
|
||||
using namespace ltp;
|
||||
|
||||
void gr_opengl_deferred_init();
|
||||
|
||||
@@ -12,7 +15,7 @@ void gr_opengl_deferred_lighting_begin(bool clearNonColorBufs = false);
|
||||
void gr_opengl_deferred_lighting_msaa();
|
||||
void gr_opengl_deferred_lighting_end();
|
||||
void gr_opengl_deferred_lighting_finish();
|
||||
graphics::deferred_light_data* prepare_light_uniforms(light& l, graphics::util::UniformAligner& uniformAligner);
|
||||
graphics::deferred_light_data* prepare_light_uniforms(light& l, graphics::util::UniformAligner& uniformAligner, ltp::profile lp);
|
||||
|
||||
void gr_opengl_deferred_light_sphere_init(int rings, int segments);
|
||||
void gr_opengl_deferred_light_cylinder_init(int segments);
|
||||
|
||||
@@ -52,6 +52,7 @@ GLuint Scene_stencil_buffer;
|
||||
GLuint Back_framebuffer;
|
||||
GLuint Back_texture;
|
||||
GLuint Back_depth_texture;
|
||||
GLuint Back_stencil_buffer;
|
||||
|
||||
GLuint Distortion_framebuffer = 0;
|
||||
GLuint Distortion_texture[2];
|
||||
@@ -551,6 +552,11 @@ void opengl_setup_scene_textures()
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, Back_depth_texture, 0);
|
||||
gr_zbuffer_set(GR_ZBUFF_FULL);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glGenRenderbuffers(1, &Back_stencil_buffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, Back_stencil_buffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, gr_screen.max_w, gr_screen.max_h);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, Back_stencil_buffer);
|
||||
}
|
||||
|
||||
//Setup thruster distortion framebuffer
|
||||
@@ -1147,14 +1153,16 @@ void gr_opengl_render_decals(decal_material* material_info,
|
||||
primitive_type prim_type,
|
||||
vertex_layout* layout,
|
||||
int num_elements,
|
||||
const indexed_vertex_source& binding) {
|
||||
const indexed_vertex_source& binding,
|
||||
const gr_buffer_handle& instance_buffer,
|
||||
int num_instances) {
|
||||
opengl_tnl_set_material_decal(material_info);
|
||||
|
||||
opengl_bind_vertex_layout(*layout,
|
||||
opengl_buffer_get_id(GL_ARRAY_BUFFER, binding.Vbuffer_handle),
|
||||
opengl_bind_vertex_layout_multiple(*layout,
|
||||
SCP_vector<GLuint> { opengl_buffer_get_id(GL_ARRAY_BUFFER, binding.Vbuffer_handle), opengl_buffer_get_id(GL_ARRAY_BUFFER, instance_buffer) },
|
||||
opengl_buffer_get_id(GL_ELEMENT_ARRAY_BUFFER, binding.Ibuffer_handle));
|
||||
|
||||
glDrawElements(opengl_primitive_type(prim_type), num_elements, GL_UNSIGNED_INT, nullptr);
|
||||
glDrawElementsInstanced(opengl_primitive_type(prim_type), num_elements, GL_UNSIGNED_INT, nullptr, num_instances);
|
||||
}
|
||||
|
||||
void gr_opengl_start_decal_pass() {
|
||||
|
||||
@@ -112,7 +112,9 @@ void gr_opengl_render_decals(decal_material* material_info,
|
||||
primitive_type prim_type,
|
||||
vertex_layout* layout,
|
||||
int num_elements,
|
||||
const indexed_vertex_source& binding);
|
||||
const indexed_vertex_source& binding,
|
||||
const gr_buffer_handle& instance_buffer,
|
||||
int num_instances);
|
||||
void gr_opengl_render_rocket_primitives(interface_material* material_info,
|
||||
primitive_type prim_type,
|
||||
vertex_layout* layout,
|
||||
|
||||
@@ -597,6 +597,18 @@ void gr_opengl_post_process_end()
|
||||
case graphics::PostEffectUniformType::Tint:
|
||||
data->tint = postEffects[idx].rgb;
|
||||
break;
|
||||
case graphics::PostEffectUniformType::CustomEffectVEC3A:
|
||||
data->custom_effect_vec3_a = postEffects[idx].rgb;
|
||||
break;
|
||||
case graphics::PostEffectUniformType::CustomEffectFloatA:
|
||||
data->custom_effect_float_a = value;
|
||||
break;
|
||||
case graphics::PostEffectUniformType::CustomEffectVEC3B:
|
||||
data->custom_effect_vec3_b = postEffects[idx].rgb;
|
||||
break;
|
||||
case graphics::PostEffectUniformType::CustomEffectFloatB:
|
||||
data->custom_effect_float_b = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ SCP_vector<opengl_vert_attrib> GL_vertex_attrib_info =
|
||||
{ opengl_vert_attrib::MODEL_ID, "vertModelID", {{{ 0.0f, 0.0f, 0.0f, 0.0f }}} },
|
||||
{ opengl_vert_attrib::RADIUS, "vertRadius", {{{ 1.0f, 0.0f, 0.0f, 0.0f }}} },
|
||||
{ opengl_vert_attrib::UVEC, "vertUvec", {{{ 0.0f, 1.0f, 0.0f, 0.0f }}} },
|
||||
{ opengl_vert_attrib::WORLD_MATRIX, "vertWorldMatrix", {{{ 1.0f, 0.0f, 0.0f, 0.0f }}} },
|
||||
{ opengl_vert_attrib::MODEL_MATRIX, "vertModelMatrix", {{{ 1.0f, 0.0f, 0.0f, 0.0f }}} },
|
||||
};
|
||||
|
||||
struct opengl_uniform_block_binding {
|
||||
@@ -141,7 +141,7 @@ static opengl_shader_type_t GL_shader_types[] = {
|
||||
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::TEXCOORD }, "NanoVG shader", false },
|
||||
|
||||
{ SDR_TYPE_DECAL, "decal-v.sdr", "decal-f.sdr", nullptr,
|
||||
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::WORLD_MATRIX }, "Decal rendering", false },
|
||||
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::MODEL_MATRIX }, "Decal rendering", false },
|
||||
|
||||
{ SDR_TYPE_SCENE_FOG, "post-v.sdr", "fog-f.sdr", nullptr,
|
||||
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::TEXCOORD }, "Scene fogging", false },
|
||||
@@ -190,6 +190,8 @@ static opengl_shader_variant_t GL_shader_variants[] = {
|
||||
|
||||
{SDR_TYPE_EFFECT_PARTICLE, true, SDR_FLAG_PARTICLE_POINT_GEN, "FLAG_EFFECT_GEOMETRY", {opengl_vert_attrib::UVEC}, "Geometry shader point-based particles"},
|
||||
|
||||
{SDR_TYPE_DEFERRED_LIGHTING, false, SDR_FLAG_ENV_MAP, "ENV_MAP", {}, "Render ambient light with env and irrmaps"},
|
||||
|
||||
{SDR_TYPE_POST_PROCESS_BLUR, false, SDR_FLAG_BLUR_HORIZONTAL, "PASS_0", {}, "Horizontal blur pass"},
|
||||
|
||||
{SDR_TYPE_POST_PROCESS_BLUR, false, SDR_FLAG_BLUR_VERTICAL, "PASS_1", {}, "Vertical blur pass"},
|
||||
@@ -884,7 +886,7 @@ void opengl_compile_shader_actual(shader_type sdr, const uint &flags, opengl_sha
|
||||
|
||||
// initialize the attributes
|
||||
for (auto& attr : sdr_info->attributes) {
|
||||
new_shader.program->initAttribute(GL_vertex_attrib_info[attr].name, GL_vertex_attrib_info[attr].attribute_id, GL_vertex_attrib_info[attr].default_value);
|
||||
new_shader.program->initAttribute(GL_vertex_attrib_info[attr].name, GL_vertex_attrib_info[attr].default_value);
|
||||
}
|
||||
|
||||
for (auto& uniform_block : GL_uniform_blocks) {
|
||||
@@ -904,7 +906,7 @@ void opengl_compile_shader_actual(shader_type sdr, const uint &flags, opengl_sha
|
||||
if (sdr_info->type_id == variant.type_id && variant.flag & flags) {
|
||||
for (auto& attr : variant.attributes) {
|
||||
auto& attr_info = GL_vertex_attrib_info[attr];
|
||||
new_shader.program->initAttribute(attr_info.name, attr_info.attribute_id, attr_info.default_value);
|
||||
new_shader.program->initAttribute(attr_info.name, attr_info.default_value);
|
||||
}
|
||||
|
||||
nprintf(("shaders"," %s\n", variant.description));
|
||||
@@ -1076,19 +1078,6 @@ void opengl_shader_init()
|
||||
nprintf(("shaders","\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal OpenGL location for a given attribute. Requires that the Current_shader global variable is valid
|
||||
*
|
||||
* @param attribute_text Name of the attribute
|
||||
* @return Internal OpenGL location for the attribute
|
||||
*/
|
||||
GLint opengl_shader_get_attribute(opengl_vert_attrib::attrib_id attribute)
|
||||
{
|
||||
Assertion(Current_shader != nullptr, "Current shader may not be null!");
|
||||
|
||||
return Current_shader->program->getAttributeLocation(attribute);
|
||||
}
|
||||
|
||||
void opengl_shader_set_passthrough(bool textured, bool hdr)
|
||||
{
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_PASSTHROUGH_RENDER, 0));
|
||||
|
||||
@@ -35,7 +35,7 @@ struct opengl_vert_attrib {
|
||||
MODEL_ID,
|
||||
RADIUS,
|
||||
UVEC,
|
||||
WORLD_MATRIX,
|
||||
MODEL_MATRIX,
|
||||
NUM_ATTRIBS,
|
||||
};
|
||||
|
||||
@@ -151,8 +151,6 @@ void opengl_shader_shutdown();
|
||||
|
||||
int opengl_compile_shader(shader_type sdr, uint flags);
|
||||
|
||||
GLint opengl_shader_get_attribute(opengl_vert_attrib::attrib_id attribute);
|
||||
|
||||
void opengl_shader_set_passthrough(bool textured, bool hdr);
|
||||
|
||||
void opengl_shader_set_default_material(bool textured, bool alpha, vec4* clr, float color_scale, uint32_t array_index, const material::clip_plane& clip_plane);
|
||||
|
||||
@@ -673,8 +673,10 @@ static int opengl_texture_set_level(int bitmap_handle, int bitmap_type, int bmap
|
||||
auto mipmap_w = tex_w;
|
||||
auto mipmap_h = tex_h;
|
||||
|
||||
// should never have mipmap levels if we also have to manually resize
|
||||
if ((mipmap_levels > 1) && resize) {
|
||||
// if we are doing mipmap resizing we need to account for adjusted tex size
|
||||
// (we can end up with only one mipmap level if base_level is high enough so don't check it)
|
||||
if (base_level > 0) {
|
||||
Assertion(resize == false, "Cannot use manual and mipmap resizing at the same time!");
|
||||
Assert(texmem == nullptr);
|
||||
|
||||
// If we have mipmaps then tex_w/h are already adjusted for the base level but that will cause problems with
|
||||
@@ -1864,11 +1866,13 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
|
||||
GL_state.Texture.Enable(0);
|
||||
|
||||
// render buffer
|
||||
// glGenRenderbuffers(1, &new_fbo.renderbuffer_id);
|
||||
// glBindRenderbuffer(GL_RENDERBUFFER, new_fbo.renderbuffer_id);
|
||||
// glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, *w, *h);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
// render buffer (depth)
|
||||
if (flags & BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT) {
|
||||
glGenRenderbuffers(1, &new_fbo->renderbuffer_id);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, new_fbo->renderbuffer_id);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, *w, *h);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
}
|
||||
|
||||
// frame buffer
|
||||
glGenFramebuffers(1, &new_fbo->framebuffer_id);
|
||||
@@ -1881,7 +1885,9 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ts->texture_id, 0, 0);
|
||||
}
|
||||
|
||||
// glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, new_fbo.renderbuffer_id);
|
||||
if (flags & BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT) {
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, new_fbo->renderbuffer_id);
|
||||
}
|
||||
|
||||
if ( opengl_check_framebuffer() ) {
|
||||
// Oops!! reset everything and then bail
|
||||
@@ -1896,7 +1902,10 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
|
||||
glDeleteFramebuffers(1, &new_fbo->framebuffer_id);
|
||||
|
||||
// glDeleteRenderbuffersEXT(1, &new_fbo.renderbuffer_id);
|
||||
if (new_fbo->renderbuffer_id) {
|
||||
glDeleteRenderbuffers(1, &new_fbo->renderbuffer_id);
|
||||
new_fbo->renderbuffer_id = 0;
|
||||
}
|
||||
|
||||
opengl_set_texture_target();
|
||||
opengl_free_fbo_slot(fbo_id);
|
||||
|
||||
@@ -85,6 +85,7 @@ static opengl_vertex_bind GL_array_binding_data[] =
|
||||
{ vertex_format_data::MODEL_ID, 1, GL_FLOAT, GL_FALSE, opengl_vert_attrib::MODEL_ID },
|
||||
{ vertex_format_data::RADIUS, 1, GL_FLOAT, GL_FALSE, opengl_vert_attrib::RADIUS },
|
||||
{ vertex_format_data::UVEC, 3, GL_FLOAT, GL_FALSE, opengl_vert_attrib::UVEC },
|
||||
{ vertex_format_data::MATRIX4, 16, GL_FLOAT, GL_FALSE, opengl_vert_attrib::MODEL_MATRIX },
|
||||
};
|
||||
|
||||
struct opengl_buffer_object {
|
||||
@@ -821,7 +822,10 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
opengl_tnl_set_material(material_info, false, false);
|
||||
|
||||
if ( GL_state.CullFace() ) {
|
||||
GL_state.FrontFaceValue(GL_CW);
|
||||
// Model rendering uses CW front faces in the default backbuffer path.
|
||||
// Render-to-texture flips the projection vertically, which flips winding,
|
||||
// so we must invert the front-face definition there.
|
||||
GL_state.FrontFaceValue(gr_screen.rendering_to_texture != -1 ? GL_CCW : GL_CW);
|
||||
}
|
||||
|
||||
gr_set_center_alpha(material_info->get_center_alpha());
|
||||
@@ -855,10 +859,6 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
Current_shader->program->Uniforms.setTextureUniform("sGlowmap", 1);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_SPEC))
|
||||
Current_shader->program->Uniforms.setTextureUniform("sSpecmap", 2);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_ENV)) {
|
||||
Current_shader->program->Uniforms.setTextureUniform("sEnvmap", 3);
|
||||
Current_shader->program->Uniforms.setTextureUniform("sIrrmap", 11);
|
||||
}
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_NORMAL))
|
||||
Current_shader->program->Uniforms.setTextureUniform("sNormalmap", 4);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_AMBIENT))
|
||||
@@ -874,7 +874,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
//No shader ever defines this, so don't push it.
|
||||
//Current_shader->program->Uniforms.setTextureUniform("sHeightmap", 5);
|
||||
|
||||
if (material_info->get_texture_map(TM_BASE_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_BASE_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -883,7 +883,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
0);
|
||||
}
|
||||
|
||||
if (material_info->get_texture_map(TM_GLOW_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_GLOW_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_GLOW_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -892,9 +892,9 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
1);
|
||||
}
|
||||
|
||||
if (material_info->get_texture_map(TM_SPECULAR_TYPE) > 0 ||
|
||||
material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_SPECULAR_TYPE) >= 0 ||
|
||||
material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) >= 0) {
|
||||
if (material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_SPEC_GLOSS_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -911,13 +911,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
}
|
||||
}
|
||||
|
||||
if (ENVMAP > 0) {
|
||||
gr_opengl_tcache_set(ENVMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, 3);
|
||||
gr_opengl_tcache_set(IRRMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, 11);
|
||||
Assertion(array_index == 0, "Cube map arrays are not supported yet!");
|
||||
}
|
||||
|
||||
if (material_info->get_texture_map(TM_NORMAL_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_NORMAL_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_NORMAL_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -926,7 +920,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
4);
|
||||
}
|
||||
|
||||
if (material_info->get_texture_map(TM_HEIGHT_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_HEIGHT_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_HEIGHT_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -935,7 +929,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
5);
|
||||
}
|
||||
|
||||
if (material_info->get_texture_map(TM_AMBIENT_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_AMBIENT_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_AMBIENT_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -944,7 +938,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
6);
|
||||
}
|
||||
|
||||
if (material_info->get_texture_map(TM_MISC_TYPE) > 0) {
|
||||
if (material_info->get_texture_map(TM_MISC_TYPE) >= 0) {
|
||||
gr_opengl_tcache_set(material_info->get_texture_map(TM_MISC_TYPE),
|
||||
TCACHE_TYPE_NORMAL,
|
||||
&u_scale,
|
||||
@@ -957,7 +951,7 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
GL_state.Texture.Enable(8, GL_TEXTURE_2D_ARRAY, Shadow_map_texture);
|
||||
}
|
||||
|
||||
if (material_info->get_animated_effect() > 0) {
|
||||
if (material_info->get_animated_effect() >= 0) {
|
||||
if (Scene_framebuffer_in_frame) {
|
||||
GL_state.Texture.Enable(9, GL_TEXTURE_2D, Scene_composite_texture);
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||
@@ -1189,7 +1183,7 @@ void opengl_bind_vertex_component(const vertex_format_data &vert_component, size
|
||||
|
||||
if ( Current_shader != NULL ) {
|
||||
// grabbing a vertex attribute is dependent on what current shader has been set. i hope no one calls opengl_bind_vertex_layout before opengl_set_current_shader
|
||||
GLint index = opengl_shader_get_attribute(attrib_info.attribute_id);
|
||||
GLint index = attrib_info.attribute_id;
|
||||
|
||||
if ( index >= 0 ) {
|
||||
GL_state.Array.EnableVertexAttrib(index);
|
||||
@@ -1233,15 +1227,23 @@ void opengl_bind_vertex_array(const vertex_layout& layout) {
|
||||
|
||||
auto attribIndex = attrib_info.attribute_id;
|
||||
|
||||
glEnableVertexAttribArray(attribIndex);
|
||||
glVertexAttribFormat(attribIndex,
|
||||
bind_info.size,
|
||||
bind_info.data_type,
|
||||
bind_info.normalized,
|
||||
static_cast<GLuint>(component->offset));
|
||||
GLuint add_val_index = 0;
|
||||
for (GLint size = bind_info.size; size > 0; size -=4) {
|
||||
glEnableVertexAttribArray(attribIndex + add_val_index);
|
||||
glVertexAttribFormat(attribIndex + add_val_index,
|
||||
std::min(size, 4),
|
||||
bind_info.data_type,
|
||||
bind_info.normalized,
|
||||
static_cast<GLuint>(component->offset) + add_val_index * 16);
|
||||
|
||||
// Currently, all vertex data comes from one buffer.
|
||||
glVertexAttribBinding(attribIndex, 0);
|
||||
glVertexAttribBinding(attribIndex + add_val_index, static_cast<GLuint>(component->buffer_number));
|
||||
|
||||
add_val_index++;
|
||||
}
|
||||
|
||||
if (component->divisor != 0) {
|
||||
glVertexBindingDivisor(static_cast<GLuint>(component->buffer_number), static_cast<GLuint>(component->divisor));
|
||||
}
|
||||
}
|
||||
|
||||
Stored_vertex_arrays.insert(std::make_pair(layout, vao));
|
||||
@@ -1267,3 +1269,28 @@ void opengl_bind_vertex_layout(vertex_layout &layout, GLuint vertexBuffer, GLuin
|
||||
static_cast<GLsizei>(layout.get_vertex_stride()));
|
||||
GL_state.Array.BindElementBuffer(indexBuffer);
|
||||
}
|
||||
|
||||
void opengl_bind_vertex_layout_multiple(vertex_layout &layout, const SCP_vector<GLuint>& vertexBuffer, GLuint indexBuffer, size_t base_offset) {
|
||||
GR_DEBUG_SCOPE("Bind vertex layout");
|
||||
if (!GLAD_GL_ARB_vertex_attrib_binding) {
|
||||
/*
|
||||
* This will mean that decals don't render.
|
||||
* It's possible, but way too much effort to support non-instanced fallback rendering here.
|
||||
* By my estimation, every GPU you might still run FSO run supports this.
|
||||
* per mesamatrix.net, even the least-extension supporting mesa drivers all support this extension
|
||||
* */
|
||||
return;
|
||||
}
|
||||
|
||||
opengl_bind_vertex_array(layout);
|
||||
|
||||
GLuint i = 0;
|
||||
for(const auto& buffer : vertexBuffer) {
|
||||
GL_state.Array.BindVertexBuffer(i,
|
||||
buffer,
|
||||
static_cast<GLintptr>(base_offset),
|
||||
static_cast<GLsizei>(layout.get_vertex_stride(i)));
|
||||
i++;
|
||||
}
|
||||
GL_state.Array.BindElementBuffer(indexBuffer);
|
||||
}
|
||||
@@ -83,5 +83,6 @@ void opengl_tnl_set_model_material(model_material *material_info);
|
||||
void gr_opengl_set_viewport(int x, int y, int width, int height);
|
||||
|
||||
void opengl_bind_vertex_layout(vertex_layout &layout, GLuint vertexBuffer, GLuint indexBuffer, size_t base_offset = 0);
|
||||
void opengl_bind_vertex_layout_multiple(vertex_layout &layout, const SCP_vector<GLuint>& vertexBuffer, GLuint indexBuffer, size_t base_offset = 0);
|
||||
|
||||
#endif //_GROPENGLTNL_H
|
||||
@@ -30,6 +30,14 @@ PostEffectUniformType mapUniformNameToType(const SCP_string& uniform_name)
|
||||
return PostEffectUniformType::Dither;
|
||||
} else if (!stricmp(uniform_name.c_str(), "tint")) {
|
||||
return PostEffectUniformType::Tint;
|
||||
} else if (!stricmp(uniform_name.c_str(), "custom_effect_vec3_a")) {
|
||||
return PostEffectUniformType::CustomEffectVEC3A;
|
||||
} else if (!stricmp(uniform_name.c_str(), "custom_effect_float_a")) {
|
||||
return PostEffectUniformType::CustomEffectFloatA;
|
||||
} else if (!stricmp(uniform_name.c_str(), "custom_effect_vec3_b")) {
|
||||
return PostEffectUniformType::CustomEffectVEC3B;
|
||||
} else if (!stricmp(uniform_name.c_str(), "custom_effect_float_b")) {
|
||||
return PostEffectUniformType::CustomEffectFloatB;
|
||||
} else {
|
||||
error_display(0, "Unknown uniform name '%s'!", uniform_name.c_str());
|
||||
return PostEffectUniformType::Invalid;
|
||||
|
||||
@@ -17,6 +17,10 @@ enum class PostEffectUniformType {
|
||||
Cutoff,
|
||||
Tint,
|
||||
Dither,
|
||||
CustomEffectVEC3A,
|
||||
CustomEffectFloatA,
|
||||
CustomEffectVEC3B,
|
||||
CustomEffectFloatB,
|
||||
};
|
||||
|
||||
struct post_effect_t {
|
||||
|
||||
@@ -242,7 +242,7 @@ void gr_aabitmap(int x, int y, int resize_mode, bool mirror, float scale_factor)
|
||||
resize_mode,
|
||||
true,
|
||||
mirror,
|
||||
&gr_screen.current_color,
|
||||
&GR_CURRENT_COLOR,
|
||||
scale_factor);
|
||||
}
|
||||
void gr_aabitmap_ex(int x, int y, int w, int h, int sx, int sy, int resize_mode, bool mirror, float scale_factor) {
|
||||
@@ -383,7 +383,7 @@ void gr_aabitmap_ex(int x, int y, int w, int h, int sx, int sy, int resize_mode,
|
||||
resize_mode,
|
||||
true,
|
||||
mirror,
|
||||
&gr_screen.current_color,
|
||||
&GR_CURRENT_COLOR,
|
||||
scale_factor);
|
||||
}
|
||||
//these are penguins bitmap functions
|
||||
@@ -1439,7 +1439,7 @@ void gr_aabitmap_list(bitmap_rect_list* list, int n_bm, int resize_mode, float a
|
||||
render_mat.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
|
||||
render_mat.set_depth_mode(ZBUFFER_TYPE_NONE);
|
||||
render_mat.set_texture_map(TM_BASE_TYPE, gr_screen.current_bitmap);
|
||||
render_mat.set_color(gr_screen.current_color);
|
||||
render_mat.set_color(GR_CURRENT_COLOR);
|
||||
render_mat.set_cull_mode(false);
|
||||
render_mat.set_texture_type(material::TEX_TYPE_AABITMAP);
|
||||
|
||||
|
||||
@@ -70,6 +70,11 @@ namespace font
|
||||
this->filename = newName;
|
||||
}
|
||||
|
||||
void FSFont::setFamilyName(const SCP_string& newName)
|
||||
{
|
||||
this->familyName = newName;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool FSFont::getAutoScaleBehavior() const
|
||||
{
|
||||
return this->autoScale;
|
||||
@@ -105,6 +110,11 @@ namespace font
|
||||
return this->filename;
|
||||
}
|
||||
|
||||
const SCP_string& FSFont::getFamilyName() const
|
||||
{
|
||||
return this->familyName;
|
||||
}
|
||||
|
||||
void FSFont::computeFontMetrics() {
|
||||
_height = this->getTextHeight() + this->offsetTop + this->offsetBottom;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace font
|
||||
private:
|
||||
SCP_string name = "<Invalid>"; //!< The name of this font
|
||||
SCP_string filename; //!< The file name used to retrieve this font
|
||||
SCP_string familyName; //!< The family name of the font. Will be "volition font" for bitmap fonts
|
||||
|
||||
protected:
|
||||
bool autoScale = false; //!< If the font is allowed to auto scale. Only used for VFNT fonts as NVG fonts do the auto scale calculation during parse time
|
||||
@@ -86,6 +87,15 @@ namespace font
|
||||
*/
|
||||
void setFilename(const SCP_string& newName);
|
||||
|
||||
/**
|
||||
* @brief Sets the family name of this font.
|
||||
*
|
||||
* @date 17.6.2025
|
||||
*
|
||||
* @param newName The new famly name.
|
||||
*/
|
||||
void setFamilyName(const SCP_string& newName);
|
||||
|
||||
/**
|
||||
* @brief Gets the name of this font.
|
||||
*
|
||||
@@ -104,6 +114,15 @@ namespace font
|
||||
*/
|
||||
const SCP_string& getFilename() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the family name of this font. Will be "Volition font" for bitmap fonts
|
||||
*
|
||||
* @date 17.6.2025
|
||||
*
|
||||
* @return The family name.
|
||||
*/
|
||||
virtual const SCP_string& getFamilyName() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the type of this font.
|
||||
*
|
||||
|
||||
@@ -53,7 +53,8 @@ namespace font
|
||||
|
||||
FSFont *FontManager::getCurrentFont()
|
||||
{
|
||||
return getFont(currentFontIndex);
|
||||
int id = gr_lua_context_active() ? gr_lua_screen.current_font_index : currentFontIndex;
|
||||
return getFont(id);
|
||||
}
|
||||
|
||||
int FontManager::getCurrentFontIndex()
|
||||
@@ -309,6 +310,153 @@ namespace font
|
||||
}
|
||||
}
|
||||
|
||||
// This function will extract the font family name (Name ID 1) from TrueType font data.
|
||||
// It handles UCS-2 (UTF-16BE) to UTF-8 conversion.
|
||||
static SCP_string extractFamilyNameFromTTF(const TrueTypeFontData& fontData)
|
||||
{
|
||||
try {
|
||||
// TTF/OTF fonts start the table directory at byte 12.
|
||||
constexpr size_t table_offset = 12;
|
||||
|
||||
const ubyte* data = fontData.data.get();
|
||||
const size_t size = fontData.size;
|
||||
|
||||
if (size < table_offset) {
|
||||
throw std::runtime_error("Font data too small for table offset");
|
||||
}
|
||||
|
||||
// Offset to the start of the table directory (after SFNT header)
|
||||
const uint8_t* tableDir = data + table_offset;
|
||||
|
||||
// Read numTables (ushort at offset 4 in SFNT header)
|
||||
// The SFNT header bytes are big-endian
|
||||
uint16_t numTables = (static_cast<uint16_t>(data[4]) << 8) | static_cast<uint16_t>(data[5]);
|
||||
|
||||
const uint8_t* nameTable = nullptr;
|
||||
|
||||
// Each table entry is 16 bytes
|
||||
constexpr size_t tableEntrySize = 16;
|
||||
const size_t tableDirSize = static_cast<size_t>(numTables) * tableEntrySize;
|
||||
if (size < table_offset + tableDirSize) {
|
||||
throw std::runtime_error("Table directory extends past file size");
|
||||
}
|
||||
|
||||
// Iterate through table directory entries to find the 'name' table
|
||||
for (int i = 0; i < numTables; ++i) {
|
||||
const uint8_t* entry = tableDir + i * tableEntrySize;
|
||||
|
||||
// Ensure entry access is within range
|
||||
if (entry + 12 >= data + size) {
|
||||
throw std::runtime_error("Table entry access out of bounds");
|
||||
}
|
||||
|
||||
// Read table tag (4 bytes)
|
||||
uint32_t tag = (static_cast<uint32_t>(entry[0]) << 24) | (static_cast<uint32_t>(entry[1]) << 16) |
|
||||
(static_cast<uint32_t>(entry[2]) << 8) | static_cast<uint32_t>(entry[3]);
|
||||
|
||||
// Check if it's the 'name' table (tag 0x6E616D65)
|
||||
if (tag == 0x6E616D65) {
|
||||
// Read offset to the 'name' table (4 bytes, big-endian)
|
||||
uint32_t offset = (static_cast<uint32_t>(entry[8]) << 24) |
|
||||
(static_cast<uint32_t>(entry[9]) << 16) |
|
||||
(static_cast<uint32_t>(entry[10]) << 8) | static_cast<uint32_t>(entry[11]);
|
||||
|
||||
if (offset >= static_cast<uint32_t>(size)) {
|
||||
throw std::runtime_error("Name table offset beyond file size");
|
||||
}
|
||||
|
||||
nameTable = data + offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nameTable || nameTable + 6 > data + size) {
|
||||
throw std::runtime_error("Name table header is missing or truncated");
|
||||
}
|
||||
|
||||
// Name table header - All values are big-endian
|
||||
uint16_t count = (static_cast<uint16_t>(nameTable[2]) << 8) | static_cast<uint16_t>(nameTable[3]);
|
||||
uint16_t stringOffset = (static_cast<uint16_t>(nameTable[4]) << 8) | static_cast<uint16_t>(nameTable[5]);
|
||||
|
||||
constexpr size_t recordSize = 12;
|
||||
const uint8_t* recordBase = nameTable + 6;
|
||||
|
||||
if (recordBase + count * recordSize > data + size) {
|
||||
throw std::runtime_error("Name records extend beyond font data");
|
||||
}
|
||||
|
||||
// Iterate through name records
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const uint8_t* record =
|
||||
recordBase + i * recordSize; // 6 bytes for name table header, 12 bytes per record
|
||||
|
||||
// Read metadata for the record
|
||||
uint16_t platformID = (static_cast<uint16_t>(record[0]) << 8) | static_cast<uint16_t>(record[1]);
|
||||
uint16_t encodingID = (static_cast<uint16_t>(record[2]) << 8) | static_cast<uint16_t>(record[3]);
|
||||
uint16_t nameID = (static_cast<uint16_t>(record[6]) << 8) | static_cast<uint16_t>(record[7]);
|
||||
uint16_t length = (static_cast<uint16_t>(record[8]) << 8) | static_cast<uint16_t>(record[9]);
|
||||
uint16_t offset = (static_cast<uint16_t>(record[10]) << 8) | static_cast<uint16_t>(record[11]);
|
||||
|
||||
// We are looking for Name ID 1 (Font Family Name)
|
||||
// Prefer Unicode (Platform ID 0, 3) or Apple Roman (Platform ID 1) if available
|
||||
if (nameID == 1) {
|
||||
// Check for Unicode (Platform ID 0, Encoding ID 3 or 4) or (Platform ID 3, Encoding ID 1)
|
||||
// or Mac Roman (Platform ID 1, Encoding ID 0)
|
||||
bool isUnicode = (platformID == 0 && (encodingID == 3 || encodingID == 4)) ||
|
||||
(platformID == 3 && encodingID == 1);
|
||||
bool isMacRoman = (platformID == 1 && encodingID == 0);
|
||||
|
||||
if (isUnicode || isMacRoman) {
|
||||
const uint8_t* nameString = nameTable + stringOffset + offset;
|
||||
SCP_string familyName;
|
||||
|
||||
// Bounds check to prevent reading past the end of the font metadata
|
||||
if (nameString + length > data + size) {
|
||||
throw std::runtime_error("Name string extends past font data");
|
||||
}
|
||||
|
||||
if (isUnicode) {
|
||||
// UCS-2 (UTF-16BE) to UTF-8 conversion
|
||||
// This assumes standard UCS-2, which is UTF-16BE for basic multilingual plane.
|
||||
for (int j = 0; j < length; j += 2) {
|
||||
uint16_t unicodeChar = (static_cast<uint16_t>(nameString[j]) << 8) |
|
||||
static_cast<uint16_t>(nameString[j + 1]);
|
||||
|
||||
if (unicodeChar <= 0x7F) { // 1-byte UTF-8
|
||||
familyName += static_cast<char>(unicodeChar);
|
||||
} else if (unicodeChar <= 0x7FF) { // 2-byte UTF-8
|
||||
familyName += static_cast<char>(0xC0 | (unicodeChar >> 6));
|
||||
familyName += static_cast<char>(0x80 | (unicodeChar & 0x3F));
|
||||
} else { // 3-byte UTF-8
|
||||
familyName += static_cast<char>(0xE0 | (unicodeChar >> 12));
|
||||
familyName += static_cast<char>(0x80 | ((unicodeChar >> 6) & 0x3F));
|
||||
familyName += static_cast<char>(0x80 | (unicodeChar & 0x3F));
|
||||
}
|
||||
// For supplementary planes (4-byte UTF-8, characters > 0xFFFF),
|
||||
// this simple conversion is not sufficient and would require surrogate pair handling.
|
||||
// Most common font names will be in BMP (Basic Multilingual Plane) according to my
|
||||
// research - Mjn
|
||||
}
|
||||
} else { // Mac Roman (single byte) - this encoding is less common but supported easily enough
|
||||
for (int j = 0; j < length; ++j) {
|
||||
familyName += static_cast<char>(nameString[j]);
|
||||
}
|
||||
}
|
||||
return familyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("No suitable family name found");
|
||||
} catch (const std::exception& e) {
|
||||
mprintf(("Failed to extract font name: %s\n", e.what()));
|
||||
return "";
|
||||
} catch (...) {
|
||||
mprintf(("Failed to extract font name: Unknown exception\n"));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<NVGFont*, int> FontManager::loadNVGFont(const SCP_string& fileName, float fontSize)
|
||||
{
|
||||
auto iter = allocatedData.find(fileName);
|
||||
@@ -359,6 +507,7 @@ namespace font
|
||||
std::unique_ptr<NVGFont> nvgFont(new NVGFont());
|
||||
nvgFont->setHandle(handle);
|
||||
nvgFont->setSize(fontSize);
|
||||
nvgFont->setFamilyName(extractFamilyNameFromTTF(*data));
|
||||
|
||||
auto ptr = nvgFont.get();
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace font
|
||||
return this->fontPtr;
|
||||
}
|
||||
|
||||
const SCP_string& VFNTFont::getFamilyName() const
|
||||
{
|
||||
static const SCP_string volitionFontName = "Volition Font";
|
||||
return volitionFontName;
|
||||
}
|
||||
|
||||
extern int get_char_width_old(font* fnt, ubyte c1, ubyte c2, int *width, int* spacing);
|
||||
void VFNTFont::getStringSize(const char *text, size_t textSize, int /* resize_mode */, float *w1, float *h1, float scaleMultiplier) const
|
||||
{
|
||||
|
||||
@@ -55,6 +55,15 @@ namespace font
|
||||
*/
|
||||
float getTextHeight() const override;
|
||||
|
||||
/**
|
||||
* @brief Gets the family name of this font
|
||||
*
|
||||
* @see FSFont::getFamilyName()
|
||||
*
|
||||
* @return The family name.
|
||||
*/
|
||||
const SCP_string& getFamilyName() const override;
|
||||
|
||||
/**
|
||||
* @brief Gets the size of the specified string in pixels.
|
||||
*
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void parse_nvg_font(const SCP_string& fontFilename)
|
||||
bool parse_nvg_font(const SCP_string& fontFilename)
|
||||
{
|
||||
float size = 8.0f;
|
||||
SCP_string fontStr;
|
||||
@@ -122,27 +122,27 @@ namespace
|
||||
if (hasName)
|
||||
{
|
||||
error_display(0, "Font with name \"%s\" is already present! Font names have to be unique!", fontStr.c_str());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
error_display(0, "Found font with same default name (\"%s\"). This is most likely a duplicate.", fontStr.c_str());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto nvgPair = FontManager::loadNVGFont(fontFilename, size);
|
||||
auto nvgFont = nvgPair.first;
|
||||
|
||||
// Now we can set the auto size behavior which is used for special character rendering
|
||||
nvgFont->setAutoScaleBehavior(autoSize);
|
||||
|
||||
if (nvgFont == NULL)
|
||||
{
|
||||
error_display(0, "Couldn't load font \"%s\".", fontFilename.c_str());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now we can set the auto size behavior which is used for special character rendering
|
||||
nvgFont->setAutoScaleBehavior(autoSize);
|
||||
|
||||
if (optional_string("+Can Scale:")) {
|
||||
bool temp;
|
||||
|
||||
@@ -289,9 +289,11 @@ namespace
|
||||
|
||||
// Make sure that the height is not invalid
|
||||
nvgFont->computeFontMetrics();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void parse_vfnt_font(const SCP_string& fontFilename)
|
||||
bool parse_vfnt_font(const SCP_string& fontFilename)
|
||||
{
|
||||
auto vfntPair = FontManager::loadVFNTFont(fontFilename);
|
||||
auto font = vfntPair.first;
|
||||
@@ -299,7 +301,7 @@ namespace
|
||||
if (font == NULL)
|
||||
{
|
||||
error_display(0, "Couldn't load font\"%s\".", fontFilename.c_str());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
SCP_string fontName;
|
||||
@@ -315,6 +317,7 @@ namespace
|
||||
|
||||
font->setName(fontName);
|
||||
font->setFilename(fontFilename);
|
||||
font->setFamilyName("Volition Font");
|
||||
|
||||
int font_id = vfntPair.second;
|
||||
|
||||
@@ -420,8 +423,11 @@ namespace
|
||||
|
||||
font->setBottomOffset(temp);
|
||||
}
|
||||
|
||||
// Make sure that the height is not invalid
|
||||
font->computeFontMetrics();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void font_parse_setup(const char *fileName)
|
||||
@@ -459,25 +465,39 @@ namespace
|
||||
FontType type;
|
||||
SCP_string fontName;
|
||||
|
||||
SCP_vector<SCP_string> skipped_font_names;
|
||||
|
||||
while (parse_type(type, fontName))
|
||||
{
|
||||
bool parsed = false;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case VFNT_FONT:
|
||||
if (Unicode_text_mode) {
|
||||
WarningEx(LOCATION, "Bitmap fonts are not supported in Unicode text mode! Font %s will be ignored.", fontName.c_str());
|
||||
skip_to_start_of_string_one_of({"$TrueType:", "$Font:", "#End"});
|
||||
skipped_font_names.push_back(fontName);
|
||||
} else {
|
||||
parse_vfnt_font(fontName);
|
||||
parsed = parse_vfnt_font(fontName);
|
||||
}
|
||||
break;
|
||||
case NVG_FONT:
|
||||
parse_nvg_font(fontName);
|
||||
parsed = parse_nvg_font(fontName);
|
||||
break;
|
||||
default:
|
||||
error_display(0, "Unknown font type %d! Get a coder!", (int)type);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parsed)
|
||||
skip_to_start_of_string_one_of({ "$TrueType:", "$Font:", "#End" });
|
||||
}
|
||||
|
||||
// check if we skipped any fonts
|
||||
if (!skipped_font_names.empty()) {
|
||||
Warning(LOCATION, "One or more bitmap fonts were skipped because they are not supported in Unicode text mode. The list of fonts is in the debug log.");
|
||||
for (const auto& skippedFont : skipped_font_names) {
|
||||
mprintf(("Warning: Skipped bitmap font: %s\n", skippedFont.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
// done parsing
|
||||
@@ -539,24 +559,48 @@ namespace font
|
||||
font_initialized = false;
|
||||
}
|
||||
|
||||
int force_fit_string(char *str, int max_str, int max_width, float scale)
|
||||
int force_fit_string(char *str, size_t max_str_len, int max_width, float scale)
|
||||
{
|
||||
int w;
|
||||
if (max_width <= 0 || max_str_len == 0) {
|
||||
*str = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (len > max_str_len) {
|
||||
len = max_str_len;
|
||||
str[len] = 0;
|
||||
}
|
||||
|
||||
int w;
|
||||
gr_get_string_size(&w, nullptr, str, scale);
|
||||
if (w > max_width) {
|
||||
if ((int)strlen(str) > max_str - 3) {
|
||||
Assert(max_str >= 3);
|
||||
str[max_str - 3] = 0;
|
||||
constexpr char ellipsis_char = '.';
|
||||
constexpr size_t ellipsis_len = 3;
|
||||
|
||||
// make sure an ellipsis will fit
|
||||
if (len < ellipsis_len) {
|
||||
*str = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
strcpy(str + strlen(str) - 1, "...");
|
||||
gr_get_string_size(&w, nullptr, str, scale);
|
||||
while (w > max_width) {
|
||||
Assert(strlen(str) >= 4); // if this is hit, a bad max_width was passed in and the calling function needs fixing.
|
||||
strcpy(str + strlen(str) - 4, "...");
|
||||
gr_get_string_size(&w, nullptr, str, scale);
|
||||
// replace the last few chars with an ellipsis
|
||||
for (size_t i = 0; i < ellipsis_len; ++i) {
|
||||
--len;
|
||||
str[len] = ellipsis_char;
|
||||
}
|
||||
|
||||
// measure with ellipsis before shrinking further (the ellipsis characters could be narrower than the characters they replaced)
|
||||
gr_get_string_size(&w, nullptr, str, scale, len + ellipsis_len);
|
||||
|
||||
// move the ellipsis back until the whole string fits
|
||||
while (len > 0 && w > max_width) {
|
||||
--len;
|
||||
str[len] = ellipsis_char;
|
||||
gr_get_string_size(&w, nullptr, str, scale, len + ellipsis_len);
|
||||
}
|
||||
|
||||
str[len + ellipsis_len] = 0;
|
||||
}
|
||||
|
||||
return w;
|
||||
|
||||
Loaded 100 of 1093 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user