Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
3 | * described in COPYING.txt. |
||
4 | * Portions of this file are copyright Parallax Software and licensed |
||
5 | * according to the Parallax license below. |
||
6 | * See COPYING.txt for license details. |
||
7 | |||
8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
18 | */ |
||
19 | |||
20 | /* |
||
21 | * |
||
22 | * Multiplayer robot code |
||
23 | * |
||
24 | */ |
||
25 | #include <type_traits> |
||
26 | #include "multiinternal.h" |
||
27 | |||
28 | #include <string.h> |
||
29 | #include <stdlib.h> |
||
30 | #include <stdexcept> |
||
31 | |||
32 | #include "vecmat.h" |
||
33 | #include "object.h" |
||
34 | #include "multibot.h" |
||
35 | #include "game.h" |
||
36 | #include "multi.h" |
||
37 | #include "laser.h" |
||
38 | #include "dxxerror.h" |
||
39 | #include "timer.h" |
||
40 | #include "text.h" |
||
41 | #include "player.h" |
||
42 | #include "ai.h" |
||
43 | #include "fireball.h" |
||
44 | #include "aistruct.h" |
||
45 | #include "gameseg.h" |
||
46 | #include "robot.h" |
||
47 | #include "powerup.h" |
||
48 | #include "scores.h" |
||
49 | #include "gauges.h" |
||
50 | #include "fuelcen.h" |
||
51 | #include "morph.h" |
||
52 | #include "digi.h" |
||
53 | #include "sounds.h" |
||
54 | #include "effects.h" |
||
55 | #include "automap.h" |
||
56 | #include "physics.h" |
||
57 | #include "byteutil.h" |
||
58 | #include "escort.h" |
||
59 | |||
60 | #include "compiler-range_for.h" |
||
61 | #include "partial_range.h" |
||
62 | |||
63 | namespace dsx { |
||
64 | static int multi_add_controlled_robot(vmobjptridx_t objnum, int agitation); |
||
65 | } |
||
66 | static void multi_send_robot_position_sub(const vmobjptridx_t objnum, int now); |
||
67 | static void multi_send_release_robot(vmobjptridx_t objnum); |
||
68 | static void multi_delete_controlled_robot(const vmobjptridx_t objnum); |
||
69 | |||
70 | constexpr serial::endian_access::foreign_endian_type serial::endian_access::foreign_endian; |
||
71 | constexpr serial::endian_access::little_endian_type serial::endian_access::little_endian; |
||
72 | constexpr serial::endian_access::big_endian_type serial::endian_access::big_endian; |
||
73 | constexpr serial::endian_access::native_endian_type serial::endian_access::native_endian; |
||
74 | |||
75 | // |
||
76 | // Code for controlling robots in multiplayer games |
||
77 | // |
||
78 | |||
79 | #define STANDARD_EXPL_DELAY (F1_0/4) |
||
80 | #if defined(DXX_BUILD_DESCENT_I) |
||
81 | #define MIN_CONTROL_TIME F1_0*2 |
||
82 | #define ROBOT_TIMEOUT F1_0*3 |
||
83 | |||
84 | #define MAX_TO_DELETE 67 |
||
85 | #elif defined(DXX_BUILD_DESCENT_II) |
||
86 | #define MIN_CONTROL_TIME F1_0*1 |
||
87 | #define ROBOT_TIMEOUT F1_0*2 |
||
88 | #endif |
||
89 | |||
90 | #define MIN_TO_ADD 60 |
||
91 | |||
92 | std::array<objnum_t, MAX_ROBOTS_CONTROLLED> robot_controlled; |
||
93 | std::array<int, MAX_ROBOTS_CONTROLLED> robot_agitation, |
||
94 | robot_send_pending, |
||
95 | robot_fired; |
||
96 | std::array<fix64, MAX_ROBOTS_CONTROLLED> robot_controlled_time, |
||
97 | robot_last_send_time, |
||
98 | robot_last_message_time; |
||
99 | ubyte robot_fire_buf[MAX_ROBOTS_CONTROLLED][18+3]; |
||
100 | |||
101 | #define MULTI_ROBOT_PRIORITY(objnum, pnum) (((objnum % 4) + pnum) % N_players) |
||
102 | |||
103 | //#define MULTI_ROBOT_PRIORITY(objnum, pnum) multi_robot_priority(objnum, pnum) |
||
104 | //int multi_robot_priority(int objnum, int pnum) |
||
105 | //{ |
||
106 | // return( ((objnum % 4) + pnum) % N_players); |
||
107 | //} |
||
108 | |||
109 | int multi_can_move_robot(const vmobjptridx_t objnum, int agitation) |
||
110 | { |
||
111 | auto &BossUniqueState = LevelUniqueObjectState.BossState; |
||
112 | // Determine whether or not I am allowed to move this robot. |
||
113 | // Claim robot if necessary. |
||
114 | |||
115 | if (Player_dead_state == player_dead_state::exploded) |
||
116 | return 0; |
||
117 | |||
118 | if (objnum->type != OBJ_ROBOT) |
||
119 | { |
||
120 | #ifndef NDEBUG |
||
121 | Int3(); |
||
122 | #endif |
||
123 | return 0; |
||
124 | } |
||
125 | |||
126 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
127 | if (Robot_info[get_robot_id(objnum)].boss_flag && BossUniqueState.Boss_dying) |
||
128 | return 0; |
||
129 | else if (objnum->ctype.ai_info.REMOTE_OWNER == Player_num) // Already my robot! |
||
130 | { |
||
131 | int slot_num = objnum->ctype.ai_info.REMOTE_SLOT_NUM; |
||
132 | |||
133 | if ((slot_num < 0) || (slot_num >= MAX_ROBOTS_CONTROLLED)) |
||
134 | { |
||
135 | return 0; |
||
136 | } |
||
137 | |||
138 | if (robot_fired[slot_num]) { |
||
139 | return 0; |
||
140 | } |
||
141 | else { |
||
142 | robot_agitation[slot_num] = agitation; |
||
143 | robot_last_message_time[slot_num] = GameTime64; |
||
144 | return 1; |
||
145 | } |
||
146 | } |
||
147 | else if ((objnum->ctype.ai_info.REMOTE_OWNER != -1) || (agitation < MIN_TO_ADD)) |
||
148 | { |
||
149 | if (agitation == ROBOT_FIRE_AGITATION) // Special case for firing at non-player |
||
150 | { |
||
151 | return 1; // Try to fire at player even tho we're not in control! |
||
152 | } |
||
153 | else |
||
154 | return 0; |
||
155 | } |
||
156 | else |
||
157 | return multi_add_controlled_robot(objnum, agitation); |
||
158 | } |
||
159 | |||
160 | void multi_check_robot_timeout() |
||
161 | { |
||
162 | auto &Objects = LevelUniqueObjectState.Objects; |
||
163 | auto &vmobjptridx = Objects.vmptridx; |
||
164 | static fix64 lastcheck = 0; |
||
165 | int i; |
||
166 | |||
167 | if (GameTime64 > lastcheck + F1_0 || lastcheck > GameTime64) |
||
168 | { |
||
169 | lastcheck = GameTime64; |
||
170 | for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++) |
||
171 | { |
||
172 | if (robot_controlled[i] != object_none && robot_last_send_time[i] + ROBOT_TIMEOUT < GameTime64) |
||
173 | { |
||
174 | const auto &&robot_objp = vmobjptridx(robot_controlled[i]); |
||
175 | if (robot_objp->ctype.ai_info.REMOTE_OWNER != Player_num) |
||
176 | { |
||
177 | robot_controlled[i] = object_none; |
||
178 | Int3(); // Non-terminal but Rob is interesting, step over please... |
||
179 | return; |
||
180 | } |
||
181 | if (robot_send_pending[i]) |
||
182 | multi_send_robot_position(robot_objp, 1); |
||
183 | multi_send_release_robot(robot_objp); |
||
184 | // multi_delete_controlled_robot(robot_controlled[i]); |
||
185 | // robot_controlled[i] = -1; |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | } |
||
190 | |||
191 | void multi_strip_robots(const int playernum) |
||
192 | { |
||
193 | auto &Objects = LevelUniqueObjectState.Objects; |
||
194 | auto &vmobjptr = Objects.vmptr; |
||
195 | auto &vmobjptridx = Objects.vmptridx; |
||
196 | // Grab all robots away from a player |
||
197 | // (player died or exited the game) |
||
198 | if (Game_mode & GM_MULTI_ROBOTS) { |
||
199 | |||
200 | if (playernum == Player_num) |
||
201 | { |
||
202 | range_for (const auto r, robot_controlled) |
||
203 | if (r != object_none) |
||
204 | multi_delete_controlled_robot(vmobjptridx(r)); |
||
205 | } |
||
206 | |||
207 | /* clang-3.7 crashes with a segmentation fault if asked to |
||
208 | * implicitly convert 1u to std::size_t in partial_range inline |
||
209 | * chain. Add a conversion up front, which seems to avoid the |
||
210 | * bug. The value must not be cast on non-clang targets because |
||
211 | * some non-clang targets issue a `-Wuseless-cast` diagnostic |
||
212 | * for the cast. Fortunately, clang does not understand |
||
213 | * `-Wuseless-cast`, so the cast can be used on all clang |
||
214 | * targets, even ones where it is useless. |
||
215 | * |
||
216 | * This crash was first seen in x86_64-pc-linux-gnu-clang-3.7, |
||
217 | * then later reported by kreator in `Apple LLVM version 7.3.0` |
||
218 | * on `x86_64-apple-darwin15.6.0`. |
||
219 | * |
||
220 | * Comment from kreator regarding the clang crash bug: |
||
221 | * This has been reported with Apple, bug report 28247284 |
||
222 | */ |
||
223 | #ifdef __clang__ |
||
224 | #define DXX_partial_range_vobjptr_skip_distance static_cast<std::size_t> |
||
225 | #else |
||
226 | #define DXX_partial_range_vobjptr_skip_distance |
||
227 | #endif |
||
228 | range_for (const auto &&objp, partial_range(vmobjptr, DXX_partial_range_vobjptr_skip_distance(1u), vmobjptr.count())) |
||
229 | #undef DXX_partial_range_vobjptr_skip_distance |
||
230 | { |
||
231 | auto &obj = *objp; |
||
232 | if (obj.type == OBJ_ROBOT && obj.ctype.ai_info.REMOTE_OWNER == playernum) |
||
233 | { |
||
234 | assert(obj.control_type == CT_AI || obj.control_type == CT_NONE || obj.control_type == CT_MORPH); |
||
235 | obj.ctype.ai_info.REMOTE_OWNER = -1; |
||
236 | if (playernum == Player_num) |
||
237 | obj.ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD; |
||
238 | else |
||
239 | obj.ctype.ai_info.REMOTE_SLOT_NUM = 0; |
||
240 | } |
||
241 | } |
||
242 | } |
||
243 | // Note -- only call this with playernum == Player_num if all other players |
||
244 | // already know that we are clearing house. This does not send a release |
||
245 | // message for each controlled robot!! |
||
246 | |||
247 | } |
||
248 | |||
249 | namespace dsx { |
||
250 | int multi_add_controlled_robot(const vmobjptridx_t objnum, int agitation) |
||
251 | { |
||
252 | auto &Objects = LevelUniqueObjectState.Objects; |
||
253 | auto &vcobjptr = Objects.vcptr; |
||
254 | auto &vmobjptridx = Objects.vmptridx; |
||
255 | int i; |
||
256 | int lowest_agitation = INT32_MAX; // MAX POSITIVE INT |
||
257 | int lowest_agitated_bot = -1; |
||
258 | int first_free_robot = -1; |
||
259 | |||
260 | // Try to add a new robot to the controlled list, return 1 if added, 0 if not. |
||
261 | |||
262 | #if defined(DXX_BUILD_DESCENT_II) |
||
263 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
264 | if (Robot_info[get_robot_id(objnum)].boss_flag) // this is a boss, so make sure he gets a slot |
||
265 | agitation=(agitation*3)+Player_num; |
||
266 | #endif |
||
267 | if (objnum->ctype.ai_info.REMOTE_SLOT_NUM > 0) |
||
268 | { |
||
269 | objnum->ctype.ai_info.REMOTE_SLOT_NUM -= 1; |
||
270 | return 0; |
||
271 | } |
||
272 | |||
273 | for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++) |
||
274 | { |
||
275 | if (robot_controlled[i] == object_none || vcobjptr(robot_controlled[i])->type != OBJ_ROBOT) { |
||
276 | first_free_robot = i; |
||
277 | break; |
||
278 | } |
||
279 | |||
280 | if (robot_last_message_time[i] + ROBOT_TIMEOUT < GameTime64) { |
||
281 | const auto &&robot_objp = vmobjptridx(robot_controlled[i]); |
||
282 | if (robot_send_pending[i]) |
||
283 | multi_send_robot_position(robot_objp, 1); |
||
284 | multi_send_release_robot(robot_objp); |
||
285 | first_free_robot = i; |
||
286 | break; |
||
287 | } |
||
288 | |||
289 | if (robot_agitation[i] < lowest_agitation && robot_controlled_time[i] + MIN_CONTROL_TIME < GameTime64) |
||
290 | { |
||
291 | lowest_agitation = robot_agitation[i]; |
||
292 | lowest_agitated_bot = i; |
||
293 | } |
||
294 | } |
||
295 | |||
296 | if (first_free_robot != -1) // Room left for new robots |
||
297 | i = first_free_robot; |
||
298 | |||
299 | else if ((agitation > lowest_agitation) |
||
300 | #if defined(DXX_BUILD_DESCENT_I) |
||
301 | && (lowest_agitation <= MAX_TO_DELETE) |
||
302 | #endif |
||
303 | ) // Replace some old robot with a more agitated one |
||
304 | { |
||
305 | const auto &&robot_objp = vmobjptridx(robot_controlled[lowest_agitated_bot]); |
||
306 | if (robot_send_pending[lowest_agitated_bot]) |
||
307 | multi_send_robot_position(robot_objp, 1); |
||
308 | multi_send_release_robot(robot_objp); |
||
309 | |||
310 | i = lowest_agitated_bot; |
||
311 | } |
||
312 | else { |
||
313 | return(0); // Sorry, can't squeeze him in! |
||
314 | } |
||
315 | |||
316 | multi_send_claim_robot(objnum); |
||
317 | robot_controlled[i] = objnum; |
||
318 | robot_agitation[i] = agitation; |
||
319 | objnum->ctype.ai_info.REMOTE_OWNER = Player_num; |
||
320 | objnum->ctype.ai_info.REMOTE_SLOT_NUM = i; |
||
321 | robot_controlled_time[i] = GameTime64; |
||
322 | robot_last_send_time[i] = robot_last_message_time[i] = GameTime64; |
||
323 | return(1); |
||
324 | } |
||
325 | } |
||
326 | |||
327 | void multi_delete_controlled_robot(const vmobjptridx_t objnum) |
||
328 | { |
||
329 | int i; |
||
330 | |||
331 | // Delete robot object number objnum from list of controlled robots because it is dead |
||
332 | |||
333 | for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++) |
||
334 | if (robot_controlled[i] == objnum) |
||
335 | break; |
||
336 | |||
337 | if (i == MAX_ROBOTS_CONTROLLED) |
||
338 | return; |
||
339 | |||
340 | if (objnum->ctype.ai_info.REMOTE_SLOT_NUM != i) |
||
341 | { |
||
342 | Int3(); // can't release this bot! |
||
343 | return; |
||
344 | } |
||
345 | |||
346 | objnum->ctype.ai_info.REMOTE_OWNER = -1; |
||
347 | objnum->ctype.ai_info.REMOTE_SLOT_NUM = 0; |
||
348 | robot_controlled[i] = object_none; |
||
349 | robot_send_pending[i] = 0; |
||
350 | robot_fired[i] = 0; |
||
351 | } |
||
352 | |||
353 | struct multi_claim_robot |
||
354 | { |
||
355 | uint8_t pnum; |
||
356 | int8_t owner; |
||
357 | uint16_t robjnum; |
||
358 | }; |
||
359 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_ROBOT_CLAIM, multi_claim_robot, b, (b.pnum, b.owner, b.robjnum)); |
||
360 | |||
361 | void multi_send_claim_robot(const vmobjptridx_t objnum) |
||
362 | { |
||
363 | if (objnum->type != OBJ_ROBOT) |
||
364 | throw std::runtime_error("claiming non-robot"); // See rob |
||
365 | // The AI tells us we should take control of this robot. |
||
366 | auto r = objnum_local_to_remote(objnum); |
||
367 | multi_serialize_write(2, multi_claim_robot{static_cast<uint8_t>(Player_num), r.owner, r.objnum}); |
||
368 | } |
||
369 | |||
370 | void multi_send_release_robot(const vmobjptridx_t objnum) |
||
371 | { |
||
372 | multi_command<MULTI_ROBOT_RELEASE> multibuf; |
||
373 | short s; |
||
374 | if (objnum->type != OBJ_ROBOT) |
||
375 | { |
||
376 | Int3(); // See rob |
||
377 | return; |
||
378 | } |
||
379 | |||
380 | multi_delete_controlled_robot(objnum); |
||
381 | |||
382 | multibuf[1] = Player_num; |
||
383 | s = objnum_local_to_remote(objnum, reinterpret_cast<int8_t *>(&multibuf[4])); |
||
384 | PUT_INTEL_SHORT(&multibuf[2], s); |
||
385 | |||
386 | multi_send_data(multibuf, 2); |
||
387 | } |
||
388 | |||
389 | #define MIN_ROBOT_COM_GAP F1_0/12 |
||
390 | |||
391 | int multi_send_robot_frame(int sent) |
||
392 | { |
||
393 | auto &Objects = LevelUniqueObjectState.Objects; |
||
394 | auto &vmobjptridx = Objects.vmptridx; |
||
395 | static int last_sent = 0; |
||
396 | |||
397 | int i; |
||
398 | int rval = 0; |
||
399 | |||
400 | for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++) |
||
401 | { |
||
402 | int sending = (last_sent+1+i)%MAX_ROBOTS_CONTROLLED; |
||
403 | if (robot_controlled[sending] != object_none && (robot_send_pending[sending] > sent || robot_fired[sending] > sent)) |
||
404 | { |
||
405 | if (robot_send_pending[sending]) |
||
406 | { |
||
407 | multi_send_robot_position_sub(vmobjptridx(robot_controlled[sending]), (std::exchange(robot_send_pending[sending], 0) > 1)); |
||
408 | } |
||
409 | |||
410 | if (robot_fired[sending]) |
||
411 | { |
||
412 | robot_fired[sending] = 0; |
||
413 | multi_send_data<MULTI_ROBOT_FIRE>(robot_fire_buf[sending], 18, 1); |
||
414 | } |
||
415 | |||
416 | if (!(Game_mode & GM_NETWORK)) |
||
417 | sent += 1; |
||
418 | |||
419 | last_sent = sending; |
||
420 | rval++; |
||
421 | } |
||
422 | } |
||
423 | Assert((last_sent >= 0) && (last_sent <= MAX_ROBOTS_CONTROLLED)); |
||
424 | return(rval); |
||
425 | } |
||
426 | |||
427 | #if defined(DXX_BUILD_DESCENT_II) |
||
428 | namespace dsx { |
||
429 | /* |
||
430 | * The thief bot moves around even when not controlled by a player. Due to its erratic and random behaviour, it's movement will diverge heavily between players and cause it to teleport when a player takes over. |
||
431 | * To counter this, let host update positions when no one controls it OR the client which does. |
||
432 | * Seperated this function to allow the positions being updated more frequently then multi_send_robot_frame (see net_udp_do_frame()). |
||
433 | */ |
||
434 | void multi_send_thief_frame() |
||
435 | { |
||
436 | auto &Objects = LevelUniqueObjectState.Objects; |
||
437 | auto &vmobjptridx = Objects.vmptridx; |
||
438 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
439 | if (!(Game_mode & GM_MULTI_ROBOTS)) |
||
440 | return; |
||
441 | |||
442 | range_for (const auto &&objp, vmobjptridx) |
||
443 | { |
||
444 | if (objp->type == OBJ_ROBOT) |
||
445 | { |
||
446 | if (robot_is_thief(Robot_info[get_robot_id(objp)])) |
||
447 | { |
||
448 | if ((multi_i_am_master() && objp->ctype.ai_info.REMOTE_OWNER == -1) || objp->ctype.ai_info.REMOTE_OWNER == Player_num) |
||
449 | { |
||
450 | multi_send_robot_position_sub(objp,1); |
||
451 | } |
||
452 | return; |
||
453 | } |
||
454 | } |
||
455 | } |
||
456 | } |
||
457 | |||
458 | } |
||
459 | #endif |
||
460 | |||
461 | void multi_send_robot_position_sub(const vmobjptridx_t objnum, int now) |
||
462 | { |
||
463 | multi_command<MULTI_ROBOT_POSITION> multibuf; |
||
464 | int loc = 0; |
||
465 | short s; |
||
466 | |||
467 | loc += 1; |
||
468 | multibuf[loc] = Player_num; loc += 1; |
||
469 | s = objnum_local_to_remote(objnum, reinterpret_cast<int8_t *>(&multibuf[loc+2])); |
||
470 | PUT_INTEL_SHORT(&multibuf[loc], s); loc += 3; // 5 |
||
471 | |||
472 | quaternionpos qpp{}; |
||
473 | create_quaternionpos(qpp, objnum); |
||
474 | PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.w); loc += 2; |
||
475 | PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.x); loc += 2; |
||
476 | PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.y); loc += 2; |
||
477 | PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.z); loc += 2; |
||
478 | PUT_INTEL_INT(&multibuf[loc], qpp.pos.x); loc += 4; |
||
479 | PUT_INTEL_INT(&multibuf[loc], qpp.pos.y); loc += 4; |
||
480 | PUT_INTEL_INT(&multibuf[loc], qpp.pos.z); loc += 4; |
||
481 | PUT_INTEL_SHORT(&multibuf[loc], qpp.segment); loc += 2; |
||
482 | PUT_INTEL_INT(&multibuf[loc], qpp.vel.x); loc += 4; |
||
483 | PUT_INTEL_INT(&multibuf[loc], qpp.vel.y); loc += 4; |
||
484 | PUT_INTEL_INT(&multibuf[loc], qpp.vel.z); loc += 4; |
||
485 | PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.x); loc += 4; |
||
486 | PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.y); loc += 4; |
||
487 | PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.z); loc += 4; // 46 + 5 = 51 |
||
488 | |||
489 | multi_send_data<MULTI_ROBOT_POSITION>(multibuf, now?1:0); |
||
490 | } |
||
491 | |||
492 | void multi_send_robot_position(object &obj, int force) |
||
493 | { |
||
494 | // Send robot position to other player(s). Includes a byte |
||
495 | // value describing whether or not they fired a weapon |
||
496 | |||
497 | if (!(Game_mode & GM_MULTI)) |
||
498 | return; |
||
499 | |||
500 | if (obj.type != OBJ_ROBOT) |
||
501 | { |
||
502 | Int3(); // See rob |
||
503 | return; |
||
504 | } |
||
505 | |||
506 | if (obj.ctype.ai_info.REMOTE_OWNER != Player_num) |
||
507 | return; |
||
508 | |||
509 | // Objects[objnum].phys_info.drag = Robot_info[Objects[objnum].id].drag; // Set drag to normal |
||
510 | |||
511 | const auto slot = obj.ctype.ai_info.REMOTE_SLOT_NUM; |
||
512 | robot_last_send_time[slot] = GameTime64; |
||
513 | robot_send_pending[slot] = 1+force; |
||
514 | return; |
||
515 | } |
||
516 | |||
517 | void multi_send_robot_fire(const vmobjptridx_t obj, int gun_num, const vms_vector &fire) |
||
518 | { |
||
519 | multi_command<MULTI_ROBOT_FIRE> multibuf; |
||
520 | // Send robot fire event |
||
521 | int loc = 0; |
||
522 | short s; |
||
523 | |||
524 | loc += 1; |
||
525 | multibuf[loc] = Player_num; loc += 1; |
||
526 | s = objnum_local_to_remote(obj, reinterpret_cast<int8_t *>(&multibuf[loc+2])); |
||
527 | PUT_INTEL_SHORT(&multibuf[loc], s); |
||
528 | loc += 3; |
||
529 | multibuf[loc] = gun_num; loc += 1; |
||
530 | if constexpr (words_bigendian) |
||
531 | { |
||
532 | vms_vector swapped_vec; |
||
533 | swapped_vec.x = INTEL_INT(static_cast<int>(fire.x)); |
||
534 | swapped_vec.y = INTEL_INT(static_cast<int>(fire.y)); |
||
535 | swapped_vec.z = INTEL_INT(static_cast<int>(fire.z)); |
||
536 | memcpy(&multibuf[loc], &swapped_vec, sizeof(vms_vector)); |
||
537 | } |
||
538 | else |
||
539 | { |
||
540 | memcpy(&multibuf[loc], &fire, sizeof(vms_vector)); |
||
541 | } |
||
542 | loc += sizeof(vms_vector); // 12 |
||
543 | // -------------------------- |
||
544 | // Total = 18 |
||
545 | |||
546 | if (obj->ctype.ai_info.REMOTE_OWNER == Player_num) |
||
547 | { |
||
548 | int slot = obj->ctype.ai_info.REMOTE_SLOT_NUM; |
||
549 | if (slot<0 || slot>=MAX_ROBOTS_CONTROLLED) |
||
550 | { |
||
551 | return; |
||
552 | } |
||
553 | if (robot_fired[slot] != 0) |
||
554 | { |
||
555 | // Int3(); // ROB! |
||
556 | return; |
||
557 | } |
||
558 | memcpy(robot_fire_buf[slot], multibuf.data(), loc); |
||
559 | robot_fired[slot] = 1; |
||
560 | } |
||
561 | else |
||
562 | multi_send_data(multibuf, 1); // Not our robot, send ASAP |
||
563 | } |
||
564 | |||
565 | struct multi_explode_robot |
||
566 | { |
||
567 | int16_t robj_killer, robj_killed; |
||
568 | int8_t owner_killer, owner_killed; |
||
569 | }; |
||
570 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_ROBOT_EXPLODE, multi_explode_robot, b, (b.robj_killer, b.robj_killed, b.owner_killer, b.owner_killed)); |
||
571 | |||
572 | void multi_send_robot_explode(const imobjptridx_t objnum, objnum_t killer) |
||
573 | { |
||
574 | // Send robot explosion event to the other players |
||
575 | const auto k = objnum_local_to_remote(killer); |
||
576 | multi_explode_robot e; |
||
577 | e.robj_killer = k.objnum; |
||
578 | e.owner_killer = k.owner; |
||
579 | const auto d = objnum_local_to_remote(objnum); |
||
580 | e.robj_killed = d.objnum; |
||
581 | e.owner_killed = d.owner; |
||
582 | multi_serialize_write(2, e); |
||
583 | |||
584 | multi_delete_controlled_robot(objnum); |
||
585 | } |
||
586 | |||
587 | void multi_send_create_robot(int station, objnum_t objnum, int type) |
||
588 | { |
||
589 | multi_command<MULTI_CREATE_ROBOT> multibuf; |
||
590 | // Send create robot information |
||
591 | |||
592 | int loc = 0; |
||
593 | |||
594 | loc += 1; |
||
595 | multibuf[loc] = Player_num; loc += 1; |
||
596 | multibuf[loc] = static_cast<int8_t>(station); loc += 1; |
||
597 | PUT_INTEL_SHORT(&multibuf[loc], objnum); loc += 2; |
||
598 | multibuf[loc] = type; loc += 1; |
||
599 | |||
600 | map_objnum_local_to_local(objnum); |
||
601 | |||
602 | multi_send_data(multibuf, 2); |
||
603 | } |
||
604 | |||
605 | namespace { |
||
606 | |||
607 | struct boss_teleport |
||
608 | { |
||
609 | objnum_t objnum; |
||
610 | segnum_t where; |
||
611 | }; |
||
612 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_TELEPORT, boss_teleport, b, (b.objnum, b.where)); |
||
613 | |||
614 | struct boss_cloak |
||
615 | { |
||
616 | objnum_t objnum; |
||
617 | }; |
||
618 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_CLOAK, boss_cloak, b, (b.objnum)); |
||
619 | |||
620 | struct boss_start_gate |
||
621 | { |
||
622 | objnum_t objnum; |
||
623 | }; |
||
624 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_START_GATE, boss_start_gate, b, (b.objnum)); |
||
625 | |||
626 | struct boss_stop_gate |
||
627 | { |
||
628 | objnum_t objnum; |
||
629 | }; |
||
630 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_STOP_GATE, boss_stop_gate, b, (b.objnum)); |
||
631 | |||
632 | struct boss_create_robot |
||
633 | { |
||
634 | objnum_t objnum; |
||
635 | objnum_t objrobot; |
||
636 | segnum_t where; |
||
637 | uint8_t robot_type; |
||
638 | }; |
||
639 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_CREATE_ROBOT, boss_create_robot, b, (b.objnum, b.objrobot, b.where, b.robot_type)); |
||
640 | |||
641 | #if defined(DXX_BUILD_DESCENT_II) |
||
642 | struct update_buddy_state |
||
643 | { |
||
644 | uint8_t Looking_for_marker; |
||
645 | escort_goal_t Escort_special_goal; |
||
646 | int Last_buddy_key; |
||
647 | }; |
||
648 | DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_UPDATE_BUDDY_STATE, update_buddy_state, b, (b.Looking_for_marker, b.Escort_special_goal, b.Last_buddy_key)); |
||
649 | #endif |
||
650 | |||
651 | } |
||
652 | |||
653 | template <typename T, typename... Args> |
||
654 | static inline void multi_send_boss_action(objnum_t bossobjnum, Args&&... args) |
||
655 | { |
||
656 | multi_serialize_write(2, T{bossobjnum, std::forward<Args>(args)...}); |
||
657 | } |
||
658 | |||
659 | namespace dsx { |
||
660 | void multi_send_boss_teleport(const vmobjptridx_t bossobj, const vcsegidx_t where) |
||
661 | { |
||
662 | // Boss is up for grabs after teleporting |
||
663 | Assert((bossobj->ctype.ai_info.REMOTE_SLOT_NUM >= 0) && (bossobj->ctype.ai_info.REMOTE_SLOT_NUM < MAX_ROBOTS_CONTROLLED)); |
||
664 | multi_delete_controlled_robot(bossobj); |
||
665 | #if defined(DXX_BUILD_DESCENT_I) |
||
666 | bossobj->ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD; // Hands-off period! |
||
667 | #endif |
||
668 | multi_send_boss_action<boss_teleport>(bossobj, where); |
||
669 | } |
||
670 | } |
||
671 | |||
672 | void multi_send_boss_cloak(objnum_t bossobjnum) |
||
673 | { |
||
674 | multi_send_boss_action<boss_cloak>(bossobjnum); |
||
675 | } |
||
676 | |||
677 | void multi_send_boss_start_gate(objnum_t bossobjnum) |
||
678 | { |
||
679 | multi_send_boss_action<boss_start_gate>(bossobjnum); |
||
680 | } |
||
681 | |||
682 | void multi_send_boss_stop_gate(objnum_t bossobjnum) |
||
683 | { |
||
684 | multi_send_boss_action<boss_stop_gate>(bossobjnum); |
||
685 | } |
||
686 | |||
687 | void multi_send_boss_create_robot(vmobjidx_t bossobjnum, const vmobjptridx_t objrobot) |
||
688 | { |
||
689 | map_objnum_local_to_local(objrobot); |
||
690 | multi_send_boss_action<boss_create_robot>(bossobjnum, objrobot, objrobot->segnum, get_robot_id(objrobot)); |
||
691 | } |
||
692 | |||
693 | #define MAX_ROBOT_POWERUPS 4 |
||
694 | |||
695 | namespace dsx { |
||
696 | |||
697 | static void multi_send_create_robot_powerups(const object_base &del_obj) |
||
698 | { |
||
699 | multi_command<MULTI_CREATE_ROBOT_POWERUPS> multibuf; |
||
700 | // Send create robot information |
||
701 | |||
702 | int loc = 0; |
||
703 | |||
704 | loc += 1; |
||
705 | multibuf[loc] = Player_num; loc += 1; |
||
706 | multibuf[loc] = del_obj.contains_count; loc += 1; |
||
707 | multibuf[loc] = del_obj.contains_type; loc += 1; |
||
708 | multibuf[loc] = del_obj.contains_id; loc += 1; |
||
709 | PUT_INTEL_SHORT(&multibuf[loc], del_obj.segnum); loc += 2; |
||
710 | if constexpr (words_bigendian) |
||
711 | { |
||
712 | vms_vector swapped_vec; |
||
713 | swapped_vec.x = INTEL_INT(static_cast<int>(del_obj.pos.x)); |
||
714 | swapped_vec.y = INTEL_INT(static_cast<int>(del_obj.pos.y)); |
||
715 | swapped_vec.z = INTEL_INT(static_cast<int>(del_obj.pos.z)); |
||
716 | memcpy(&multibuf[loc], &swapped_vec, sizeof(vms_vector)); |
||
717 | loc += 12; |
||
718 | } |
||
719 | else |
||
720 | { |
||
721 | memcpy(&multibuf[loc], &del_obj.pos, sizeof(vms_vector)); |
||
722 | loc += 12; |
||
723 | } |
||
724 | |||
725 | memset(&multibuf[loc], -1, MAX_ROBOT_POWERUPS*sizeof(short)); |
||
726 | #if defined(DXX_BUILD_DESCENT_II) |
||
727 | if (del_obj.contains_count != Net_create_loc) |
||
728 | Int3(); //Get Jason, a bad thing happened |
||
729 | #endif |
||
730 | |||
731 | if ((Net_create_loc > MAX_ROBOT_POWERUPS) || (Net_create_loc < 1)) |
||
732 | { |
||
733 | Int3(); // See Rob |
||
734 | } |
||
735 | range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc)) |
||
736 | { |
||
737 | PUT_INTEL_SHORT(&multibuf[loc], i); |
||
738 | loc += 2; |
||
739 | map_objnum_local_to_local(i); |
||
740 | } |
||
741 | |||
742 | Net_create_loc = 0; |
||
743 | |||
744 | multi_send_data(multibuf, 2); |
||
745 | } |
||
746 | } |
||
747 | |||
748 | void multi_do_claim_robot(const playernum_t pnum, const ubyte *buf) |
||
749 | { |
||
750 | auto &Objects = LevelUniqueObjectState.Objects; |
||
751 | auto &vmobjptridx = Objects.vmptridx; |
||
752 | multi_claim_robot b; |
||
753 | multi_serialize_read(buf, b); |
||
754 | auto botnum = objnum_remote_to_local(b.robjnum, b.owner); |
||
755 | if (botnum > Highest_object_index) |
||
756 | { |
||
757 | return; |
||
758 | } |
||
759 | |||
760 | const auto &&botp = vmobjptridx(botnum); |
||
761 | if (botp->type != OBJ_ROBOT) |
||
762 | { |
||
763 | return; |
||
764 | } |
||
765 | |||
766 | if (botp->ctype.ai_info.REMOTE_OWNER != -1) |
||
767 | { |
||
768 | if (MULTI_ROBOT_PRIORITY(b.robjnum, pnum) <= MULTI_ROBOT_PRIORITY(b.robjnum, botp->ctype.ai_info.REMOTE_OWNER)) |
||
769 | return; |
||
770 | } |
||
771 | |||
772 | // Perform the requested change |
||
773 | |||
774 | if (botp->ctype.ai_info.REMOTE_OWNER == Player_num) |
||
775 | { |
||
776 | multi_delete_controlled_robot(botp); |
||
777 | } |
||
778 | |||
779 | botp->ctype.ai_info.REMOTE_OWNER = pnum; |
||
780 | botp->ctype.ai_info.REMOTE_SLOT_NUM = 0; |
||
781 | } |
||
782 | |||
783 | void multi_do_release_robot(const playernum_t pnum, const ubyte *buf) |
||
784 | { |
||
785 | auto &Objects = LevelUniqueObjectState.Objects; |
||
786 | auto &vmobjptr = Objects.vmptr; |
||
787 | short remote_botnum; |
||
788 | |||
789 | remote_botnum = GET_INTEL_SHORT(buf + 2); |
||
790 | auto botnum = objnum_remote_to_local(remote_botnum, buf[4]); |
||
791 | |||
792 | if (botnum > Highest_object_index) |
||
793 | { |
||
794 | return; |
||
795 | } |
||
796 | |||
797 | const auto &&botp = vmobjptr(botnum); |
||
798 | if (botp->type != OBJ_ROBOT) |
||
799 | { |
||
800 | return; |
||
801 | } |
||
802 | |||
803 | if (botp->ctype.ai_info.REMOTE_OWNER != pnum) |
||
804 | { |
||
805 | return; |
||
806 | } |
||
807 | |||
808 | // Perform the requested change |
||
809 | |||
810 | botp->ctype.ai_info.REMOTE_OWNER = -1; |
||
811 | botp->ctype.ai_info.REMOTE_SLOT_NUM = 0; |
||
812 | } |
||
813 | |||
814 | void multi_do_robot_position(const playernum_t pnum, const ubyte *buf) |
||
815 | { |
||
816 | auto &Objects = LevelUniqueObjectState.Objects; |
||
817 | auto &vmobjptridx = Objects.vmptridx; |
||
818 | // Process robot movement sent by another player |
||
819 | |||
820 | short remote_botnum; |
||
821 | int loc = 1; |
||
822 | |||
823 | ; loc += 1; |
||
824 | |||
825 | remote_botnum = GET_INTEL_SHORT(buf + loc); |
||
826 | auto botnum = objnum_remote_to_local(remote_botnum, buf[loc+2]); loc += 3; |
||
827 | |||
828 | if (botnum > Highest_object_index) |
||
829 | { |
||
830 | return; |
||
831 | } |
||
832 | |||
833 | const auto robot = vmobjptridx(botnum); |
||
834 | |||
835 | if ((robot->type != OBJ_ROBOT) || (robot->flags & OF_EXPLODING)) { |
||
836 | return; |
||
837 | } |
||
838 | |||
839 | if (robot->ctype.ai_info.REMOTE_OWNER != pnum) |
||
840 | { |
||
841 | if (robot->ctype.ai_info.REMOTE_OWNER == -1) |
||
842 | { |
||
843 | // Robot claim packet must have gotten lost, let this player claim it. |
||
844 | if (robot->ctype.ai_info.REMOTE_SLOT_NUM >= MAX_ROBOTS_CONTROLLED) { // == HANDS_OFF_PERIOD should do the same trick |
||
845 | robot->ctype.ai_info.REMOTE_OWNER = pnum; |
||
846 | robot->ctype.ai_info.REMOTE_SLOT_NUM = 0; |
||
847 | } |
||
848 | else |
||
849 | robot->ctype.ai_info.REMOTE_SLOT_NUM++; |
||
850 | } |
||
851 | else |
||
852 | { |
||
853 | return; |
||
854 | } |
||
855 | } |
||
856 | |||
857 | set_thrust_from_velocity(robot); // Try to smooth out movement |
||
858 | // Objects[botnum].phys_info.drag = Robot_info[Objects[botnum].id].drag >> 4; // Set drag to low |
||
859 | |||
860 | quaternionpos qpp{}; |
||
861 | qpp.orient.w = GET_INTEL_SHORT(&buf[loc]); loc += 2; |
||
862 | qpp.orient.x = GET_INTEL_SHORT(&buf[loc]); loc += 2; |
||
863 | qpp.orient.y = GET_INTEL_SHORT(&buf[loc]); loc += 2; |
||
864 | qpp.orient.z = GET_INTEL_SHORT(&buf[loc]); loc += 2; |
||
865 | qpp.pos.x = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
866 | qpp.pos.y = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
867 | qpp.pos.z = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
868 | qpp.segment = GET_INTEL_SHORT(&buf[loc]); loc += 2; |
||
869 | qpp.vel.x = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
870 | qpp.vel.y = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
871 | qpp.vel.z = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
872 | qpp.rotvel.x = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
873 | qpp.rotvel.y = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
874 | qpp.rotvel.z = GET_INTEL_INT(&buf[loc]); loc += 4; |
||
875 | extract_quaternionpos(robot, qpp); |
||
876 | } |
||
877 | |||
878 | static inline vms_vector calc_gun_point(const object_base &obj, unsigned gun_num) |
||
879 | { |
||
880 | vms_vector v; |
||
881 | return calc_gun_point(v, obj, gun_num), v; |
||
882 | } |
||
883 | |||
884 | namespace dsx { |
||
885 | void multi_do_robot_fire(const uint8_t *const buf) |
||
886 | { |
||
887 | auto &Objects = LevelUniqueObjectState.Objects; |
||
888 | auto &vmobjptridx = Objects.vmptridx; |
||
889 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
890 | // Send robot fire event |
||
891 | int loc = 1; |
||
892 | short remote_botnum; |
||
893 | int gun_num; |
||
894 | vms_vector fire; |
||
895 | loc += 1; // pnum |
||
896 | remote_botnum = GET_INTEL_SHORT(buf + loc); |
||
897 | auto botnum = objnum_remote_to_local(remote_botnum, buf[loc+2]); loc += 3; |
||
898 | gun_num = static_cast<int8_t>(buf[loc]); loc += 1; |
||
899 | memcpy(&fire, buf+loc, sizeof(vms_vector)); |
||
900 | fire.x = INTEL_INT(fire.x); |
||
901 | fire.y = INTEL_INT(fire.y); |
||
902 | fire.z = INTEL_INT(fire.z); |
||
903 | |||
904 | if (botnum > Highest_object_index) |
||
905 | return; |
||
906 | |||
907 | auto botp = vmobjptridx(botnum); |
||
908 | if (botp->type != OBJ_ROBOT || botp->flags & OF_EXPLODING) |
||
909 | return; |
||
910 | |||
911 | using pt_weapon = std::pair<vms_vector, weapon_id_type>; |
||
912 | const pt_weapon pw = |
||
913 | // Do the firing |
||
914 | (gun_num == -1 |
||
915 | #if defined(DXX_BUILD_DESCENT_II) |
||
916 | || gun_num==-2 |
||
917 | #endif |
||
918 | ) |
||
919 | ? pt_weapon(vm_vec_add(botp->pos, fire), |
||
920 | #if defined(DXX_BUILD_DESCENT_II) |
||
921 | gun_num != -1 ? weapon_id_type::SUPERPROX_ID : |
||
922 | #endif |
||
923 | weapon_id_type::PROXIMITY_ID) |
||
924 | : pt_weapon(calc_gun_point(botp, gun_num), get_robot_weapon(Robot_info[get_robot_id(botp)], 1)); |
||
925 | Laser_create_new_easy(fire, pw.first, botp, pw.second, 1); |
||
926 | } |
||
927 | } |
||
928 | |||
929 | namespace dsx { |
||
930 | int multi_explode_robot_sub(const vmobjptridx_t robot) |
||
931 | { |
||
932 | auto &BossUniqueState = LevelUniqueObjectState.BossState; |
||
933 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
934 | if (robot->type != OBJ_ROBOT) { // Object is robot? |
||
935 | return 0; |
||
936 | } |
||
937 | |||
938 | if (robot->flags & OF_EXPLODING) { // Object not already exploding |
||
939 | return 0; |
||
940 | } |
||
941 | |||
942 | // Data seems valid, explode the sucker |
||
943 | |||
944 | if (Network_send_objects && multi_objnum_is_past(robot)) |
||
945 | { |
||
946 | Network_send_objnum = -1; |
||
947 | } |
||
948 | |||
949 | // Drop non-random KEY powerups locally only! |
||
950 | if ((robot->contains_count > 0) && (robot->contains_type == OBJ_POWERUP) && (Game_mode & GM_MULTI_COOP) && (robot->contains_id >= POW_KEY_BLUE) && (robot->contains_id <= POW_KEY_GOLD)) |
||
951 | { |
||
952 | object_create_robot_egg(robot); |
||
953 | } |
||
954 | else if (robot->ctype.ai_info.REMOTE_OWNER == Player_num) |
||
955 | { |
||
956 | multi_drop_robot_powerups(robot); |
||
957 | multi_delete_controlled_robot(robot); |
||
958 | } |
||
959 | else if (robot->ctype.ai_info.REMOTE_OWNER == -1 && multi_i_am_master()) |
||
960 | { |
||
961 | multi_drop_robot_powerups(robot); |
||
962 | } |
||
963 | if (robot_is_thief(Robot_info[get_robot_id(robot)])) |
||
964 | drop_stolen_items(robot); |
||
965 | |||
966 | if (Robot_info[get_robot_id(robot)].boss_flag) { |
||
967 | if (!BossUniqueState.Boss_dying) |
||
968 | start_boss_death_sequence(robot); |
||
969 | else |
||
970 | return (0); |
||
971 | } |
||
972 | #if defined(DXX_BUILD_DESCENT_II) |
||
973 | else if (Robot_info[get_robot_id(robot)].death_roll) { |
||
974 | start_robot_death_sequence(robot); |
||
975 | } |
||
976 | #endif |
||
977 | else |
||
978 | { |
||
979 | #if defined(DXX_BUILD_DESCENT_II) |
||
980 | const auto robot_id = get_robot_id(robot); |
||
981 | if (robot_id == SPECIAL_REACTOR_ROBOT) |
||
982 | special_reactor_stuff(); |
||
983 | if (Robot_info[robot_id].kamikaze) |
||
984 | explode_object(robot,1); // Kamikaze, explode right away, IN YOUR FACE! |
||
985 | else |
||
986 | #endif |
||
987 | explode_object(robot,STANDARD_EXPL_DELAY); |
||
988 | } |
||
989 | |||
990 | return 1; |
||
991 | } |
||
992 | } |
||
993 | |||
994 | void multi_do_robot_explode(const uint8_t *const buf) |
||
995 | { |
||
996 | auto &Objects = LevelUniqueObjectState.Objects; |
||
997 | auto &vmobjptridx = Objects.vmptridx; |
||
998 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
999 | multi_explode_robot b; |
||
1000 | multi_serialize_read(buf, b); |
||
1001 | auto killer = objnum_remote_to_local(b.robj_killer, b.owner_killer); |
||
1002 | auto botnum = objnum_remote_to_local(b.robj_killed, b.owner_killed); |
||
1003 | // Explode robot controlled by other player |
||
1004 | if (botnum > Highest_object_index) |
||
1005 | { |
||
1006 | return; |
||
1007 | } |
||
1008 | |||
1009 | const auto robot = vmobjptridx(botnum); |
||
1010 | const auto rval = multi_explode_robot_sub(robot); |
||
1011 | if (!rval) |
||
1012 | return; |
||
1013 | |||
1014 | ++ Players[0u].num_kills_level; |
||
1015 | ++ Players[0u].num_kills_total; |
||
1016 | if (killer == get_local_player().objnum) |
||
1017 | add_points_to_score(ConsoleObject->ctype.player_info, Robot_info[get_robot_id(robot)].score_value); |
||
1018 | } |
||
1019 | |||
1020 | namespace dsx { |
||
1021 | |||
1022 | void multi_do_create_robot(const d_vclip_array &Vclip, const playernum_t pnum, const ubyte *buf) |
||
1023 | { |
||
1024 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
1025 | auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState; |
||
1026 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
1027 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
1028 | const uint_fast32_t fuelcen_num = buf[2]; |
||
1029 | int type = buf[5]; |
||
1030 | |||
1031 | FuelCenter *robotcen; |
||
1032 | |||
1033 | objnum_t objnum; |
||
1034 | objnum = GET_INTEL_SHORT(buf + 3); |
||
1035 | |||
1036 | if (fuelcen_num >= LevelUniqueFuelcenterState.Num_fuelcenters || pnum >= N_players) |
||
1037 | { |
||
1038 | Int3(); // Bogus data |
||
1039 | return; |
||
1040 | } |
||
1041 | |||
1042 | robotcen = &Station[fuelcen_num]; |
||
1043 | |||
1044 | // Play effect and sound |
||
1045 | |||
1046 | const auto &&robotcen_segp = vmsegptridx(robotcen->segnum); |
||
1047 | auto &vcvertptr = Vertices.vcptr; |
||
1048 | const auto &&cur_object_loc = compute_segment_center(vcvertptr, robotcen_segp); |
||
1049 | if (const auto &&obj = object_create_explosion(robotcen_segp, cur_object_loc, i2f(10), VCLIP_MORPHING_ROBOT)) |
||
1050 | extract_orient_from_segment(vcvertptr, obj->orient, robotcen_segp); |
||
1051 | if (Vclip[VCLIP_MORPHING_ROBOT].sound_num > -1) |
||
1052 | digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, robotcen_segp, 0, cur_object_loc, 0, F1_0); |
||
1053 | |||
1054 | // Set robot center flags, in case we become the master for the next one |
||
1055 | |||
1056 | robotcen->Flag = 0; |
||
1057 | robotcen->Capacity -= EnergyToCreateOneRobot; |
||
1058 | robotcen->Timer = 0; |
||
1059 | |||
1060 | const auto &&obj = create_morph_robot(vmsegptridx(robotcen->segnum), cur_object_loc, type); |
||
1061 | if (obj == object_none) |
||
1062 | return; // Cannot create object! |
||
1063 | |||
1064 | obj->matcen_creator = fuelcen_num | 0x80; |
||
1065 | // extract_orient_from_segment(&obj->orient, &Segments[robotcen->segnum]); |
||
1066 | const auto direction = vm_vec_sub(ConsoleObject->pos, obj->pos ); |
||
1067 | vm_vector_2_matrix( obj->orient, direction, &obj->orient.uvec, nullptr); |
||
1068 | morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, obj); |
||
1069 | |||
1070 | map_objnum_local_to_remote(obj, objnum, pnum); |
||
1071 | |||
1072 | Assert(obj->ctype.ai_info.REMOTE_OWNER == -1); |
||
1073 | } |
||
1074 | |||
1075 | #if defined(DXX_BUILD_DESCENT_II) |
||
1076 | void multi_send_escort_goal(const d_unique_buddy_state &BuddyState) |
||
1077 | { |
||
1078 | update_buddy_state b; |
||
1079 | b.Looking_for_marker = static_cast<uint8_t>(BuddyState.Looking_for_marker); |
||
1080 | b.Escort_special_goal = BuddyState.Escort_special_goal; |
||
1081 | b.Last_buddy_key = BuddyState.Last_buddy_key; |
||
1082 | multi_serialize_write(2, b); |
||
1083 | } |
||
1084 | |||
1085 | void multi_recv_escort_goal(d_unique_buddy_state &BuddyState, const uint8_t *const buf) |
||
1086 | { |
||
1087 | update_buddy_state b; |
||
1088 | multi_serialize_read(buf, b); |
||
1089 | const auto Looking_for_marker = b.Looking_for_marker; |
||
1090 | BuddyState.Looking_for_marker = MarkerState.imobjidx.valid_index(Looking_for_marker) |
||
1091 | ? static_cast<game_marker_index>(Looking_for_marker) |
||
1092 | : game_marker_index::None; |
||
1093 | BuddyState.Escort_special_goal = b.Escort_special_goal; |
||
1094 | BuddyState.Last_buddy_key = b.Last_buddy_key; |
||
1095 | BuddyState.Buddy_messages_suppressed = 0; |
||
1096 | BuddyState.Last_buddy_message_time = GameTime64 - 2 * F1_0; |
||
1097 | BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; |
||
1098 | } |
||
1099 | #endif |
||
1100 | |||
1101 | void multi_do_boss_teleport(const d_vclip_array &Vclip, const playernum_t pnum, const ubyte *buf) |
||
1102 | { |
||
1103 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
1104 | auto &BossUniqueState = LevelUniqueObjectState.BossState; |
||
1105 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1106 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
1107 | auto &vcobjptr = Objects.vcptr; |
||
1108 | auto &vmobjptr = Objects.vmptr; |
||
1109 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1110 | boss_teleport b; |
||
1111 | multi_serialize_read(buf, b); |
||
1112 | const auto &&guarded_boss_obj = Objects.vmptridx.check_untrusted(b.objnum); |
||
1113 | if (!guarded_boss_obj) |
||
1114 | return; |
||
1115 | const auto &&boss_obj = *guarded_boss_obj; |
||
1116 | if ((boss_obj->type != OBJ_ROBOT) || !(Robot_info[get_robot_id(boss_obj)].boss_flag)) |
||
1117 | { |
||
1118 | Int3(); // Got boss actions for a robot who's not a boss? |
||
1119 | return; |
||
1120 | } |
||
1121 | const auto &&guarded_teleport_segnum = vmsegptridx.check_untrusted(b.where); |
||
1122 | if (!guarded_teleport_segnum) |
||
1123 | return; |
||
1124 | const auto &&teleport_segnum = *guarded_teleport_segnum; |
||
1125 | auto &vcvertptr = Vertices.vcptr; |
||
1126 | compute_segment_center(vcvertptr, boss_obj->pos, teleport_segnum); |
||
1127 | obj_relink(vmobjptr, vmsegptr, boss_obj, teleport_segnum); |
||
1128 | BossUniqueState.Last_teleport_time = GameTime64; |
||
1129 | |||
1130 | const auto boss_dir = vm_vec_sub(vcobjptr(vcplayerptr(pnum)->objnum)->pos, boss_obj->pos); |
||
1131 | vm_vector_2_matrix(boss_obj->orient, boss_dir, nullptr, nullptr); |
||
1132 | |||
1133 | digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, teleport_segnum, 0, boss_obj->pos, 0 , F1_0); |
||
1134 | digi_kill_sound_linked_to_object( boss_obj); |
||
1135 | boss_link_see_sound(boss_obj); |
||
1136 | ai_local *ailp = &boss_obj->ctype.ai_info.ail; |
||
1137 | ailp->next_fire = 0; |
||
1138 | |||
1139 | if (boss_obj->ctype.ai_info.REMOTE_OWNER == Player_num) |
||
1140 | { |
||
1141 | multi_delete_controlled_robot(boss_obj); |
||
1142 | } |
||
1143 | |||
1144 | boss_obj->ctype.ai_info.REMOTE_OWNER = -1; // Boss is up for grabs again! |
||
1145 | boss_obj->ctype.ai_info.REMOTE_SLOT_NUM = 0; // Available immediately! |
||
1146 | } |
||
1147 | |||
1148 | void multi_do_boss_cloak(const ubyte *buf) |
||
1149 | { |
||
1150 | auto &BossUniqueState = LevelUniqueObjectState.BossState; |
||
1151 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1152 | auto &vmobjptridx = Objects.vmptridx; |
||
1153 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1154 | boss_cloak b; |
||
1155 | multi_serialize_read(buf, b); |
||
1156 | const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum); |
||
1157 | if (!guarded_boss_obj) |
||
1158 | return; |
||
1159 | const auto &&boss_obj = *guarded_boss_obj; |
||
1160 | if (boss_obj->type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag) |
||
1161 | { |
||
1162 | Int3(); // Got boss actions for a robot who's not a boss? |
||
1163 | return; |
||
1164 | } |
||
1165 | BossUniqueState.Boss_hit_this_frame = 0; |
||
1166 | #if defined(DXX_BUILD_DESCENT_II) |
||
1167 | BossUniqueState.Boss_hit_time = -F1_0*10; |
||
1168 | #endif |
||
1169 | BossUniqueState.Boss_cloak_start_time = GameTime64; |
||
1170 | boss_obj->ctype.ai_info.CLOAKED = 1; |
||
1171 | } |
||
1172 | } |
||
1173 | |||
1174 | void multi_do_boss_start_gate(const ubyte *buf) |
||
1175 | { |
||
1176 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1177 | auto &vmobjptridx = Objects.vmptridx; |
||
1178 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1179 | boss_start_gate b; |
||
1180 | multi_serialize_read(buf, b); |
||
1181 | const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum); |
||
1182 | if (!guarded_boss_obj) |
||
1183 | return; |
||
1184 | const object_base &boss_obj = *guarded_boss_obj; |
||
1185 | if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag) |
||
1186 | { |
||
1187 | Int3(); // Got boss actions for a robot who's not a boss? |
||
1188 | return; |
||
1189 | } |
||
1190 | restart_effect(ECLIP_NUM_BOSS); |
||
1191 | } |
||
1192 | |||
1193 | void multi_do_boss_stop_gate(const ubyte *buf) |
||
1194 | { |
||
1195 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1196 | auto &vmobjptridx = Objects.vmptridx; |
||
1197 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1198 | boss_start_gate b; |
||
1199 | multi_serialize_read(buf, b); |
||
1200 | const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum); |
||
1201 | if (!guarded_boss_obj) |
||
1202 | return; |
||
1203 | const object_base &boss_obj = *guarded_boss_obj; |
||
1204 | if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag) |
||
1205 | { |
||
1206 | Int3(); // Got boss actions for a robot who's not a boss? |
||
1207 | return; |
||
1208 | } |
||
1209 | stop_effect(ECLIP_NUM_BOSS); |
||
1210 | } |
||
1211 | |||
1212 | void multi_do_boss_create_robot(const playernum_t pnum, const ubyte *buf) |
||
1213 | { |
||
1214 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1215 | auto &vmobjptridx = Objects.vmptridx; |
||
1216 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1217 | boss_create_robot b; |
||
1218 | multi_serialize_read(buf, b); |
||
1219 | const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum); |
||
1220 | if (!guarded_boss_obj) |
||
1221 | return; |
||
1222 | const object_base &boss_obj = *guarded_boss_obj; |
||
1223 | if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag) |
||
1224 | { |
||
1225 | Int3(); // Got boss actions for a robot who's not a boss? |
||
1226 | return; |
||
1227 | } |
||
1228 | // Do some validity checking |
||
1229 | if (b.objrobot >= MAX_OBJECTS) |
||
1230 | { |
||
1231 | Int3(); // See Rob, bad data in boss gate action message |
||
1232 | return; |
||
1233 | } |
||
1234 | // Gate one in! |
||
1235 | const auto &&robot = gate_in_robot(b.robot_type, vmsegptridx(b.where)); |
||
1236 | if (robot != object_none) |
||
1237 | map_objnum_local_to_remote(robot, b.objrobot, pnum); |
||
1238 | } |
||
1239 | |||
1240 | void multi_do_create_robot_powerups(const playernum_t pnum, const ubyte *buf) |
||
1241 | { |
||
1242 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1243 | auto &vmobjptr = Objects.vmptr; |
||
1244 | // Code to drop remote-controlled robot powerups |
||
1245 | |||
1246 | int loc = 1; |
||
1247 | ; loc += 1; |
||
1248 | uint8_t contains_count = buf[loc]; loc += 1; |
||
1249 | uint8_t contains_type = buf[loc]; loc += 1; |
||
1250 | uint8_t contains_id = buf[loc]; loc += 1; |
||
1251 | segnum_t segnum = GET_INTEL_SHORT(buf + loc); loc += 2; |
||
1252 | vms_vector pos; |
||
1253 | memcpy(&pos, &buf[loc], sizeof(pos)); loc += 12; |
||
1254 | |||
1255 | vms_vector velocity{}; |
||
1256 | pos.x = INTEL_INT(pos.x); |
||
1257 | pos.y = INTEL_INT(pos.y); |
||
1258 | pos.z = INTEL_INT(pos.z); |
||
1259 | |||
1260 | Assert(pnum < N_players); |
||
1261 | Assert (pnum!=Player_num); // What? How'd we send ourselves this? |
||
1262 | |||
1263 | Net_create_loc = 0; |
||
1264 | d_srand(1245L); |
||
1265 | |||
1266 | const auto &&egg_objnum = object_create_robot_egg(contains_type, contains_id, contains_count, velocity, pos, vmsegptridx(segnum)); |
||
1267 | |||
1268 | if (egg_objnum == object_none) |
||
1269 | return; // Object buffer full |
||
1270 | |||
1271 | // Assert(egg_objnum > -1); |
||
1272 | Assert((Net_create_loc > 0) && (Net_create_loc <= MAX_ROBOT_POWERUPS)); |
||
1273 | |||
1274 | range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc)) |
||
1275 | { |
||
1276 | short s; |
||
1277 | |||
1278 | s = GET_INTEL_SHORT(buf + loc); |
||
1279 | if ( s != -1) |
||
1280 | map_objnum_local_to_remote(i, s, pnum); |
||
1281 | else |
||
1282 | vmobjptr(i)->flags |= OF_SHOULD_BE_DEAD; // Delete objects other guy didn't create one of |
||
1283 | loc += 2; |
||
1284 | } |
||
1285 | } |
||
1286 | |||
1287 | void multi_drop_robot_powerups(const vmobjptr_t del_obj) |
||
1288 | { |
||
1289 | // Code to handle dropped robot powerups in network mode ONLY! |
||
1290 | |||
1291 | objnum_t egg_objnum = object_none; |
||
1292 | |||
1293 | if (del_obj->type != OBJ_ROBOT) |
||
1294 | { |
||
1295 | Int3(); // dropping powerups for non-robot, Rob's fault |
||
1296 | return; |
||
1297 | } |
||
1298 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1299 | auto &robptr = Robot_info[get_robot_id(del_obj)]; |
||
1300 | |||
1301 | Net_create_loc = 0; |
||
1302 | |||
1303 | if (del_obj->contains_count > 0) { |
||
1304 | // If dropping a weapon that the player has, drop energy instead, unless it's vulcan, in which case drop vulcan ammo. |
||
1305 | if (del_obj->contains_type == OBJ_POWERUP) { |
||
1306 | maybe_replace_powerup_with_energy(del_obj); |
||
1307 | if (!multi_powerup_is_allowed(del_obj->contains_id, Netgame.AllowedItems)) |
||
1308 | del_obj->contains_id=POW_SHIELD_BOOST; |
||
1309 | |||
1310 | // No key drops in non-coop games! |
||
1311 | if (!(Game_mode & GM_MULTI_COOP)) { |
||
1312 | if ((del_obj->contains_id >= POW_KEY_BLUE) && (del_obj->contains_id <= POW_KEY_GOLD)) |
||
1313 | del_obj->contains_count = 0; |
||
1314 | } |
||
1315 | } |
||
1316 | d_srand(1245L); |
||
1317 | if (del_obj->contains_count > 0) |
||
1318 | egg_objnum = object_create_robot_egg(del_obj); |
||
1319 | } |
||
1320 | |||
1321 | else if (del_obj->ctype.ai_info.REMOTE_OWNER == -1) // No random goodies for robots we weren't in control of |
||
1322 | return; |
||
1323 | |||
1324 | else if (robptr.contains_count) { |
||
1325 | d_srand(static_cast<fix>(timer_query())); |
||
1326 | if (((d_rand() * 16) >> 15) < robptr.contains_prob) { |
||
1327 | del_obj->contains_count = ((d_rand() * robptr.contains_count) >> 15) + 1; |
||
1328 | del_obj->contains_type = robptr.contains_type; |
||
1329 | del_obj->contains_id = robptr.contains_id; |
||
1330 | if (del_obj->contains_type == OBJ_POWERUP) |
||
1331 | { |
||
1332 | maybe_replace_powerup_with_energy(del_obj); |
||
1333 | if (!multi_powerup_is_allowed(del_obj->contains_id, Netgame.AllowedItems)) |
||
1334 | del_obj->contains_id=POW_SHIELD_BOOST; |
||
1335 | } |
||
1336 | |||
1337 | d_srand(1245L); |
||
1338 | if (del_obj->contains_count > 0) |
||
1339 | egg_objnum = object_create_robot_egg(del_obj); |
||
1340 | } |
||
1341 | } |
||
1342 | |||
1343 | if (egg_objnum != object_none) { |
||
1344 | // Transmit the object creation to the other players |
||
1345 | multi_send_create_robot_powerups(del_obj); |
||
1346 | } |
||
1347 | } |
||
1348 | |||
1349 | // ----------------------------------------------------------------------------- |
||
1350 | // Robot *robot got whacked by player player_num and requests permission to do something about it. |
||
1351 | // Note: This function will be called regardless of whether Game_mode is a multiplayer mode, so it |
||
1352 | // should quick-out if not in a multiplayer mode. On the other hand, it only gets called when a |
||
1353 | // player or player weapon whacks a robot, so it happens rarely. |
||
1354 | namespace dsx { |
||
1355 | void multi_robot_request_change(const vmobjptridx_t robot, int player_num) |
||
1356 | { |
||
1357 | int remote_objnum; |
||
1358 | sbyte dummy; |
||
1359 | |||
1360 | if (!(Game_mode & GM_MULTI_ROBOTS)) |
||
1361 | return; |
||
1362 | #if defined(DXX_BUILD_DESCENT_I) |
||
1363 | if (robot->ctype.ai_info.REMOTE_OWNER != Player_num) |
||
1364 | return; |
||
1365 | #endif |
||
1366 | |||
1367 | const auto slot = robot->ctype.ai_info.REMOTE_SLOT_NUM; |
||
1368 | |||
1369 | if (slot == HANDS_OFF_PERIOD) |
||
1370 | { |
||
1371 | con_printf(CON_DEBUG, "Suppressing debugger trap for hands off robot %hu with player %i", static_cast<vmobjptridx_t::integral_type>(robot), player_num); |
||
1372 | return; |
||
1373 | } |
||
1374 | if ((slot < 0) || (slot >= MAX_ROBOTS_CONTROLLED)) { |
||
1375 | Int3(); |
||
1376 | return; |
||
1377 | } |
||
1378 | if (robot_controlled[slot] == object_none) |
||
1379 | return; |
||
1380 | const auto &&rcrobot = robot.absolute_sibling(robot_controlled[slot]); |
||
1381 | remote_objnum = objnum_local_to_remote(robot, &dummy); |
||
1382 | if (remote_objnum < 0) |
||
1383 | return; |
||
1384 | |||
1385 | if ( (robot_agitation[slot] < 70) || (MULTI_ROBOT_PRIORITY(remote_objnum, player_num) > MULTI_ROBOT_PRIORITY(remote_objnum, Player_num)) || (d_rand() > 0x4400)) |
||
1386 | { |
||
1387 | if (robot_send_pending[slot]) |
||
1388 | multi_send_robot_position(rcrobot, -1); |
||
1389 | multi_send_release_robot(rcrobot); |
||
1390 | robot->ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD; // Hands-off period |
||
1391 | } |
||
1392 | } |
||
1393 | |||
1394 | } |