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(&current_impulse, 0.5);
213     registerMemberReplication(&has_warp_drive);
214     registerMemberReplication(&warp_request, 0.1);
215     registerMemberReplication(&current_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