1 #include "spaceship.h"
2
3 #include <array>
4
5 #include <i18n.h>
6
7 #include "mesh.h"
8 #include "shipTemplate.h"
9 #include "playerInfo.h"
10 #include "spaceObjects/beamEffect.h"
11 #include "factionInfo.h"
12 #include "spaceObjects/explosionEffect.h"
13 #include "particleEffect.h"
14 #include "spaceObjects/warpJammer.h"
15 #include "gameGlobalInfo.h"
16
17 #include "scriptInterface.h"
REGISTER_SCRIPT_SUBCLASS_NO_CREATE(SpaceShip,ShipTemplateBasedObject)18 REGISTER_SCRIPT_SUBCLASS_NO_CREATE(SpaceShip, ShipTemplateBasedObject)
19 {
20 /// [DEPRECATED]
21 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isFriendOrFoeIdentified);
22 /// [DEPRECATED]
23 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isFullyScanned);
24 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isFriendOrFoeIdentifiedBy);
25 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isFullyScannedBy);
26 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isFriendOrFoeIdentifiedByFaction);
27 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isFullyScannedByFaction);
28 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, isDocked);
29 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getDockedWith);
30 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getDockingState);
31 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getTarget);
32 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getWeaponStorage);
33 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getWeaponStorageMax);
34 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWeaponStorage);
35 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWeaponStorageMax);
36 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getShieldsFrequency);
37 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setShieldsFrequency);
38 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamFrequency);
39 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getMaxEnergy);
40 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setMaxEnergy);
41 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getEnergy);
42 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setEnergy);
43 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, hasSystem);
44 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemHackedLevel);
45 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemHackedLevel);
46 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemHealth);
47 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemHealth);
48 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemHealthMax);
49 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemHealthMax);
50 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemHeat);
51 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemHeat);
52 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemHeatRate);
53 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemHeatRate);
54 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemPower);
55 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemPower);
56 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemPowerRate);
57 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemPowerRate);
58 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemPowerFactor);
59 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemPowerFactor);
60 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemCoolant);
61 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemCoolant);
62 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getSystemCoolantRate);
63 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setSystemCoolantRate);
64 ///Get multiple results, first one is forward speed and second one is reverse speed.
65 ///ex : forward,reverse = getImpulseMaxSpeed() (you can also use select or _ to get only reverse speed)
66 ///You can also only get forward speed, reverse speed will just be discarded :
67 ///forward = getImpulseMaxSpeed()
68 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getImpulseMaxSpeed);
69 ///Sets max speed.
70 ///If called with only one argument, sets forward and reverse speed to equal values.
71 ///If called with two arguments, first one is forward speed and second one is reverse speed.
72 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setImpulseMaxSpeed);
73 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getRotationMaxSpeed);
74 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setRotationMaxSpeed);
75 ///Get multiple resulsts, first one is forward acceleration and second one is reverse acceleration.
76 ///ex : forward, reverse = getAcceleration (you can also use select or _ to get only reverse speed)
77 ///You can also only get forward speed, reverse speed will just be discarded :
78 ///forward = getAcceleration()
79 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getAcceleration);
80 ///Sets acceleration.
81 ///If called with one argument, sets forward and reverse acceleration to equal values.
82 ///If called with two arguments, first one is forward acceleration and second one is reverse acceleration.
83 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setAcceleration);
84 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setCombatManeuver);
85 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, hasJumpDrive);
86 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setJumpDrive);
87 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setJumpDriveRange);
88 /// sets the current jump range charged.
89 /// ships will be able to jump when this is equal to their max jump drive range.
90 /// Example ship:setJumpDriveCharge(50000)
91 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setJumpDriveCharge);
92 /// returns the current amount of jump charged.
93 /// Example ship:getJumpDriveCharge()
94 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getJumpDriveCharge);
95 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getJumpDelay);
96 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, hasWarpDrive);
97 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWarpDrive);
98 /// Set the warp speed for this ship's warp level 1.
99 /// Setting this is equivalent to also setting setWarpDrive(true).
100 /// If a value isn't specified in the ship template, the default is 1000.
101 /// Requires a numeric value.
102 /// Example: ship:setWarpSpeed(500);
103 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWarpSpeed);
104 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getWarpSpeed);
105 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponArc);
106 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponDirection);
107 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponRange);
108 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponTurretArc);
109 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponTurretDirection);
110 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponCycleTime);
111 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponDamage);
112 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponEnergyPerFire);
113 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getBeamWeaponHeatPerFire);
114 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setBeamWeapon);
115 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setBeamWeaponTurret);
116 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setBeamWeaponTexture);
117 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setBeamWeaponEnergyPerFire);
118 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setBeamWeaponHeatPerFire);
119 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWeaponTubeCount);
120 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getWeaponTubeCount);
121 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getWeaponTubeLoadType);
122 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, weaponTubeAllowMissle);
123 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, weaponTubeDisallowMissle);
124 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWeaponTubeExclusiveFor);
125 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setWeaponTubeDirection);
126 /// Set the tube size
127 /// Example: ship:setTubeSize(0,"small")
128 /// Valid Sizes: "small" "medium" "large"
129 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setTubeSize);
130 /// Returns the size of the tube
131 /// Example: local size = ship:getTubeSize(0)
132 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getTubeSize);
133 // Returns the time for a tube load
134 // Example: load_time = ship:getTubeLoadTime(0)
135 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getTubeLoadTime);
136 // Sets the load time for a tube
137 // Example ship:setTubeLoadTime(0, 15)
138 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setTubeLoadTime);
139 /// Set the icon to be used for this ship on the radar.
140 /// For example, ship:setRadarTrace("RadarBlip.png") will show a dot instead of an arrow for this ship.
141 /// Note: Icon is only shown after scanning, before the ship is scanned it is always shown as an arrow.
142 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setRadarTrace);
143 /// Get the dynamic radar signature values for each component band.
144 /// Returns a float.
145 /// Example: obj:getDynamicRadarSignatureGravity()
146 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getDynamicRadarSignatureGravity);
147 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getDynamicRadarSignatureElectrical);
148 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, getDynamicRadarSignatureBiological);
149 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, addBroadcast);
150 /// Set the scan state of this ship for every faction.
151 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setScanState);
152 /// Set the scane state of this ship for a particular faction.
153 REGISTER_SCRIPT_CLASS_FUNCTION(SpaceShip, setScanStateByFaction);
154 }
155
156 std::array<float, SYS_COUNT> SpaceShip::default_system_power_factors{
157 /*SYS_Reactor*/ -25.0,
158 /*SYS_BeamWeapons*/ 3.0,
159 /*SYS_MissileSystem*/ 1.0,
160 /*SYS_Maneuver*/ 2.0,
161 /*SYS_Impulse*/ 4.0,
162 /*SYS_Warp*/ 5.0,
163 /*SYS_JumpDrive*/ 5.0,
164 /*SYS_FrontShield*/ 5.0,
165 /*SYS_RearShield*/ 5.0,
166 };
167
SpaceShip(string multiplayerClassName,float multiplayer_significant_range)168 SpaceShip::SpaceShip(string multiplayerClassName, float multiplayer_significant_range)
169 : ShipTemplateBasedObject(50, multiplayerClassName, multiplayer_significant_range)
170 {
171 setCollisionPhysics(true, false);
172
173 target_rotation = getRotation();
174 impulse_request = 0;
175 current_impulse = 0;
176 has_warp_drive = true;
177 warp_request = 0.0;
178 current_warp = 0.0;
179 warp_speed_per_warp_level = 1000.0;
180 has_jump_drive = true;
181 jump_drive_min_distance = 5000.0;
182 jump_drive_max_distance = 50000.0;
183 jump_drive_charge = jump_drive_max_distance;
184 jump_distance = 0.0;
185 jump_delay = 0.0;
186 wormhole_alpha = 0.0;
187 weapon_tube_count = 0;
188 turn_speed = 10.0;
189 impulse_max_speed = 600.0;
190 impulse_max_reverse_speed = 600.0;
191 combat_maneuver_charge = 1.0;
192 combat_maneuver_boost_request = 0.0;
193 combat_maneuver_boost_active = 0.0;
194 combat_maneuver_strafe_request = 0.0;
195 combat_maneuver_strafe_active = 0.0;
196 combat_maneuver_boost_speed = 0.0f;
197 combat_maneuver_strafe_speed = 0.0f;
198 target_id = -1;
199 beam_frequency = irandom(0, max_frequency);
200 beam_system_target = SYS_None;
201 shield_frequency = irandom(0, max_frequency);
202 docking_state = DS_NotDocking;
203 impulse_acceleration = 20.0;
204 impulse_reverse_acceleration = 20.0;
205 energy_level = 1000;
206 max_energy_level = 1000;
207 turnSpeed = 0.0f;
208
209 registerMemberReplication(&target_rotation, 1.5);
210 registerMemberReplication(&turnSpeed, 0.1);
211 registerMemberReplication(&impulse_request, 0.1);
212 registerMemberReplication(¤t_impulse, 0.5);
213 registerMemberReplication(&has_warp_drive);
214 registerMemberReplication(&warp_request, 0.1);
215 registerMemberReplication(¤t_warp, 0.1);
216 registerMemberReplication(&has_jump_drive);
217 registerMemberReplication(&jump_drive_charge, 0.5);
218 registerMemberReplication(&jump_delay, 0.5);
219 registerMemberReplication(&jump_drive_min_distance);
220 registerMemberReplication(&jump_drive_max_distance);
221 registerMemberReplication(&wormhole_alpha, 0.5);
222 registerMemberReplication(&weapon_tube_count);
223 registerMemberReplication(&target_id);
224 registerMemberReplication(&turn_speed);
225 registerMemberReplication(&impulse_max_speed);
226 registerMemberReplication(&impulse_max_reverse_speed);
227 registerMemberReplication(&impulse_acceleration);
228 registerMemberReplication(&impulse_reverse_acceleration);
229 registerMemberReplication(&warp_speed_per_warp_level);
230 registerMemberReplication(&shield_frequency);
231 registerMemberReplication(&docking_state);
232 registerMemberReplication(&beam_frequency);
233 registerMemberReplication(&combat_maneuver_charge, 0.5);
234 registerMemberReplication(&combat_maneuver_boost_request);
235 registerMemberReplication(&combat_maneuver_boost_active, 0.2);
236 registerMemberReplication(&combat_maneuver_strafe_request);
237 registerMemberReplication(&combat_maneuver_strafe_active, 0.2);
238 registerMemberReplication(&combat_maneuver_boost_speed);
239 registerMemberReplication(&combat_maneuver_strafe_speed);
240 registerMemberReplication(&radar_trace);
241
242 for(unsigned int n=0; n<SYS_COUNT; n++)
243 {
244 assert(n < default_system_power_factors.size());
245 systems[n].health = 1.0f;
246 systems[n].health_max = 1.0f;
247 systems[n].power_level = 1.0f;
248 systems[n].power_rate_per_second = ShipSystem::default_power_rate_per_second;
249 systems[n].power_request = 1.0f;
250 systems[n].coolant_level = 0.0f;
251 systems[n].coolant_rate_per_second = ShipSystem::default_coolant_rate_per_second;
252 systems[n].coolant_request = 0.0f;
253 systems[n].heat_level = 0.0f;
254 systems[n].heat_rate_per_second = ShipSystem::default_heat_rate_per_second;
255 systems[n].hacked_level = 0.0f;
256 systems[n].power_factor = default_system_power_factors[n];
257
258 registerMemberReplication(&systems[n].health, 0.1);
259 registerMemberReplication(&systems[n].health_max, 0.1);
260 registerMemberReplication(&systems[n].hacked_level, 0.1);
261 }
262
263 for(int n = 0; n < max_beam_weapons; n++)
264 {
265 beam_weapons[n].setParent(this);
266 }
267
268 for(int n = 0; n < max_weapon_tubes; n++)
269 {
270 weapon_tube[n].setParent(this);
271 weapon_tube[n].setIndex(n);
272 }
273
274 for(int n = 0; n < MW_Count; n++)
275 {
276 weapon_storage[n] = 0;
277 weapon_storage_max[n] = 0;
278 registerMemberReplication(&weapon_storage[n]);
279 registerMemberReplication(&weapon_storage_max[n]);
280 }
281
282 scanning_complexity_value = -1;
283 scanning_depth_value = -1;
284
285 // Ships can have dynamic signatures. Initialize a default baseline value
286 // from which clients derive the dynamic signature on update.
287 setRadarSignatureInfo(0.05, 0.2, 0.2);
288
289 if (game_server)
290 setCallSign(gameGlobalInfo->getNextShipCallsign());
291 }
292
293 //due to a suspected compiler bug this deconstructor needs to be explicitly defined
~SpaceShip()294 SpaceShip::~SpaceShip()
295 {
296 }
297
applyTemplateValues()298 void SpaceShip::applyTemplateValues()
299 {
300 for(int n=0; n<max_beam_weapons; n++)
301 {
302 beam_weapons[n].setPosition(ship_template->model_data->getBeamPosition(n));
303 beam_weapons[n].setArc(ship_template->beams[n].getArc());
304 beam_weapons[n].setDirection(ship_template->beams[n].getDirection());
305 beam_weapons[n].setRange(ship_template->beams[n].getRange());
306 beam_weapons[n].setTurretArc(ship_template->beams[n].getTurretArc());
307 beam_weapons[n].setTurretDirection(ship_template->beams[n].getTurretDirection());
308 beam_weapons[n].setTurretRotationRate(ship_template->beams[n].getTurretRotationRate());
309 beam_weapons[n].setCycleTime(ship_template->beams[n].getCycleTime());
310 beam_weapons[n].setDamage(ship_template->beams[n].getDamage());
311 beam_weapons[n].setBeamTexture(ship_template->beams[n].getBeamTexture());
312 beam_weapons[n].setEnergyPerFire(ship_template->beams[n].getEnergyPerFire());
313 beam_weapons[n].setHeatPerFire(ship_template->beams[n].getHeatPerFire());
314 }
315 weapon_tube_count = ship_template->weapon_tube_count;
316 energy_level = max_energy_level = ship_template->energy_storage_amount;
317
318 impulse_max_speed = ship_template->impulse_speed;
319 impulse_max_reverse_speed = ship_template->impulse_reverse_speed;
320 impulse_acceleration = ship_template->impulse_acceleration;
321 impulse_reverse_acceleration = ship_template->impulse_reverse_acceleration;
322
323 turn_speed = ship_template->turn_speed;
324 combat_maneuver_boost_speed = ship_template->combat_maneuver_boost_speed;
325 combat_maneuver_strafe_speed = ship_template->combat_maneuver_strafe_speed;
326 has_warp_drive = ship_template->warp_speed > 0.0;
327 warp_speed_per_warp_level = ship_template->warp_speed;
328 has_jump_drive = ship_template->has_jump_drive;
329 jump_drive_min_distance = ship_template->jump_drive_min_distance;
330 jump_drive_max_distance = ship_template->jump_drive_max_distance;
331 for(int n=0; n<max_weapon_tubes; n++)
332 {
333 weapon_tube[n].setLoadTimeConfig(ship_template->weapon_tube[n].load_time);
334 weapon_tube[n].setDirection(ship_template->weapon_tube[n].direction);
335 weapon_tube[n].setSize(ship_template->weapon_tube[n].size);
336 for(int m=0; m<MW_Count; m++)
337 {
338 if (ship_template->weapon_tube[n].type_allowed_mask & (1 << m))
339 weapon_tube[n].allowLoadOf(EMissileWeapons(m));
340 else
341 weapon_tube[n].disallowLoadOf(EMissileWeapons(m));
342 }
343 }
344 //shipTemplate->has_cloaking;
345 for(int n=0; n<MW_Count; n++)
346 weapon_storage[n] = weapon_storage_max[n] = ship_template->weapon_storage[n];
347
348 ship_template->setCollisionData(this);
349 model_info.setData(ship_template->model_data);
350 }
351
352 #if FEATURE_3D_RENDERING
draw3DTransparent()353 void SpaceShip::draw3DTransparent()
354 {
355 if (!ship_template) return;
356 ShipTemplateBasedObject::draw3DTransparent();
357
358 if ((has_jump_drive && jump_delay > 0.0f) ||
359 (wormhole_alpha > 0.0f))
360 {
361 float delay = jump_delay;
362 if (wormhole_alpha > 0.0f)
363 delay = wormhole_alpha;
364 float alpha = 1.0f - (delay / 10.0f);
365 model_info.renderOverlay(textureManager.getTexture("electric_sphere_texture.png"), alpha);
366 }
367 }
368 #endif//FEATURE_3D_RENDERING
369
getDynamicRadarSignatureInfo()370 RawRadarSignatureInfo SpaceShip::getDynamicRadarSignatureInfo()
371 {
372 // Adjust radar_signature dynamically based on current state and activity.
373 // radar_signature becomes the ship's baseline radar signature.
374 RawRadarSignatureInfo signature_delta;
375
376 // For each ship system ...
377 for(int n = 0; n < SYS_COUNT; n++)
378 {
379 ESystem ship_system = static_cast<ESystem>(n);
380
381 // ... increase the biological band based on system heat, offset by
382 // coolant.
383 signature_delta.biological += std::max(
384 0.0f,
385 std::min(
386 1.0f,
387 getSystemHeat(ship_system) - (getSystemCoolant(ship_system) / 10.0f)
388 )
389 );
390
391 // ... adjust the electrical band if system power allocation is not
392 // 100%.
393 if (ship_system == SYS_JumpDrive && jump_drive_charge < jump_drive_max_distance)
394 {
395 // ... elevate electrical after a jump, since recharging jump
396 // consumes energy.
397 signature_delta.electrical += std::max(
398 0.0f,
399 std::min(
400 1.0f,
401 getSystemPower(ship_system) * (jump_drive_charge + 0.01f / jump_drive_max_distance)
402 )
403 );
404 } else if (getSystemPower(ship_system) != 1.0f)
405 {
406 // For non-Jump systems, allow underpowered systems to reduce the
407 // total electrical signal output.
408 signature_delta.electrical += std::max(
409 -1.0f,
410 std::min(
411 1.0f,
412 getSystemPower(ship_system) - 1.0f
413 )
414 );
415 }
416 }
417
418 // Increase the gravitational band if the ship is about to jump, or is
419 // actively warping.
420 if (jump_delay > 0.0f)
421 {
422 signature_delta.gravity += std::max(
423 0.0f,
424 std::min(
425 (1.0f / jump_delay + 0.01f) + 0.25f,
426 10.0f
427 )
428 );
429 } else if (current_warp > 0.0f)
430 {
431 signature_delta.gravity += current_warp;
432 }
433
434 // Update the signature by adding the delta to its baseline.
435 RawRadarSignatureInfo info = getRadarSignatureInfo();
436 info += signature_delta;
437 return info;
438 }
439
drawOnRadar(sf::RenderTarget & window,sf::Vector2f position,float scale,float rotation,bool long_range)440 void SpaceShip::drawOnRadar(sf::RenderTarget& window, sf::Vector2f position, float scale, float rotation, bool long_range)
441 {
442 // Draw beam arcs on short-range radar only, and only for fully scanned
443 // ships.
444 if (!long_range && (!my_spaceship || (getScannedStateFor(my_spaceship) == SS_FullScan)))
445 {
446 // For each beam ...
447 for(int n = 0; n < max_beam_weapons; n++)
448 {
449 // Draw beam arcs only if the beam has a range. A beam with range 0
450 // effectively doesn't exist; exit if that's the case.
451 if (beam_weapons[n].getRange() == 0.0) continue;
452
453 // Color beam arcs red.
454 // TODO: Make this color configurable.
455 sf::Color color = sf::Color::Red;
456
457 // If the beam is cooling down, flash and fade the arc color.
458 if (beam_weapons[n].getCooldown() > 0)
459 color = sf::Color(255, 255 * (beam_weapons[n].getCooldown() / beam_weapons[n].getCycleTime()), 0);
460
461 // Initialize variables from the beam's data.
462 float beam_direction = beam_weapons[n].getDirection();
463 float beam_arc = beam_weapons[n].getArc();
464 float beam_range = beam_weapons[n].getRange();
465
466 // Set the beam's origin on radar to its relative position on the
467 // mesh.
468 sf::Vector2f beam_offset = sf::rotateVector(ship_template->model_data->getBeamPosition2D(n) * scale, getRotation()-rotation);
469
470 // Configure an array to hold each point of the arc. Each point in
471 // the array draws a line to the next point. If the color between
472 // points is different, it's drawn as a gradient from the origin
473 // point's color to the destination point's.
474 sf::VertexArray a(sf::LinesStrip, 3);
475 a[0].color = color;
476 a[1].color = color;
477 a[2].color = sf::Color(color.r, color.g, color.b, 0);
478
479 // Drop the pen onto the beam's origin.
480 a[0].position = beam_offset + position;
481
482 // Draw the beam's left bound.
483 a[1].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (beam_direction + beam_arc / 2.0f)) * beam_range * scale;
484 a[2].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (beam_direction + beam_arc / 2.0f)) * beam_range * scale * 1.3f;
485 window.draw(a);
486
487 // Draw the beam's right bound.
488 a[1].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (beam_direction - beam_arc / 2.0f)) * beam_range * scale;
489 a[2].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (beam_direction - beam_arc / 2.0f)) * beam_range * scale * 1.3f;
490 window.draw(a);
491
492 // Draw the beam's arc.
493 int arcPoints = int(beam_arc / 10) + 1;
494 sf::VertexArray arc_line(sf::LinesStrip, arcPoints);
495 for(int i=0; i<arcPoints; i++)
496 {
497 arc_line[i].color = color;
498 arc_line[i].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (beam_direction - beam_arc / 2.0f + 10 * i)) * beam_range * scale;
499 }
500 arc_line[arcPoints-1].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (beam_direction + beam_arc / 2.0f)) * beam_range * scale;
501 window.draw(arc_line);
502
503 // If the beam is turreted, draw the turret's arc. Otherwise, exit.
504 if (beam_weapons[n].getTurretArc() == 0.0) continue;
505
506 // Initialize variables from the turret data.
507 float turret_arc = beam_weapons[n].getTurretArc();
508 float turret_direction = beam_weapons[n].getTurretDirection();
509
510 // Draw the turret's bounds, at half the transparency of the beam's.
511 // TODO: Make this color configurable.
512 a[0].color = sf::Color(color.r, color.g, color.b, color.a / 2);
513 a[1].color = sf::Color(color.r, color.g, color.b, color.a / 2);
514
515 // Draw the turret's left bound. (We're reusing the beam's origin.)
516 a[1].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (turret_direction + turret_arc / 2.0f)) * beam_range * scale;
517 a[2].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (turret_direction + turret_arc / 2.0f)) * beam_range * scale * 1.3f;
518 window.draw(a);
519
520 // Draw the turret's right bound.
521 a[1].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (turret_direction - turret_arc / 2.0f)) * beam_range * scale;
522 a[2].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (turret_direction - turret_arc / 2.0f)) * beam_range * scale * 1.3f;
523 window.draw(a);
524
525 // Draw the turret's arc.
526 int turret_points = int(turret_arc / 10) + 1;
527 sf::VertexArray turret_line(sf::LinesStrip, turret_points);
528 for(int i = 0; i < turret_points; i++)
529 {
530 turret_line[i].color = sf::Color(color.r, color.g, color.b, color.a / 2);
531 turret_line[i].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (turret_direction - turret_arc / 2.0f + 10 * i)) * beam_range * scale;
532 }
533 turret_line[turret_points-1].position = beam_offset + position + sf::vector2FromAngle(getRotation()-rotation + (turret_direction + turret_arc / 2.0f)) * beam_range * scale;
534 window.draw(turret_line);
535 }
536 }
537 // If not on long-range radar ...
538 if (!long_range)
539 {
540 // ... and the ship being drawn is either not our ship or has been
541 // scanned ...
542 if (!my_spaceship || getScannedStateFor(my_spaceship) >= SS_SimpleScan)
543 {
544 // ... draw and show shield indicators on our radar.
545 drawShieldsOnRadar(window, position, scale, rotation, 1.0, true);
546 } else {
547 // Otherwise, draw the indicators, but don't show them.
548 drawShieldsOnRadar(window, position, scale, rotation, 1.0, false);
549 }
550 }
551
552 // Set up the radar sprite for objects.
553 sf::Sprite objectSprite;
554
555 // If the object is a ship that hasn't been scanned, draw the default icon.
556 // Otherwise, draw the ship-specific icon.
557 if (my_spaceship && (getScannedStateFor(my_spaceship) == SS_NotScanned || getScannedStateFor(my_spaceship) == SS_FriendOrFoeIdentified))
558 {
559 textureManager.setTexture(objectSprite, "RadarArrow.png");
560 }
561 else
562 {
563 textureManager.setTexture(objectSprite, radar_trace);
564 }
565
566 objectSprite.setRotation(getRotation()-rotation);
567 objectSprite.setPosition(position);
568 if (long_range)
569 {
570 objectSprite.setScale(0.7, 0.7);
571 }
572 if (my_spaceship == this)
573 {
574 objectSprite.setColor(sf::Color(192, 192, 255));
575 }else if (my_spaceship)
576 {
577 if (getScannedStateFor(my_spaceship) != SS_NotScanned)
578 {
579 if (isEnemy(my_spaceship))
580 objectSprite.setColor(sf::Color::Red);
581 else if (isFriendly(my_spaceship))
582 objectSprite.setColor(sf::Color(128, 255, 128));
583 else
584 objectSprite.setColor(sf::Color(128, 128, 255));
585 }else{
586 objectSprite.setColor(sf::Color(192, 192, 192));
587 }
588 }else{
589 objectSprite.setColor(factionInfo[getFactionId()]->gm_color);
590 }
591 window.draw(objectSprite);
592 }
593
drawOnGMRadar(sf::RenderTarget & window,sf::Vector2f position,float scale,float rotation,bool long_range)594 void SpaceShip::drawOnGMRadar(sf::RenderTarget& window, sf::Vector2f position, float scale, float rotation, bool long_range)
595 {
596 if (!long_range)
597 {
598 sf::RectangleShape bar(sf::Vector2f(60, 10));
599 bar.setPosition(position.x - 30, position.y - 30);
600 bar.setSize(sf::Vector2f(60 * hull_strength / hull_max, 5));
601 bar.setFillColor(sf::Color(128, 255, 128, 128));
602 window.draw(bar);
603 }
604 }
605
update(float delta)606 void SpaceShip::update(float delta)
607 {
608 ShipTemplateBasedObject::update(delta);
609
610 if (game_server)
611 {
612 if (docking_state == DS_Docking)
613 {
614 if (!docking_target)
615 docking_state = DS_NotDocking;
616 else
617 target_rotation = sf::vector2ToAngle(getPosition() - docking_target->getPosition());
618 if (fabs(sf::angleDifference(target_rotation, getRotation())) < 10.0)
619 impulse_request = -1.0;
620 else
621 impulse_request = 0.0;
622 }
623 if (docking_state == DS_Docked)
624 {
625 if (!docking_target)
626 {
627 docking_state = DS_NotDocking;
628 }else{
629 setPosition(docking_target->getPosition() + sf::rotateVector(docking_offset, docking_target->getRotation()));
630 target_rotation = sf::vector2ToAngle(getPosition() - docking_target->getPosition());
631
632 P<ShipTemplateBasedObject> docked_with_template_based = docking_target;
633 if (docked_with_template_based && docked_with_template_based->repair_docked) //Check if what we are docked to allows hull repairs, and if so, do it.
634 {
635 if (hull_strength < hull_max)
636 {
637 hull_strength += delta;
638 if (hull_strength > hull_max)
639 hull_strength = hull_max;
640 }
641 }
642 }
643 impulse_request = 0.0;
644 }
645 if ((docking_state == DS_Docked) || (docking_state == DS_Docking))
646 warp_request = 0.0;
647 }
648
649 float rotationDiff;
650 if (fabs(turnSpeed) < 0.0005f) {
651 rotationDiff = sf::angleDifference(getRotation(), target_rotation);
652 } else {
653 rotationDiff = turnSpeed;
654 }
655
656 if (rotationDiff > 1.0)
657 setAngularVelocity(turn_speed * getSystemEffectiveness(SYS_Maneuver));
658 else if (rotationDiff < -1.0)
659 setAngularVelocity(-turn_speed * getSystemEffectiveness(SYS_Maneuver));
660 else
661 setAngularVelocity(rotationDiff * turn_speed * getSystemEffectiveness(SYS_Maneuver));
662
663 //Here we want to have max speed at 100% impulse, and max reverse speed at -100% impulse
664 float cap_speed = impulse_max_speed;
665
666 if(current_impulse < 0 && impulse_max_reverse_speed <= 0.01)
667 {
668 current_impulse = 0; //we could get stuck with a ship with no reverse speed, not being able to accelerate
669 }
670 if(current_impulse < 0)
671 {
672 cap_speed = impulse_max_reverse_speed;
673 }
674 if ((has_jump_drive && jump_delay > 0) || (has_warp_drive && warp_request > 0))
675 {
676 if (WarpJammer::isWarpJammed(getPosition()))
677 {
678 jump_delay = 0;
679 warp_request = 0.0f;
680 }
681 }
682 if (has_jump_drive && jump_delay > 0)
683 {
684 if (current_impulse > 0.0)
685 {
686 if (cap_speed > 0)
687 current_impulse -= delta * (impulse_reverse_acceleration / cap_speed);
688 if (current_impulse < 0.0)
689 current_impulse = 0.0;
690 }
691 if (current_impulse < 0.0)
692 {
693 if (cap_speed > 0)
694 current_impulse += delta * (impulse_acceleration / cap_speed);
695 if (current_impulse > 0.0)
696 current_impulse = 0.0;
697 }
698 if (current_warp > 0.0)
699 {
700 current_warp -= delta;
701 if (current_warp < 0.0)
702 current_warp = 0.0;
703 }
704 jump_delay -= delta * getSystemEffectiveness(SYS_JumpDrive);
705 if (jump_delay <= 0.0)
706 {
707 executeJump(jump_distance);
708 jump_delay = 0.0;
709 }
710 }else if (has_warp_drive && (warp_request > 0 || current_warp > 0))
711 {
712 if (current_impulse > 0.0)
713 {
714 if (cap_speed > 0)
715 current_impulse -= delta * (impulse_reverse_acceleration / cap_speed);
716 if (current_impulse < 0.0)
717 current_impulse = 0.0;
718 }else if (current_impulse < 0.0)
719 {
720 if (cap_speed > 0)
721 current_impulse += delta * (impulse_acceleration / cap_speed);
722 if (current_impulse > 0.0)
723 current_impulse = 0.0;
724 }else{
725 if (current_warp < warp_request)
726 {
727 current_warp += delta / warp_charge_time;
728 if (current_warp > warp_request)
729 current_warp = warp_request;
730 }else if (current_warp > warp_request)
731 {
732 current_warp -= delta / warp_decharge_time;
733 if (current_warp < warp_request)
734 current_warp = warp_request;
735 }
736 }
737 }else{
738 if (has_jump_drive)
739 {
740 float f = getJumpDriveRechargeRate();
741 if (f > 0)
742 {
743 if (jump_drive_charge < jump_drive_max_distance)
744 {
745 float extra_charge = (delta / jump_drive_charge_time * jump_drive_max_distance) * f;
746 if (useEnergy(extra_charge * jump_drive_energy_per_km_charge / 1000.0))
747 {
748 jump_drive_charge += extra_charge;
749 if (jump_drive_charge >= jump_drive_max_distance)
750 jump_drive_charge = jump_drive_max_distance;
751 }
752 }
753 }else{
754 jump_drive_charge += (delta / jump_drive_charge_time * jump_drive_max_distance) * f;
755 if (jump_drive_charge < 0.0f)
756 jump_drive_charge = 0.0f;
757 }
758 }
759 current_warp = 0.0;
760 if (impulse_request > 1.0)
761 impulse_request = 1.0;
762 if (impulse_request < -1.0)
763 impulse_request = -1.0;
764 if (current_impulse < impulse_request)
765 {
766 if (cap_speed > 0)
767 current_impulse += delta * (impulse_acceleration / cap_speed);
768 if (current_impulse > impulse_request)
769 current_impulse = impulse_request;
770 }else if (current_impulse > impulse_request)
771 {
772 if (cap_speed > 0)
773 current_impulse -= delta * (impulse_reverse_acceleration / cap_speed);
774 if (current_impulse < impulse_request)
775 current_impulse = impulse_request;
776 }
777 }
778
779 // Add heat based on warp factor.
780 addHeat(SYS_Warp, current_warp * delta * heat_per_warp);
781
782 // Determine forward direction and velocity.
783 sf::Vector2f forward = sf::vector2FromAngle(getRotation());
784 setVelocity(forward * (current_impulse * cap_speed * getSystemEffectiveness(SYS_Impulse) + current_warp * warp_speed_per_warp_level * getSystemEffectiveness(SYS_Warp)));
785
786 if (combat_maneuver_boost_active > combat_maneuver_boost_request)
787 {
788 combat_maneuver_boost_active -= delta;
789 if (combat_maneuver_boost_active < combat_maneuver_boost_request)
790 combat_maneuver_boost_active = combat_maneuver_boost_request;
791 }
792 if (combat_maneuver_boost_active < combat_maneuver_boost_request)
793 {
794 combat_maneuver_boost_active += delta;
795 if (combat_maneuver_boost_active > combat_maneuver_boost_request)
796 combat_maneuver_boost_active = combat_maneuver_boost_request;
797 }
798 if (combat_maneuver_strafe_active > combat_maneuver_strafe_request)
799 {
800 combat_maneuver_strafe_active -= delta;
801 if (combat_maneuver_strafe_active < combat_maneuver_strafe_request)
802 combat_maneuver_strafe_active = combat_maneuver_strafe_request;
803 }
804 if (combat_maneuver_strafe_active < combat_maneuver_strafe_request)
805 {
806 combat_maneuver_strafe_active += delta;
807 if (combat_maneuver_strafe_active > combat_maneuver_strafe_request)
808 combat_maneuver_strafe_active = combat_maneuver_strafe_request;
809 }
810
811 // If the ship is making a combat maneuver ...
812 if (combat_maneuver_boost_active != 0.0 || combat_maneuver_strafe_active != 0.0)
813 {
814 // ... consume its combat maneuver boost.
815 combat_maneuver_charge -= fabs(combat_maneuver_boost_active) * delta / combat_maneuver_boost_max_time;
816 combat_maneuver_charge -= fabs(combat_maneuver_strafe_active) * delta / combat_maneuver_strafe_max_time;
817
818 // Use boost only if we have boost available.
819 if (combat_maneuver_charge <= 0.0)
820 {
821 combat_maneuver_charge = 0.0;
822 combat_maneuver_boost_request = 0.0;
823 combat_maneuver_strafe_request = 0.0;
824 }else
825 {
826 setVelocity(getVelocity() + forward * combat_maneuver_boost_speed * combat_maneuver_boost_active);
827 setVelocity(getVelocity() + sf::vector2FromAngle(getRotation() + 90) * combat_maneuver_strafe_speed * combat_maneuver_strafe_active);
828 }
829 // If the ship isn't making a combat maneuver, recharge its boost.
830 }else if (combat_maneuver_charge < 1.0)
831 {
832 combat_maneuver_charge += (delta / combat_maneuver_charge_time) * (getSystemEffectiveness(SYS_Maneuver) + getSystemEffectiveness(SYS_Impulse)) / 2.0;
833 if (combat_maneuver_charge > 1.0)
834 combat_maneuver_charge = 1.0;
835 }
836
837 // Add heat to systems consuming combat maneuver boost.
838 addHeat(SYS_Impulse, fabs(combat_maneuver_boost_active) * delta * heat_per_combat_maneuver_boost);
839 addHeat(SYS_Maneuver, fabs(combat_maneuver_strafe_active) * delta * heat_per_combat_maneuver_strafe);
840
841 for(int n = 0; n < max_beam_weapons; n++)
842 {
843 beam_weapons[n].update(delta);
844 }
845
846 for(int n=0; n<max_weapon_tubes; n++)
847 {
848 weapon_tube[n].update(delta);
849 }
850
851 for(int n=0; n<SYS_COUNT; n++)
852 {
853 systems[n].hacked_level = std::max(0.0f, systems[n].hacked_level - delta / unhack_time);
854 systems[n].health = std::min(systems[n].health,systems[n].health_max);
855 }
856
857 model_info.engine_scale = std::min(1.0f, (float) std::max(fabs(getAngularVelocity() / turn_speed), fabs(current_impulse)));
858 if (has_jump_drive && jump_delay > 0.0f)
859 model_info.warp_scale = (10.0f - jump_delay) / 10.0f;
860 else
861 model_info.warp_scale = 0.0;
862 }
863
getShieldRechargeRate(int shield_index)864 float SpaceShip::getShieldRechargeRate(int shield_index)
865 {
866 float rate = 0.3f;
867 rate *= getSystemEffectiveness(getShieldSystemForShieldIndex(shield_index));
868 if (docking_state == DS_Docked)
869 {
870 P<SpaceShip> docked_with_ship = docking_target;
871 if (!docked_with_ship)
872 rate *= 4.0;
873 }
874 return rate;
875 }
876
getTarget()877 P<SpaceObject> SpaceShip::getTarget()
878 {
879 if (game_server)
880 return game_server->getObjectById(target_id);
881 return game_client->getObjectById(target_id);
882 }
883
executeJump(float distance)884 void SpaceShip::executeJump(float distance)
885 {
886 float f = systems[SYS_JumpDrive].health;
887 if (f <= 0.0)
888 return;
889
890 distance = (distance * f) + (distance * (1.0 - f) * random(0.5, 1.5));
891 sf::Vector2f target_position = getPosition() + sf::vector2FromAngle(getRotation()) * distance;
892 if (WarpJammer::isWarpJammed(target_position))
893 target_position = WarpJammer::getFirstNoneJammedPosition(getPosition(), target_position);
894 setPosition(target_position);
895 addHeat(SYS_JumpDrive, jump_drive_heat_per_jump);
896 }
897
canBeDockedBy(P<SpaceObject> obj)898 bool SpaceShip::canBeDockedBy(P<SpaceObject> obj)
899 {
900 if (isEnemy(obj) || !ship_template)
901 return false;
902 P<SpaceShip> ship = obj;
903 if (!ship || !ship->ship_template)
904 return false;
905 return (ship_template->can_be_docked_by_class.count(ship->ship_template->getClass()) +
906 ship_template->can_be_docked_by_class.count(ship->ship_template->getSubClass())) > 0;
907 }
908
collide(Collisionable * other,float force)909 void SpaceShip::collide(Collisionable* other, float force)
910 {
911 if (docking_state == DS_Docking && fabs(sf::angleDifference(target_rotation, getRotation())) < 10.0)
912 {
913 P<SpaceObject> dock_object = P<Collisionable>(other);
914 if (dock_object == docking_target)
915 {
916 docking_state = DS_Docked;
917 docking_offset = sf::rotateVector(getPosition() - other->getPosition(), -other->getRotation());
918 float length = sf::length(docking_offset);
919 docking_offset = docking_offset / length * (length + 2.0f);
920 }
921 }
922 }
923
initializeJump(float distance)924 void SpaceShip::initializeJump(float distance)
925 {
926 if (docking_state != DS_NotDocking)
927 return;
928 if (jump_drive_charge < jump_drive_max_distance) // You can only jump when the drive is fully charged
929 return;
930 if (jump_delay <= 0.0)
931 {
932 jump_distance = distance;
933 jump_delay = 10.0;
934 jump_drive_charge -= distance;
935 }
936 }
937
requestDock(P<SpaceObject> target)938 void SpaceShip::requestDock(P<SpaceObject> target)
939 {
940 if (!target || docking_state != DS_NotDocking || !target->canBeDockedBy(this))
941 return;
942 if (sf::length(getPosition() - target->getPosition()) > 1000 + target->getRadius())
943 return;
944 if (!canStartDocking())
945 return;
946
947 docking_state = DS_Docking;
948 docking_target = target;
949 warp_request = 0.0;
950 }
951
requestUndock()952 void SpaceShip::requestUndock()
953 {
954 if (docking_state == DS_Docked && getSystemEffectiveness(SYS_Impulse) > 0.1)
955 {
956 docking_state = DS_NotDocking;
957 impulse_request = 0.5;
958 }
959 }
960
abortDock()961 void SpaceShip::abortDock()
962 {
963 if (docking_state == DS_Docking)
964 {
965 docking_state = DS_NotDocking;
966 impulse_request = 0.0;
967 warp_request = 0.0;
968 target_rotation = getRotation();
969 }
970 }
971
scanningComplexity(P<SpaceObject> other)972 int SpaceShip::scanningComplexity(P<SpaceObject> other)
973 {
974 if (scanning_complexity_value > -1)
975 return scanning_complexity_value;
976 switch(gameGlobalInfo->scanning_complexity)
977 {
978 case SC_None:
979 return 0;
980 case SC_Simple:
981 return 1;
982 case SC_Normal:
983 if (getScannedStateFor(other) == SS_SimpleScan)
984 return 2;
985 return 1;
986 case SC_Advanced:
987 if (getScannedStateFor(other) == SS_SimpleScan)
988 return 3;
989 return 2;
990 }
991 return 0;
992 }
993
scanningChannelDepth(P<SpaceObject> other)994 int SpaceShip::scanningChannelDepth(P<SpaceObject> other)
995 {
996 if (scanning_depth_value > -1)
997 return scanning_depth_value;
998 switch(gameGlobalInfo->scanning_complexity)
999 {
1000 case SC_None:
1001 return 0;
1002 case SC_Simple:
1003 return 1;
1004 case SC_Normal:
1005 return 2;
1006 case SC_Advanced:
1007 return 2;
1008 }
1009 return 0;
1010 }
1011
scannedBy(P<SpaceObject> other)1012 void SpaceShip::scannedBy(P<SpaceObject> other)
1013 {
1014 switch(getScannedStateFor(other))
1015 {
1016 case SS_NotScanned:
1017 case SS_FriendOrFoeIdentified:
1018 setScannedStateFor(other, SS_SimpleScan);
1019 break;
1020 case SS_SimpleScan:
1021 setScannedStateFor(other, SS_FullScan);
1022 break;
1023 case SS_FullScan:
1024 break;
1025 }
1026 }
1027
setScanState(EScannedState state)1028 void SpaceShip::setScanState(EScannedState state)
1029 {
1030 for(unsigned int faction_id = 0; faction_id < factionInfo.size(); faction_id++)
1031 {
1032 setScannedStateForFaction(faction_id, state);
1033 }
1034 }
1035
setScanStateByFaction(string faction_name,EScannedState state)1036 void SpaceShip::setScanStateByFaction(string faction_name, EScannedState state)
1037 {
1038 setScannedStateForFaction(FactionInfo::findFactionId(faction_name), state);
1039 }
1040
isFriendOrFoeIdentified()1041 bool SpaceShip::isFriendOrFoeIdentified()
1042 {
1043 LOG(WARNING) << "Deprecated \"isFriendOrFoeIdentified\" function called, use isFriendOrFoeIdentifiedBy or isFriendOrFoeIdentifiedByFaction.";
1044 for(unsigned int faction_id = 0; faction_id < factionInfo.size(); faction_id++)
1045 {
1046 if (getScannedStateForFaction(faction_id) > SS_NotScanned)
1047 return true;
1048 }
1049 return false;
1050 }
1051
isFullyScanned()1052 bool SpaceShip::isFullyScanned()
1053 {
1054 LOG(WARNING) << "Deprecated \"isFullyScanned\" function called, use isFullyScannedBy or isFullyScannedByFaction.";
1055 for(unsigned int faction_id = 0; faction_id < factionInfo.size(); faction_id++)
1056 {
1057 if (getScannedStateForFaction(faction_id) >= SS_FullScan)
1058 return true;
1059 }
1060 return false;
1061 }
1062
isFriendOrFoeIdentifiedBy(P<SpaceObject> other)1063 bool SpaceShip::isFriendOrFoeIdentifiedBy(P<SpaceObject> other)
1064 {
1065 return getScannedStateFor(other) >= SS_FriendOrFoeIdentified;
1066 }
1067
isFullyScannedBy(P<SpaceObject> other)1068 bool SpaceShip::isFullyScannedBy(P<SpaceObject> other)
1069 {
1070 return getScannedStateFor(other) >= SS_FullScan;
1071 }
1072
isFriendOrFoeIdentifiedByFaction(int faction_id)1073 bool SpaceShip::isFriendOrFoeIdentifiedByFaction(int faction_id)
1074 {
1075 return getScannedStateForFaction(faction_id) >= SS_FriendOrFoeIdentified;
1076 }
1077
isFullyScannedByFaction(int faction_id)1078 bool SpaceShip::isFullyScannedByFaction(int faction_id)
1079 {
1080 return getScannedStateForFaction(faction_id) >= SS_FullScan;
1081 }
1082
canBeHackedBy(P<SpaceObject> other)1083 bool SpaceShip::canBeHackedBy(P<SpaceObject> other)
1084 {
1085 return (!(this->isFriendly(other)) && this->isFriendOrFoeIdentifiedBy(other)) ;
1086 }
1087
getHackingTargets()1088 std::vector<std::pair<ESystem, float>> SpaceShip::getHackingTargets()
1089 {
1090 std::vector<std::pair<ESystem, float>> results;
1091 for(unsigned int n=0; n<SYS_COUNT; n++)
1092 {
1093 if (n != SYS_Reactor && hasSystem(ESystem(n)))
1094 {
1095 results.emplace_back(ESystem(n), systems[n].hacked_level);
1096 }
1097 }
1098 return results;
1099 }
1100
hackFinished(P<SpaceObject> source,string target)1101 void SpaceShip::hackFinished(P<SpaceObject> source, string target)
1102 {
1103 for(unsigned int n=0; n<SYS_COUNT; n++)
1104 {
1105 if (hasSystem(ESystem(n)))
1106 {
1107 if (target == getSystemName(ESystem(n)))
1108 {
1109 systems[n].hacked_level = std::min(1.0f, systems[n].hacked_level + 0.5f);
1110 return;
1111 }
1112 }
1113 }
1114 LOG(WARNING) << "Unknown hacked target: " << target;
1115 }
1116
getShieldDamageFactor(DamageInfo & info,int shield_index)1117 float SpaceShip::getShieldDamageFactor(DamageInfo& info, int shield_index)
1118 {
1119 float frequency_damage_factor = 1.0;
1120 if (info.type == DT_Energy && gameGlobalInfo->use_beam_shield_frequencies)
1121 {
1122 frequency_damage_factor = frequencyVsFrequencyDamageFactor(info.frequency, shield_frequency);
1123 }
1124 ESystem system = getShieldSystemForShieldIndex(shield_index);
1125
1126 //Shield damage reduction curve. Damage reduction gets slightly exponetial effective with power.
1127 // This also greatly reduces the ineffectiveness at low power situations.
1128 float shield_damage_exponent = 1.6;
1129 float shield_damage_divider = 7.0;
1130 float shield_damage_factor = 1.0 + powf(1.0, shield_damage_exponent) / shield_damage_divider-powf(getSystemEffectiveness(system), shield_damage_exponent) / shield_damage_divider;
1131
1132 return shield_damage_factor * frequency_damage_factor;
1133 }
1134
didAnOffensiveAction()1135 void SpaceShip::didAnOffensiveAction()
1136 {
1137 //We did an offensive action towards our target.
1138 // Check for each faction. If this faction knows if the target is an enemy or a friendly, it now knows if this object is an enemy or a friendly.
1139 for(unsigned int faction_id=0; faction_id<factionInfo.size(); faction_id++)
1140 {
1141 if (getScannedStateForFaction(faction_id) == SS_NotScanned)
1142 {
1143 if (getTarget() && getTarget()->getScannedStateForFaction(faction_id) != SS_NotScanned)
1144 setScannedStateForFaction(faction_id, SS_FriendOrFoeIdentified);
1145 }
1146 }
1147 }
1148
takeHullDamage(float damage_amount,DamageInfo & info)1149 void SpaceShip::takeHullDamage(float damage_amount, DamageInfo& info)
1150 {
1151 if (gameGlobalInfo->use_system_damage)
1152 {
1153 if (info.system_target != SYS_None)
1154 {
1155 //Target specific system
1156 float system_damage = (damage_amount / hull_max) * 2.0;
1157 if (info.type == DT_Energy)
1158 system_damage *= 3.0; //Beam weapons do more system damage, as they penetrate the hull easier.
1159 systems[info.system_target].health -= system_damage;
1160 if (systems[info.system_target].health < -1.0)
1161 systems[info.system_target].health = -1.0;
1162
1163 for(int n=0; n<2; n++)
1164 {
1165 ESystem random_system = ESystem(irandom(0, SYS_COUNT - 1));
1166 //Damage the system compared to the amount of hull damage you would do. If we have less hull strength you get more system damage.
1167 float system_damage = (damage_amount / hull_max) * 1.0;
1168 systems[random_system].health -= system_damage;
1169 if (systems[random_system].health < -1.0)
1170 systems[random_system].health = -1.0;
1171 }
1172
1173 if (info.type == DT_Energy)
1174 damage_amount *= 0.02;
1175 else
1176 damage_amount *= 0.5;
1177 }else{
1178 ESystem random_system = ESystem(irandom(0, SYS_COUNT - 1));
1179 //Damage the system compared to the amount of hull damage you would do. If we have less hull strength you get more system damage.
1180 float system_damage = (damage_amount / hull_max) * 3.0;
1181 if (info.type == DT_Energy)
1182 system_damage *= 2.5; //Beam weapons do more system damage, as they penetrate the hull easier.
1183 systems[random_system].health -= system_damage;
1184 if (systems[random_system].health < -1.0)
1185 systems[random_system].health = -1.0;
1186 }
1187 }
1188
1189 ShipTemplateBasedObject::takeHullDamage(damage_amount, info);
1190 }
1191
destroyedByDamage(DamageInfo & info)1192 void SpaceShip::destroyedByDamage(DamageInfo& info)
1193 {
1194 ExplosionEffect* e = new ExplosionEffect();
1195 e->setSize(getRadius() * 1.5);
1196 e->setPosition(getPosition());
1197 e->setRadarSignatureInfo(0.0, 0.2, 0.2);
1198
1199 if (info.instigator)
1200 {
1201 float points = hull_max * 0.1f;
1202 for(int n=0; n<shield_count; n++)
1203 points += shield_max[n] * 0.1f;
1204 if (isEnemy(info.instigator))
1205 info.instigator->addReputationPoints(points);
1206 else
1207 info.instigator->removeReputationPoints(points);
1208 }
1209 }
1210
hasSystem(ESystem system)1211 bool SpaceShip::hasSystem(ESystem system)
1212 {
1213 switch(system)
1214 {
1215 case SYS_None:
1216 case SYS_COUNT:
1217 return false;
1218 case SYS_Warp:
1219 return has_warp_drive;
1220 case SYS_JumpDrive:
1221 return has_jump_drive;
1222 case SYS_MissileSystem:
1223 return weapon_tube_count > 0;
1224 case SYS_FrontShield:
1225 return shield_count > 0;
1226 case SYS_RearShield:
1227 return shield_count > 1;
1228 case SYS_Reactor:
1229 return true;
1230 case SYS_BeamWeapons:
1231 return true;
1232 case SYS_Maneuver:
1233 return turn_speed > 0.0;
1234 case SYS_Impulse:
1235 return impulse_max_speed > 0.0;
1236 }
1237 return true;
1238 }
1239
getSystemEffectiveness(ESystem system)1240 float SpaceShip::getSystemEffectiveness(ESystem system)
1241 {
1242 float power = systems[system].power_level;
1243
1244 // Substract the hacking from the power, making double hacked systems run at 25% efficiency.
1245 power = std::max(0.0f, power - systems[system].hacked_level * 0.75f);
1246
1247 // Degrade all systems except the reactor once energy level drops below 10.
1248 if (system != SYS_Reactor)
1249 {
1250 if (energy_level < 10.0 && energy_level > 0.0 && power > 0.0)
1251 power = std::min(power * energy_level / 10.0f, power);
1252 else if (energy_level <= 0.0 || power <= 0.0)
1253 power = 0.0f;
1254 }
1255
1256 // Degrade damaged systems.
1257 if (gameGlobalInfo && gameGlobalInfo->use_system_damage)
1258 return std::max(0.0f, power * systems[system].health);
1259
1260 // If a system cannot be damaged, excessive heat degrades it.
1261 return std::max(0.0f, power * (1.0f - systems[system].heat_level));
1262 }
1263
setWeaponTubeCount(int amount)1264 void SpaceShip::setWeaponTubeCount(int amount)
1265 {
1266 weapon_tube_count = std::max(0, std::min(amount, max_weapon_tubes));
1267 for(int n=weapon_tube_count; n<max_weapon_tubes; n++)
1268 {
1269 weapon_tube[n].forceUnload();
1270 }
1271 }
1272
getWeaponTubeCount()1273 int SpaceShip::getWeaponTubeCount()
1274 {
1275 return weapon_tube_count;
1276 }
1277
getWeaponTubeLoadType(int index)1278 EMissileWeapons SpaceShip::getWeaponTubeLoadType(int index)
1279 {
1280 if (index < 0 || index >= weapon_tube_count)
1281 return MW_None;
1282 if (!weapon_tube[index].isLoaded())
1283 return MW_None;
1284 return weapon_tube[index].getLoadType();
1285 }
1286
weaponTubeAllowMissle(int index,EMissileWeapons type)1287 void SpaceShip::weaponTubeAllowMissle(int index, EMissileWeapons type)
1288 {
1289 if (index < 0 || index >= weapon_tube_count)
1290 return;
1291 weapon_tube[index].allowLoadOf(type);
1292 }
1293
weaponTubeDisallowMissle(int index,EMissileWeapons type)1294 void SpaceShip::weaponTubeDisallowMissle(int index, EMissileWeapons type)
1295 {
1296 if (index < 0 || index >= weapon_tube_count)
1297 return;
1298 weapon_tube[index].disallowLoadOf(type);
1299 }
1300
setWeaponTubeExclusiveFor(int index,EMissileWeapons type)1301 void SpaceShip::setWeaponTubeExclusiveFor(int index, EMissileWeapons type)
1302 {
1303 if (index < 0 || index >= weapon_tube_count)
1304 return;
1305 for(int n=0; n<MW_Count; n++)
1306 weapon_tube[index].disallowLoadOf(EMissileWeapons(n));
1307 weapon_tube[index].allowLoadOf(type);
1308 }
1309
setWeaponTubeDirection(int index,float direction)1310 void SpaceShip::setWeaponTubeDirection(int index, float direction)
1311 {
1312 if (index < 0 || index >= weapon_tube_count)
1313 return;
1314 weapon_tube[index].setDirection(direction);
1315 }
1316
setTubeSize(int index,EMissileSizes size)1317 void SpaceShip::setTubeSize(int index, EMissileSizes size)
1318 {
1319 if (index < 0 || index >= weapon_tube_count)
1320 return;
1321 weapon_tube[index].setSize(size);
1322 }
1323
getTubeSize(int index)1324 EMissileSizes SpaceShip::getTubeSize(int index)
1325 {
1326 if (index < 0 || index >= weapon_tube_count)
1327 return MS_Medium;
1328 return weapon_tube[index].getSize();
1329 }
1330
getTubeLoadTime(int index)1331 float SpaceShip::getTubeLoadTime(int index)
1332 {
1333 if (index < 0 || index >= weapon_tube_count) {
1334 return 0;
1335 }
1336 return weapon_tube[index].getLoadTimeConfig();
1337 }
1338
setTubeLoadTime(int index,float time)1339 void SpaceShip::setTubeLoadTime(int index, float time)
1340 {
1341 if (index < 0 || index >= weapon_tube_count) {
1342 return;
1343 }
1344 weapon_tube[index].setLoadTimeConfig(time);
1345 }
1346
addBroadcast(int threshold,string message)1347 void SpaceShip::addBroadcast(int threshold, string message)
1348 {
1349 if ((threshold < 0) || (threshold > 2)) //if an invalid threshold is defined, alert and default to ally only
1350 {
1351 LOG(ERROR) << "Invalid threshold: " << threshold;
1352 threshold = 0;
1353 }
1354
1355 message = this->getCallSign() + " : " + message; //append the callsign at the start of broadcast
1356
1357 sf::Color color = sf::Color(255, 204, 51); //default : yellow, should never be seen
1358
1359 for(int n=0; n<GameGlobalInfo::max_player_ships; n++)
1360 {
1361 bool addtolog = 0;
1362 P<PlayerSpaceship> ship = gameGlobalInfo->getPlayerShip(n);
1363 if (ship)
1364 {
1365 if (this->isFriendly(ship))
1366 {
1367 color = sf::Color(154,255,154); //ally = light green
1368 addtolog = 1;
1369 }
1370 else if ((factionInfo[this->getFactionId()]->states[ship->getFactionId()] == FVF_Neutral) && ((threshold >= FVF_Neutral)))
1371 {
1372 color = sf::Color(128,128,128); //neutral = grey
1373 addtolog = 1;
1374 }
1375 else if ((this->isEnemy(ship)) && (threshold == FVF_Enemy))
1376 {
1377 color = sf::Color(255,102,102); //enemy = light red
1378 addtolog = 1;
1379 }
1380
1381 if (addtolog)
1382 {
1383 ship->addToShipLog(message, color);
1384 }
1385 }
1386 }
1387 }
1388
getGMInfo()1389 std::unordered_map<string, string> SpaceShip::getGMInfo()
1390 {
1391 std::unordered_map<string, string> ret;
1392 ret = ShipTemplateBasedObject::getGMInfo();
1393 return ret;
1394 }
1395
getScriptExportModificationsOnTemplate()1396 string SpaceShip::getScriptExportModificationsOnTemplate()
1397 {
1398 // Exports attributes common to ships as Lua script function calls.
1399 // Initialize the exported string.
1400 string ret = "";
1401
1402 // If traits don't differ from the ship template, don't bother exporting
1403 // them.
1404 if (getTypeName() != ship_template->getName())
1405 ret += ":setTypeName(\"" + getTypeName() + "\")";
1406 if (hull_max != ship_template->hull)
1407 ret += ":setHullMax(" + string(hull_max, 0) + ")";
1408 if (hull_strength != ship_template->hull)
1409 ret += ":setHull(" + string(hull_strength, 0) + ")";
1410 if (impulse_max_speed != ship_template->impulse_speed)
1411 ret += ":setImpulseMaxSpeed(" + string(impulse_max_speed, 1) + ")";
1412 if (impulse_max_reverse_speed != ship_template->impulse_reverse_speed)
1413 ret += ":setImpulseMaxReverseSpeed(" + string(impulse_max_reverse_speed, 1) + ")";
1414 if (turn_speed != ship_template->turn_speed)
1415 ret += ":setRotationMaxSpeed(" + string(turn_speed, 1) + ")";
1416 if (has_jump_drive != ship_template->has_jump_drive)
1417 ret += ":setJumpDrive(" + string(has_jump_drive ? "true" : "false") + ")";
1418 if (jump_drive_min_distance != ship_template->jump_drive_min_distance
1419 || jump_drive_max_distance != ship_template->jump_drive_max_distance)
1420 ret += ":setJumpDriveRange(" + string(jump_drive_min_distance) + ", " + string(jump_drive_max_distance) + ")";
1421 if (has_warp_drive != (ship_template->warp_speed > 0))
1422 ret += ":setWarpDrive(" + string(has_warp_drive ? "true" : "false") + ")";
1423 if (warp_speed_per_warp_level != ship_template->warp_speed)
1424 ret += ":setWarpSpeed(" + string(warp_speed_per_warp_level) + ")";
1425
1426 // Shield data
1427 // Determine whether to export shield data.
1428 bool add_shields_max_line = getShieldCount() != ship_template->shield_count;
1429 bool add_shields_line = getShieldCount() != ship_template->shield_count;
1430
1431 // If shield max and level don't differ from the template, don't bother
1432 // exporting them.
1433 for(int n = 0; n < getShieldCount(); n++)
1434 {
1435 if (getShieldMax(n) != ship_template->shield_level[n])
1436 add_shields_max_line = true;
1437 if (getShieldLevel(n) != ship_template->shield_level[n])
1438 add_shields_line = true;
1439 }
1440
1441 // If we're exporting shield max ...
1442 if (add_shields_max_line)
1443 {
1444 ret += ":setShieldsMax(";
1445
1446 // ... for each shield, export the shield max.
1447 for(int n = 0; n < getShieldCount(); n++)
1448 {
1449 if (n > 0)
1450 ret += ", ";
1451
1452 ret += string(getShieldMax(n));
1453 }
1454
1455 ret += ")";
1456 }
1457
1458 // If we're exporting shield level ...
1459 if (add_shields_line)
1460 {
1461 ret += ":setShields(";
1462
1463 // ... for each shield, export the shield level.
1464 for(int n = 0; n < getShieldCount(); n++)
1465 {
1466 if (n > 0)
1467 ret += ", ";
1468
1469 ret += string(getShieldLevel(n));
1470 }
1471
1472 ret += ")";
1473 }
1474
1475 // Missile weapon data
1476 if (weapon_tube_count != ship_template->weapon_tube_count)
1477 ret += ":setWeaponTubeCount(" + string(weapon_tube_count) + ")";
1478
1479 for(int n=0; n<weapon_tube_count; n++)
1480 {
1481 WeaponTube& tube = weapon_tube[n];
1482 auto& template_tube = ship_template->weapon_tube[n];
1483 if (tube.getDirection() != template_tube.direction)
1484 {
1485 ret += ":setWeaponTubeDirection(" + string(n) + ", " + string(tube.getDirection(), 0) + ")";
1486 }
1487 //TODO: Weapon tube "type_allowed_mask"
1488 //TODO: Weapon tube "load_time"
1489 if (tube.getSize() != template_tube.size)
1490 {
1491 ret += ":setTubeSize(" + string(n) + ",\"" + getMissileSizeString(tube.getSize()) + "\")";
1492 }
1493 }
1494 for(int n=0; n<MW_Count; n++)
1495 {
1496 if (weapon_storage_max[n] != ship_template->weapon_storage[n])
1497 ret += ":setWeaponStorageMax(\"" + getMissileWeaponName(EMissileWeapons(n)) + "\", " + string(weapon_storage_max[n]) + ")";
1498 if (weapon_storage[n] != ship_template->weapon_storage[n])
1499 ret += ":setWeaponStorage(\"" + getMissileWeaponName(EMissileWeapons(n)) + "\", " + string(weapon_storage[n]) + ")";
1500 }
1501
1502 // Beam weapon data
1503 for(int n=0; n<max_beam_weapons; n++)
1504 {
1505 if (beam_weapons[n].getArc() != ship_template->beams[n].getArc()
1506 || beam_weapons[n].getDirection() != ship_template->beams[n].getDirection()
1507 || beam_weapons[n].getRange() != ship_template->beams[n].getRange()
1508 || beam_weapons[n].getTurretArc() != ship_template->beams[n].getTurretArc()
1509 || beam_weapons[n].getTurretDirection() != ship_template->beams[n].getTurretDirection()
1510 || beam_weapons[n].getTurretRotationRate() != ship_template->beams[n].getTurretRotationRate()
1511 || beam_weapons[n].getCycleTime() != ship_template->beams[n].getCycleTime()
1512 || beam_weapons[n].getDamage() != ship_template->beams[n].getDamage())
1513 {
1514 ret += ":setBeamWeapon(" + string(n) + ", " + string(beam_weapons[n].getArc(), 0) + ", " + string(beam_weapons[n].getDirection(), 0) + ", " + string(beam_weapons[n].getRange(), 0) + ", " + string(beam_weapons[n].getCycleTime(), 1) + ", " + string(beam_weapons[n].getDamage(), 1) + ")";
1515 ret += ":setBeamWeaponTurret(" + string(n) + ", " + string(beam_weapons[n].getTurretArc(), 0) + ", " + string(beam_weapons[n].getTurretDirection(), 0) + ", " + string(beam_weapons[n].getTurretRotationRate(), 0) + ")";
1516 }
1517 }
1518
1519 return ret;
1520 }
1521
getMissileWeaponName(EMissileWeapons missile)1522 string getMissileWeaponName(EMissileWeapons missile)
1523 {
1524 switch(missile)
1525 {
1526 case MW_None:
1527 return "-";
1528 case MW_Homing:
1529 return "Homing";
1530 case MW_Nuke:
1531 return "Nuke";
1532 case MW_Mine:
1533 return "Mine";
1534 case MW_EMP:
1535 return "EMP";
1536 case MW_HVLI:
1537 return "HVLI";
1538 default:
1539 return "UNK: " + string(int(missile));
1540 }
1541 }
1542
getLocaleMissileWeaponName(EMissileWeapons missile)1543 string getLocaleMissileWeaponName(EMissileWeapons missile)
1544 {
1545 switch(missile)
1546 {
1547 case MW_None:
1548 return "-";
1549 case MW_Homing:
1550 return tr("missile","Homing");
1551 case MW_Nuke:
1552 return tr("missile","Nuke");
1553 case MW_Mine:
1554 return tr("missile","Mine");
1555 case MW_EMP:
1556 return tr("missile","EMP");
1557 case MW_HVLI:
1558 return tr("missile","HVLI");
1559 default:
1560 return "UNK: " + string(int(missile));
1561 }
1562 }
1563
1564
frequencyVsFrequencyDamageFactor(int beam_frequency,int shield_frequency)1565 float frequencyVsFrequencyDamageFactor(int beam_frequency, int shield_frequency)
1566 {
1567 if (beam_frequency < 0 || shield_frequency < 0)
1568 return 1.0;
1569
1570 float diff = abs(beam_frequency - shield_frequency);
1571 float f1 = sinf(Tween<float>::linear(diff, 0, SpaceShip::max_frequency, 0, M_PI * (1.2 + shield_frequency * 0.05)) + M_PI / 2);
1572 f1 = f1 * Tween<float>::easeInCubic(diff, 0, SpaceShip::max_frequency, 1.0, 0.1);
1573 f1 = Tween<float>::linear(f1, 1.0, -1.0, 0.5, 1.5);
1574 return f1;
1575 }
1576
frequencyToString(int frequency)1577 string frequencyToString(int frequency)
1578 {
1579 return string(400 + (frequency * 20)) + "THz";
1580 }
1581
1582 #include "spaceship.hpp"
1583