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 | * Functions for refueling centers. |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include <stdio.h> |
||
27 | #include <stdlib.h> |
||
28 | #include <math.h> |
||
29 | #include <string.h> |
||
30 | |||
31 | #include "fuelcen.h" |
||
32 | #include "gameseg.h" |
||
33 | #include "game.h" // For FrameTime |
||
34 | #include "dxxerror.h" |
||
35 | #include "gauges.h" |
||
36 | #include "vclip.h" |
||
37 | #include "fireball.h" |
||
38 | #include "robot.h" |
||
39 | #include "powerup.h" |
||
40 | |||
41 | #include "sounds.h" |
||
42 | #include "morph.h" |
||
43 | #include "3d.h" |
||
44 | #include "physfs-serial.h" |
||
45 | #include "bm.h" |
||
46 | #include "polyobj.h" |
||
47 | #include "ai.h" |
||
48 | #include "gamemine.h" |
||
49 | #include "gamesave.h" |
||
50 | #include "player.h" |
||
51 | #include "collide.h" |
||
52 | #include "laser.h" |
||
53 | #include "multi.h" |
||
54 | #include "multibot.h" |
||
55 | #include "escort.h" |
||
56 | #include "compiler-poison.h" |
||
57 | #include "d_enumerate.h" |
||
58 | #include "compiler-range_for.h" |
||
59 | #include "partial_range.h" |
||
60 | #include "segiter.h" |
||
61 | |||
62 | // The max number of fuel stations per mine. |
||
63 | |||
64 | namespace dcx { |
||
65 | constexpr fix Fuelcen_give_amount = i2f(25); |
||
66 | constexpr fix Fuelcen_max_amount = i2f(100); |
||
67 | |||
68 | // Every time a robot is created in the morphing code, it decreases capacity of the morpher |
||
69 | // by this amount... when capacity gets to 0, no more morphers... |
||
70 | constexpr fix EnergyToCreateOneRobot = i2f(1); |
||
71 | |||
72 | static int Num_extry_robots = 15; |
||
73 | } |
||
74 | namespace dsx { |
||
75 | #if DXX_USE_EDITOR |
||
76 | const char Special_names[MAX_CENTER_TYPES][11] = { |
||
77 | "NOTHING ", |
||
78 | "FUELCEN ", |
||
79 | "REPAIRCEN ", |
||
80 | "CONTROLCEN", |
||
81 | "ROBOTMAKER", |
||
82 | #if defined(DXX_BUILD_DESCENT_II) |
||
83 | "GOAL_RED", |
||
84 | "GOAL_BLUE", |
||
85 | #endif |
||
86 | }; |
||
87 | #endif |
||
88 | |||
89 | //------------------------------------------------------------ |
||
90 | // Resets all fuel center info |
||
91 | void fuelcen_reset() |
||
92 | { |
||
93 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
94 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
95 | DXX_MAKE_MEM_UNDEFINED(Station.begin(), Station.end()); |
||
96 | DXX_MAKE_MEM_UNDEFINED(RobotCenters.begin(), RobotCenters.end()); |
||
97 | LevelSharedRobotcenterState.Num_robot_centers = 0; |
||
98 | LevelUniqueFuelcenterState.Num_fuelcenters = 0; |
||
99 | range_for (shared_segment &i, Segments) |
||
100 | i.special = SEGMENT_IS_NOTHING; |
||
101 | } |
||
102 | |||
103 | #ifndef NDEBUG //this is sometimes called by people from the debugger |
||
104 | static void reset_all_robot_centers() __attribute_used; |
||
105 | static void reset_all_robot_centers() |
||
106 | { |
||
107 | // Remove all materialization centers |
||
108 | range_for (shared_segment &i, partial_range(Segments, LevelSharedSegmentState.Num_segments)) |
||
109 | if (i.special == SEGMENT_IS_ROBOTMAKER) |
||
110 | { |
||
111 | i.special = SEGMENT_IS_NOTHING; |
||
112 | i.matcen_num = -1; |
||
113 | } |
||
114 | } |
||
115 | #endif |
||
116 | |||
117 | //------------------------------------------------------------ |
||
118 | // Turns a segment into a fully charged up fuel center... |
||
119 | void fuelcen_create(const vmsegptridx_t segp) |
||
120 | { |
||
121 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
122 | int station_type; |
||
123 | |||
124 | station_type = segp->special; |
||
125 | |||
126 | switch( station_type ) { |
||
127 | case SEGMENT_IS_NOTHING: |
||
128 | #if defined(DXX_BUILD_DESCENT_II) |
||
129 | case SEGMENT_IS_GOAL_BLUE: |
||
130 | case SEGMENT_IS_GOAL_RED: |
||
131 | #endif |
||
132 | return; |
||
133 | case SEGMENT_IS_FUELCEN: |
||
134 | case SEGMENT_IS_REPAIRCEN: |
||
135 | case SEGMENT_IS_CONTROLCEN: |
||
136 | case SEGMENT_IS_ROBOTMAKER: |
||
137 | break; |
||
138 | default: |
||
139 | Error( "Invalid station type %d in fuelcen.c\n", station_type ); |
||
140 | } |
||
141 | |||
142 | const auto next_fuelcenter_idx = LevelUniqueFuelcenterState.Num_fuelcenters++; |
||
143 | segp->station_idx = next_fuelcenter_idx; |
||
144 | auto &station = Station.at(next_fuelcenter_idx); |
||
145 | station.Type = station_type; |
||
146 | station.Capacity = Fuelcen_max_amount; |
||
147 | station.segnum = segp; |
||
148 | station.Timer = -1; |
||
149 | station.Flag = 0; |
||
150 | } |
||
151 | |||
152 | //------------------------------------------------------------ |
||
153 | // Adds a matcen that already is a special type into the Station array. |
||
154 | // This function is separate from other fuelcens because we don't want values reset. |
||
155 | static void matcen_create(const vmsegptridx_t segp) |
||
156 | { |
||
157 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
158 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
159 | int station_type = segp->special; |
||
160 | |||
161 | Assert(station_type == SEGMENT_IS_ROBOTMAKER); |
||
162 | |||
163 | const auto next_fuelcenter_idx = LevelUniqueFuelcenterState.Num_fuelcenters++; |
||
164 | segp->station_idx = next_fuelcenter_idx; |
||
165 | auto &station = Station.at(next_fuelcenter_idx); |
||
166 | |||
167 | station.Type = station_type; |
||
168 | station.Capacity = i2f(GameUniqueState.Difficulty_level + 3); |
||
169 | station.segnum = segp; |
||
170 | station.Timer = -1; |
||
171 | station.Flag = 0; |
||
172 | |||
173 | const auto next_robot_center_idx = LevelSharedRobotcenterState.Num_robot_centers++; |
||
174 | segp->matcen_num = next_robot_center_idx; |
||
175 | auto &robotcenter = RobotCenters[next_robot_center_idx]; |
||
176 | robotcenter.fuelcen_num = next_fuelcenter_idx; |
||
177 | robotcenter.segnum = segp; |
||
178 | robotcenter.fuelcen_num = next_fuelcenter_idx; |
||
179 | } |
||
180 | |||
181 | //------------------------------------------------------------ |
||
182 | // Adds a segment that already is a special type into the Station array. |
||
183 | void fuelcen_activate(const vmsegptridx_t segp) |
||
184 | { |
||
185 | if (segp->special == SEGMENT_IS_ROBOTMAKER) |
||
186 | matcen_create( segp); |
||
187 | else |
||
188 | fuelcen_create( segp); |
||
189 | } |
||
190 | |||
191 | // The lower this number is, the more quickly the center can be re-triggered. |
||
192 | // If it's too low, it can mean all the robots won't be put out, but for about 5 |
||
193 | // robots, that's not real likely. |
||
194 | #define MATCEN_LIFE (i2f(30-2*Difficulty_level)) |
||
195 | |||
196 | //------------------------------------------------------------ |
||
197 | // Trigger (enable) the materialization center in segment segnum |
||
198 | void trigger_matcen(const vmsegptridx_t segp) |
||
199 | { |
||
200 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
201 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
202 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
203 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
204 | FuelCenter *robotcen; |
||
205 | |||
206 | Assert(segp->special == SEGMENT_IS_ROBOTMAKER); |
||
207 | assert(segp->matcen_num < LevelUniqueFuelcenterState.Num_fuelcenters); |
||
208 | Assert((segp->matcen_num >= 0) && (segp->matcen_num <= Highest_segment_index)); |
||
209 | |||
210 | robotcen = &Station[RobotCenters[segp->matcen_num].fuelcen_num]; |
||
211 | |||
212 | if (robotcen->Enabled == 1) |
||
213 | return; |
||
214 | |||
215 | if (!robotcen->Lives) |
||
216 | return; |
||
217 | |||
218 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
219 | #if defined(DXX_BUILD_DESCENT_II) |
||
220 | // MK: 11/18/95, At insane, matcens work forever! |
||
221 | if (Difficulty_level+1 < NDL) |
||
222 | #endif |
||
223 | robotcen->Lives--; |
||
224 | |||
225 | robotcen->Timer = F1_0*1000; // Make sure the first robot gets emitted right away. |
||
226 | robotcen->Enabled = 1; // Say this center is enabled, it can create robots. |
||
227 | robotcen->Capacity = i2f(Difficulty_level + 3); |
||
228 | robotcen->Disable_time = MATCEN_LIFE; |
||
229 | |||
230 | // Create a bright object in the segment. |
||
231 | auto &vcvertptr = Vertices.vcptr; |
||
232 | auto &&pos = compute_segment_center(vcvertptr, segp); |
||
233 | const auto &&delta = vm_vec_sub(vcvertptr(segp->verts[0]), pos); |
||
234 | vm_vec_scale_add2(pos, delta, F1_0/2); |
||
235 | auto objnum = obj_create( OBJ_LIGHT, 0, segp, pos, NULL, 0, CT_LIGHT, MT_NONE, RT_NONE ); |
||
236 | if (objnum != object_none) { |
||
237 | objnum->lifeleft = MATCEN_LIFE; |
||
238 | objnum->ctype.light_info.intensity = i2f(8); // Light cast by a fuelcen. |
||
239 | } else { |
||
240 | Int3(); |
||
241 | } |
||
242 | } |
||
243 | |||
244 | #if DXX_USE_EDITOR |
||
245 | //------------------------------------------------------------ |
||
246 | // Takes away a segment's fuel center properties. |
||
247 | // Deletes the segment point entry in the FuelCenter list. |
||
248 | void fuelcen_delete(const vmsegptr_t segp) |
||
249 | { |
||
250 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
251 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
252 | auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters; |
||
253 | Restart: ; |
||
254 | segp->special = 0; |
||
255 | |||
256 | for (uint_fast32_t i = 0; i < Num_fuelcenters; i++ ) { |
||
257 | FuelCenter &fi = Station[i]; |
||
258 | if (vmsegptr(fi.segnum) == segp) |
||
259 | { |
||
260 | |||
261 | auto &Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers; |
||
262 | // If Robot maker is deleted, fix Segments and RobotCenters. |
||
263 | if (fi.Type == SEGMENT_IS_ROBOTMAKER) { |
||
264 | if (!Num_robot_centers) |
||
265 | { |
||
266 | con_printf(CON_URGENT, "%s:%u: error: Num_robot_centers=0 while deleting robot maker", __FILE__, __LINE__); |
||
267 | return; |
||
268 | } |
||
269 | const auto &&range = partial_range(RobotCenters, static_cast<unsigned>(segp->matcen_num), Num_robot_centers--); |
||
270 | |||
271 | std::move(std::next(range.begin()), range.end(), range.begin()); |
||
272 | range_for (auto &fj, partial_const_range(Station, Num_fuelcenters)) |
||
273 | { |
||
274 | if ( fj.Type == SEGMENT_IS_ROBOTMAKER ) |
||
275 | if ( Segments[fj.segnum].matcen_num > segp->matcen_num ) |
||
276 | Segments[fj.segnum].matcen_num--; |
||
277 | } |
||
278 | } |
||
279 | |||
280 | //fix RobotCenters so they point to correct fuelcenter |
||
281 | range_for (auto &j, partial_range(RobotCenters, Num_robot_centers)) |
||
282 | if (j.fuelcen_num > i) //this robotcenter's fuelcen is changing |
||
283 | j.fuelcen_num--; |
||
284 | Assert(Num_fuelcenters > 0); |
||
285 | Num_fuelcenters--; |
||
286 | for (uint_fast32_t j = i; j < Num_fuelcenters; j++ ) { |
||
287 | Station[j] = Station[j+1]; |
||
288 | Segments[Station[j].segnum].station_idx = j; |
||
289 | } |
||
290 | goto Restart; |
||
291 | } |
||
292 | } |
||
293 | LevelUniqueFuelcenterState.Num_fuelcenters = Num_fuelcenters; |
||
294 | } |
||
295 | #endif |
||
296 | |||
297 | #define ROBOT_GEN_TIME (i2f(5)) |
||
298 | |||
299 | imobjptridx_t create_morph_robot(const vmsegptridx_t segp, const vms_vector &object_pos, const unsigned object_id) |
||
300 | { |
||
301 | ai_behavior default_behavior; |
||
302 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
303 | #if defined(DXX_BUILD_DESCENT_I) |
||
304 | default_behavior = ai_behavior::AIB_NORMAL; |
||
305 | if (object_id == 10) // This is a toaster guy! |
||
306 | default_behavior = ai_behavior::AIB_RUN_FROM; |
||
307 | #elif defined(DXX_BUILD_DESCENT_II) |
||
308 | default_behavior = Robot_info[object_id].behavior; |
||
309 | #endif |
||
310 | |||
311 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
312 | auto obj = robot_create(object_id, segp, object_pos, |
||
313 | &vmd_identity_matrix, Polygon_models[Robot_info[object_id].model_num].rad, |
||
314 | default_behavior); |
||
315 | |||
316 | if (obj == object_none) |
||
317 | { |
||
318 | Int3(); |
||
319 | return object_none; |
||
320 | } |
||
321 | |||
322 | ++LevelUniqueObjectState.accumulated_robots; |
||
323 | ++GameUniqueState.accumulated_robots; |
||
324 | //Set polygon-object-specific data |
||
325 | |||
326 | obj->rtype.pobj_info.model_num = Robot_info[get_robot_id(obj)].model_num; |
||
327 | obj->rtype.pobj_info.subobj_flags = 0; |
||
328 | |||
329 | //set Physics info |
||
330 | |||
331 | obj->mtype.phys_info.mass = Robot_info[get_robot_id(obj)].mass; |
||
332 | obj->mtype.phys_info.drag = Robot_info[get_robot_id(obj)].drag; |
||
333 | |||
334 | obj->mtype.phys_info.flags |= (PF_LEVELLING); |
||
335 | |||
336 | obj->shields = Robot_info[get_robot_id(obj)].strength; |
||
337 | |||
338 | create_n_segment_path(obj, 6, segment_none); // Create a 6 segment path from creation point. |
||
339 | |||
340 | #if defined(DXX_BUILD_DESCENT_I) |
||
341 | if (default_behavior == ai_behavior::AIB_RUN_FROM) |
||
342 | obj->ctype.ai_info.ail.mode = ai_mode::AIM_RUN_FROM_OBJECT; |
||
343 | #elif defined(DXX_BUILD_DESCENT_II) |
||
344 | obj->ctype.ai_info.ail.mode = ai_behavior_to_mode(default_behavior); |
||
345 | #endif |
||
346 | |||
347 | return obj; |
||
348 | } |
||
349 | |||
350 | } |
||
351 | |||
352 | // ---------------------------------------------------------------------------------------------------------- |
||
353 | static void robotmaker_proc(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, FuelCenter *const robotcen, const unsigned numrobotcen) |
||
354 | { |
||
355 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
356 | auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState; |
||
357 | auto &Objects = LevelUniqueObjectState.Objects; |
||
358 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
359 | auto &vcobjptr = Objects.vcptr; |
||
360 | auto &vmobjptridx = Objects.vmptridx; |
||
361 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
362 | int matcen_num; |
||
363 | fix top_time; |
||
364 | |||
365 | if (robotcen->Enabled == 0) |
||
366 | return; |
||
367 | |||
368 | if (robotcen->Disable_time > 0) { |
||
369 | robotcen->Disable_time -= FrameTime; |
||
370 | if (robotcen->Disable_time <= 0) { |
||
371 | robotcen->Enabled = 0; |
||
372 | } |
||
373 | } |
||
374 | |||
375 | // No robot making in multiplayer mode. |
||
376 | if ((Game_mode & GM_MULTI) && (!(Game_mode & GM_MULTI_ROBOTS) || !multi_i_am_master())) |
||
377 | return; |
||
378 | |||
379 | // Wait until transmorgafier has capacity to make a robot... |
||
380 | if ( robotcen->Capacity <= 0 ) { |
||
381 | return; |
||
382 | } |
||
383 | |||
384 | const auto &&segp = vmsegptr(robotcen->segnum); |
||
385 | matcen_num = segp->matcen_num; |
||
386 | |||
387 | if ( matcen_num == -1 ) { |
||
388 | return; |
||
389 | } |
||
390 | |||
391 | matcen_info *mi = &RobotCenters[matcen_num]; |
||
392 | for (unsigned i = 0;; ++i) |
||
393 | { |
||
394 | if (i >= mi->robot_flags.size()) |
||
395 | return; |
||
396 | if (mi->robot_flags[i]) |
||
397 | break; |
||
398 | } |
||
399 | |||
400 | // Wait until we have a free slot for this puppy... |
||
401 | // <<<<<<<<<<<<<<<< Num robots in mine >>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<< Max robots in mine >>>>>>>>>>>>>>> |
||
402 | { |
||
403 | auto &plr = get_local_player(); |
||
404 | if (LevelUniqueObjectState.accumulated_robots - plr.num_kills_level >= Gamesave_num_org_robots + Num_extry_robots) |
||
405 | { |
||
406 | return; |
||
407 | } |
||
408 | } |
||
409 | |||
410 | robotcen->Timer += FrameTime; |
||
411 | |||
412 | auto &vcvertptr = Vertices.vcptr; |
||
413 | switch( robotcen->Flag ) { |
||
414 | case 0: // Wait until next robot can generate |
||
415 | if (Game_mode & GM_MULTI) |
||
416 | { |
||
417 | top_time = ROBOT_GEN_TIME; |
||
418 | } |
||
419 | else |
||
420 | { |
||
421 | const auto center = compute_segment_center(vcvertptr, segp); |
||
422 | const auto dist_to_player = vm_vec_dist_quick( ConsoleObject->pos, center ); |
||
423 | top_time = dist_to_player/64 + d_rand() * 2 + F1_0*2; |
||
424 | if ( top_time > ROBOT_GEN_TIME ) |
||
425 | top_time = ROBOT_GEN_TIME + d_rand(); |
||
426 | if ( top_time < F1_0*2 ) |
||
427 | top_time = F1_0*3/2 + d_rand()*2; |
||
428 | } |
||
429 | |||
430 | if (robotcen->Timer > top_time ) { |
||
431 | int count=0; |
||
432 | int my_station_num = numrobotcen; |
||
433 | |||
434 | // Make sure this robotmaker hasn't put out its max without having any of them killed. |
||
435 | range_for (const auto &&objp, vcobjptr) |
||
436 | { |
||
437 | auto &obj = *objp; |
||
438 | if (obj.type == OBJ_ROBOT) |
||
439 | if ((obj.matcen_creator ^ 0x80) == my_station_num) |
||
440 | count++; |
||
441 | } |
||
442 | if (count > GameUniqueState.Difficulty_level + 3) { |
||
443 | robotcen->Timer /= 2; |
||
444 | return; |
||
445 | } |
||
446 | |||
447 | // Whack on any robot or player in the matcen segment. |
||
448 | count=0; |
||
449 | auto segnum = robotcen->segnum; |
||
450 | const auto &&csegp = vmsegptr(segnum); |
||
451 | range_for (const auto objp, objects_in(csegp, vmobjptridx, vmsegptr)) |
||
452 | { |
||
453 | count++; |
||
454 | if ( count > MAX_OBJECTS ) { |
||
455 | Int3(); |
||
456 | return; |
||
457 | } |
||
458 | if (objp->type==OBJ_ROBOT) { |
||
459 | collide_robot_and_materialization_center(objp); |
||
460 | robotcen->Timer = top_time/2; |
||
461 | return; |
||
462 | } else if (objp->type==OBJ_PLAYER ) { |
||
463 | collide_player_and_materialization_center(objp); |
||
464 | robotcen->Timer = top_time/2; |
||
465 | return; |
||
466 | } |
||
467 | } |
||
468 | |||
469 | const auto &&cur_object_loc = compute_segment_center(vcvertptr, csegp); |
||
470 | const auto &&robotcen_segp = vmsegptridx(robotcen->segnum); |
||
471 | // HACK!!! The 10 under here should be something equal to the 1/2 the size of the segment. |
||
472 | auto obj = object_create_explosion(robotcen_segp, cur_object_loc, i2f(10), VCLIP_MORPHING_ROBOT); |
||
473 | |||
474 | if (obj != object_none) |
||
475 | extract_orient_from_segment(vcvertptr, obj->orient, robotcen_segp); |
||
476 | |||
477 | if ( Vclip[VCLIP_MORPHING_ROBOT].sound_num > -1 ) { |
||
478 | digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, robotcen_segp, 0, cur_object_loc, 0, F1_0); |
||
479 | } |
||
480 | robotcen->Flag = 1; |
||
481 | robotcen->Timer = 0; |
||
482 | |||
483 | } |
||
484 | break; |
||
485 | case 1: // Wait until 1/2 second after VCLIP started. |
||
486 | if (robotcen->Timer > (Vclip[VCLIP_MORPHING_ROBOT].play_time/2) ) { |
||
487 | |||
488 | robotcen->Capacity -= EnergyToCreateOneRobot; |
||
489 | robotcen->Flag = 0; |
||
490 | |||
491 | robotcen->Timer = 0; |
||
492 | const auto &&cur_object_loc = compute_segment_center(vcvertptr, vcsegptr(robotcen->segnum)); |
||
493 | |||
494 | // If this is the first materialization, set to valid robot. |
||
495 | { |
||
496 | int type; |
||
497 | ubyte legal_types[sizeof(mi->robot_flags) * 8]; // the width of robot_flags[]. |
||
498 | int num_types; |
||
499 | |||
500 | num_types = 0; |
||
501 | for (unsigned i = 0;; ++i) |
||
502 | { |
||
503 | if (i >= mi->robot_flags.size()) |
||
504 | break; |
||
505 | uint32_t flags = mi->robot_flags[i]; |
||
506 | for (unsigned j = 0; flags && j < 8 * sizeof(flags); ++j) |
||
507 | { |
||
508 | if (flags & 1) |
||
509 | legal_types[num_types++] = (i * 32) + j; |
||
510 | flags >>= 1; |
||
511 | } |
||
512 | } |
||
513 | |||
514 | if (num_types == 1) |
||
515 | type = legal_types[0]; |
||
516 | else |
||
517 | type = legal_types[(d_rand() * num_types) / 32768]; |
||
518 | |||
519 | const auto &&obj = create_morph_robot(vmsegptridx(robotcen->segnum), cur_object_loc, type ); |
||
520 | if (obj != object_none) { |
||
521 | if (Game_mode & GM_MULTI) |
||
522 | multi_send_create_robot(numrobotcen, obj, type); |
||
523 | obj->matcen_creator = (numrobotcen) | 0x80; |
||
524 | |||
525 | // Make object faces player... |
||
526 | const auto direction = vm_vec_sub(ConsoleObject->pos,obj->pos ); |
||
527 | vm_vector_2_matrix( obj->orient, direction, &obj->orient.uvec, nullptr); |
||
528 | |||
529 | morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, obj); |
||
530 | //robotcen->last_created_obj = obj; |
||
531 | //robotcen->last_created_sig = robotcen->last_created_obj->signature; |
||
532 | } |
||
533 | } |
||
534 | |||
535 | } |
||
536 | break; |
||
537 | default: |
||
538 | robotcen->Flag = 0; |
||
539 | robotcen->Timer = 0; |
||
540 | } |
||
541 | } |
||
542 | |||
543 | //------------------------------------------------------------- |
||
544 | // Called once per frame, replenishes fuel supply. |
||
545 | void fuelcen_update_all() |
||
546 | { |
||
547 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
548 | range_for (auto &&e, enumerate(partial_range(Station, LevelUniqueFuelcenterState.Num_fuelcenters))) |
||
549 | { |
||
550 | auto &i = e.value; |
||
551 | if (i.Type == SEGMENT_IS_ROBOTMAKER) |
||
552 | { |
||
553 | if (! (Game_suspended & SUSP_ROBOTS)) |
||
554 | robotmaker_proc(Vclip, vmsegptridx, &i, e.idx); |
||
555 | } |
||
556 | } |
||
557 | } |
||
558 | |||
559 | namespace dsx { |
||
560 | |||
561 | #if defined(DXX_BUILD_DESCENT_I) |
||
562 | constexpr std::integral_constant<unsigned, F1_0 / 3> FUELCEN_SOUND_DELAY{}; |
||
563 | #elif defined(DXX_BUILD_DESCENT_II) |
||
564 | //play every half second |
||
565 | constexpr std::integral_constant<unsigned, F1_0 / 4> FUELCEN_SOUND_DELAY{}; |
||
566 | #endif |
||
567 | |||
568 | //------------------------------------------------------------- |
||
569 | fix fuelcen_give_fuel(const shared_segment &segp, fix MaxAmountCanTake) |
||
570 | { |
||
571 | static fix64 last_play_time = 0; |
||
572 | |||
573 | if (segp.special == SEGMENT_IS_FUELCEN) |
||
574 | { |
||
575 | fix amount; |
||
576 | |||
577 | #if defined(DXX_BUILD_DESCENT_II) |
||
578 | detect_escort_goal_fuelcen_accomplished(); |
||
579 | #endif |
||
580 | |||
581 | if (MaxAmountCanTake <= 0 ) { |
||
582 | return 0; |
||
583 | } |
||
584 | |||
585 | amount = fixmul(FrameTime,Fuelcen_give_amount); |
||
586 | |||
587 | if (amount > MaxAmountCanTake ) |
||
588 | amount = MaxAmountCanTake; |
||
589 | |||
590 | if (last_play_time + FUELCEN_SOUND_DELAY < GameTime64 || last_play_time > GameTime64) |
||
591 | { |
||
592 | last_play_time = GameTime64; |
||
593 | multi_digi_play_sample(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2); |
||
594 | } |
||
595 | return amount; |
||
596 | } else { |
||
597 | return 0; |
||
598 | } |
||
599 | } |
||
600 | |||
601 | #if defined(DXX_BUILD_DESCENT_II) |
||
602 | //------------------------------------------------------------- |
||
603 | // DM/050904 |
||
604 | // Repair centers |
||
605 | // use same values as fuel centers |
||
606 | fix repaircen_give_shields(const shared_segment &segp, const fix MaxAmountCanTake) |
||
607 | { |
||
608 | static fix last_play_time=0; |
||
609 | |||
610 | if (segp.special == SEGMENT_IS_REPAIRCEN) |
||
611 | { |
||
612 | fix amount; |
||
613 | if (MaxAmountCanTake <= 0 ) { |
||
614 | return 0; |
||
615 | } |
||
616 | amount = fixmul(FrameTime,Fuelcen_give_amount); |
||
617 | if (amount > MaxAmountCanTake ) |
||
618 | amount = MaxAmountCanTake; |
||
619 | if (last_play_time > GameTime64) |
||
620 | last_play_time = 0; |
||
621 | if (GameTime64 > last_play_time+FUELCEN_SOUND_DELAY) { |
||
622 | multi_digi_play_sample(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2); |
||
623 | last_play_time = GameTime64; |
||
624 | } |
||
625 | return amount; |
||
626 | } else { |
||
627 | return 0; |
||
628 | } |
||
629 | } |
||
630 | #endif |
||
631 | |||
632 | // -------------------------------------------------------------------------------------------- |
||
633 | void disable_matcens(void) |
||
634 | { |
||
635 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
636 | range_for (auto &s, partial_range(Station, LevelUniqueFuelcenterState.Num_fuelcenters)) |
||
637 | if (s.Type == SEGMENT_IS_ROBOTMAKER) |
||
638 | { |
||
639 | s.Enabled = 0; |
||
640 | s.Disable_time = 0; |
||
641 | } |
||
642 | } |
||
643 | |||
644 | // -------------------------------------------------------------------------------------------- |
||
645 | // Initialize all materialization centers. |
||
646 | // Give them all the right number of lives. |
||
647 | void init_all_matcens(void) |
||
648 | { |
||
649 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
650 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
651 | const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters; |
||
652 | const auto &&robot_range = partial_const_range(RobotCenters, LevelSharedRobotcenterState.Num_robot_centers); |
||
653 | for (uint_fast32_t i = 0; i < Num_fuelcenters; i++) |
||
654 | if (Station[i].Type == SEGMENT_IS_ROBOTMAKER) { |
||
655 | Station[i].Lives = 3; |
||
656 | Station[i].Enabled = 0; |
||
657 | Station[i].Disable_time = 0; |
||
658 | // Make sure this fuelcen is pointed at by a matcen. |
||
659 | if (std::find_if(robot_range.begin(), robot_range.end(), [i](const matcen_info &mi) { |
||
660 | return mi.fuelcen_num == i; |
||
661 | }) == robot_range.end()) |
||
662 | { |
||
663 | Station[i].Lives = 0; |
||
664 | LevelError("Station %" PRIuFAST32 " has type robotmaker, but no robotmaker uses it; ignoring.", i); |
||
665 | } |
||
666 | } |
||
667 | |||
668 | #ifndef NDEBUG |
||
669 | // Make sure all matcens point at a fuelcen |
||
670 | range_for (auto &i, robot_range) |
||
671 | { |
||
672 | auto fuelcen_num = i.fuelcen_num; |
||
673 | Assert(fuelcen_num < Num_fuelcenters); |
||
674 | Assert(Station[fuelcen_num].Type == SEGMENT_IS_ROBOTMAKER); |
||
675 | } |
||
676 | #endif |
||
677 | |||
678 | } |
||
679 | |||
680 | } |
||
681 | |||
682 | struct d1mi_v25 |
||
683 | { |
||
684 | matcen_info *m; |
||
685 | d1mi_v25(matcen_info &mi) : m(&mi) {} |
||
686 | }; |
||
687 | |||
688 | struct d1cmi_v25 |
||
689 | { |
||
690 | const matcen_info *m; |
||
691 | d1cmi_v25(const matcen_info &mi) : m(&mi) {} |
||
692 | }; |
||
693 | |||
694 | #define D1_MATCEN_V25_MEMBERLIST (p.m->robot_flags[0], serial::pad<sizeof(fix) * 2>(), p.m->segnum, p.m->fuelcen_num) |
||
695 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1mi_v25, p, D1_MATCEN_V25_MEMBERLIST); |
||
696 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1cmi_v25, p, D1_MATCEN_V25_MEMBERLIST); |
||
697 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1mi_v25, 16); |
||
698 | |||
699 | namespace dsx { |
||
700 | #if defined(DXX_BUILD_DESCENT_I) |
||
701 | struct d1mi_v26 |
||
702 | { |
||
703 | matcen_info *m; |
||
704 | d1mi_v26(matcen_info &mi) : m(&mi) {} |
||
705 | }; |
||
706 | |||
707 | struct d1cmi_v26 |
||
708 | { |
||
709 | const matcen_info *m; |
||
710 | d1cmi_v26(const matcen_info &mi) : m(&mi) {} |
||
711 | }; |
||
712 | |||
713 | #define D1_MATCEN_V26_MEMBERLIST (p.m->robot_flags[0], serial::pad<sizeof(uint32_t)>(), serial::pad<sizeof(fix) * 2>(), p.m->segnum, p.m->fuelcen_num) |
||
714 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1mi_v26, p, D1_MATCEN_V26_MEMBERLIST); |
||
715 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1cmi_v26, p, D1_MATCEN_V26_MEMBERLIST); |
||
716 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1mi_v26, 20); |
||
717 | |||
718 | void matcen_info_read(PHYSFS_File *fp, matcen_info &mi, int version) |
||
719 | { |
||
720 | if (version > 25) |
||
721 | PHYSFSX_serialize_read<const d1mi_v26>(fp, mi); |
||
722 | else |
||
723 | PHYSFSX_serialize_read<const d1mi_v25>(fp, mi); |
||
724 | } |
||
725 | #elif defined(DXX_BUILD_DESCENT_II) |
||
726 | void fuelcen_check_for_goal(object &plrobj, const shared_segment &segp) |
||
727 | { |
||
728 | Assert (game_mode_capture_flag()); |
||
729 | |||
730 | unsigned check_team; |
||
731 | powerup_type_t powerup_to_drop; |
||
732 | switch(segp.special) |
||
733 | { |
||
734 | case SEGMENT_IS_GOAL_BLUE: |
||
735 | check_team = TEAM_BLUE; |
||
736 | powerup_to_drop = POW_FLAG_RED; |
||
737 | break; |
||
738 | case SEGMENT_IS_GOAL_RED: |
||
739 | check_team = TEAM_RED; |
||
740 | powerup_to_drop = POW_FLAG_BLUE; |
||
741 | break; |
||
742 | default: |
||
743 | return; |
||
744 | } |
||
745 | if (get_team(Player_num) != check_team) |
||
746 | return; |
||
747 | auto &player_info = plrobj.ctype.player_info; |
||
748 | if (player_info.powerup_flags & PLAYER_FLAGS_FLAG) |
||
749 | { |
||
750 | player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG; |
||
751 | multi_send_capture_bonus (Player_num); |
||
752 | maybe_drop_net_powerup(powerup_to_drop, 1, 0); |
||
753 | } |
||
754 | } |
||
755 | |||
756 | void fuelcen_check_for_hoard_goal(object &plrobj, const shared_segment &segp) |
||
757 | { |
||
758 | Assert (game_mode_hoard()); |
||
759 | |||
760 | if (Player_dead_state != player_dead_state::no) |
||
761 | return; |
||
762 | |||
763 | const auto special = segp.special; |
||
764 | if (special==SEGMENT_IS_GOAL_BLUE || special==SEGMENT_IS_GOAL_RED) |
||
765 | { |
||
766 | auto &player_info = plrobj.ctype.player_info; |
||
767 | if (auto &hoard_orbs = player_info.hoard.orbs) |
||
768 | { |
||
769 | player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG; |
||
770 | multi_send_orb_bonus(Player_num, std::exchange(hoard_orbs, 0)); |
||
771 | } |
||
772 | } |
||
773 | |||
774 | } |
||
775 | |||
776 | |||
777 | /* |
||
778 | * reads an d1_matcen_info structure from a PHYSFS_File |
||
779 | */ |
||
780 | void d1_matcen_info_read(PHYSFS_File *fp, matcen_info &mi) |
||
781 | { |
||
782 | PHYSFSX_serialize_read<const d1mi_v25>(fp, mi); |
||
783 | mi.robot_flags[1] = 0; |
||
784 | } |
||
785 | |||
786 | DEFINE_SERIAL_UDT_TO_MESSAGE(matcen_info, m, (m.robot_flags, serial::pad<sizeof(fix) * 2>(), m.segnum, m.fuelcen_num)); |
||
787 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(matcen_info, 20); |
||
788 | |||
789 | void matcen_info_read(PHYSFS_File *fp, matcen_info &mi) |
||
790 | { |
||
791 | PHYSFSX_serialize_read(fp, mi); |
||
792 | } |
||
793 | #endif |
||
794 | |||
795 | void matcen_info_write(PHYSFS_File *fp, const matcen_info &mi, short version) |
||
796 | { |
||
797 | if (version >= 27) |
||
798 | #if defined(DXX_BUILD_DESCENT_I) |
||
799 | PHYSFSX_serialize_write<d1cmi_v26>(fp, mi); |
||
800 | #elif defined(DXX_BUILD_DESCENT_II) |
||
801 | PHYSFSX_serialize_write(fp, mi); |
||
802 | #endif |
||
803 | else |
||
804 | PHYSFSX_serialize_write<d1cmi_v25>(fp, mi); |
||
805 | } |
||
806 | } |
||
807 | |||
808 | DEFINE_SERIAL_UDT_TO_MESSAGE(FuelCenter, fc, (serial::sign_extend<int>(fc.Type), serial::sign_extend<int>(fc.segnum), fc.Flag, fc.Enabled, fc.Lives, serial::pad<1>(), fc.Capacity, serial::pad<sizeof(fix)>(), fc.Timer, fc.Disable_time, serial::pad<3 * sizeof(fix)>())); |
||
809 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(FuelCenter, 40); |
||
810 | |||
811 | void fuelcen_read(PHYSFS_File *fp, FuelCenter &fc) |
||
812 | { |
||
813 | PHYSFSX_serialize_read(fp, fc); |
||
814 | } |
||
815 | |||
816 | void fuelcen_write(PHYSFS_File *fp, const FuelCenter &fc) |
||
817 | { |
||
818 | PHYSFSX_serialize_write(fp, fc); |
||
819 | } |