1 #include "shipTemplateBasedObject.h"
2 
3 #include "scriptInterface.h"
4 
5 #include "i18n.h"
6 
REGISTER_SCRIPT_SUBCLASS_NO_CREATE(ShipTemplateBasedObject,SpaceObject)7 REGISTER_SCRIPT_SUBCLASS_NO_CREATE(ShipTemplateBasedObject, SpaceObject)
8 {
9     /// Set the template to be used for this ship or station. Templates define hull/shields/looks etc.
10     /// Examples:
11     /// CpuShip():setTemplate("Phobos T3")
12     /// PlayerSpaceship():setTemplate("Phobos M3P")
13     /// SpaceStation():setTemplate("Large Station")
14     /// WARNING: Using a string that is not a valid template name lets the game crash! This is case-sensitive.
15     /// See `scripts/shipTemplates.lua` for the existing templates.
16     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setTemplate);
17     /// [Depricated]
18     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setShipTemplate);
19     /// Set the class name of this object. Normally the class name is copied from the template name (Ex "Cruiser") but you can override it with this function.
20     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setTypeName);
21     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getTypeName);
22     /// Get the current amount of hull
23     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getHull);
24     /// Get the maximum hull value
25     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getHullMax);
26     /// Set the current hull value, note that setting this to 0 does not destroy the station.
27     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setHull);
28     /// Set the maximum amount of hull for this station. Stations never repair hull damage, so this only effects the percentage displays
29     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setHullMax);
30     /// Set whether the object can be destroyed.
31     /// Requires a Boolean value.
32     /// Example: ship:setCanBeDestroyed(true)
33     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setCanBeDestroyed);
34     /// Get whether the object can be destroyed.
35     /// Returns a Boolean value.
36     /// Example: ship:getCanBeDestroyed()
37     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getCanBeDestroyed);
38     /// Get the specified shield's current level.
39     /// Requires an integer index value.
40     /// Returns a float value.
41     /// Example to get shield level on front shields of a ship with two shields:
42     ///     ship:getShieldLevel(0)
43     /// Rear shields: ship:getShieldLevel(1)
44     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getShieldLevel);
45     /// Get the number of shields on this object.
46     /// For example, a ship with 1 shield count has a single shield covering
47     /// all angles, a ship with 2 covers front and back, etc.
48     /// Returns an integer count.
49     /// Example: ship:getShieldCount()
50     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getShieldCount);
51     /// Get the maxium shield level.
52     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getShieldMax);
53     /// Set the current amount of shields.
54     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setShields);
55     /// Set the maximum shield level. Note that this does low the current shield level when the max becomes lower, but it does not increase the shield level.
56     /// A seperate call to setShield is needed for that.
57     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setShieldsMax);
58     /// Set the icon to be used for this object on the radar.
59     /// For example, station:setRadarTrace("RadarArrow.png") will show an arrow instead of a dot for this station.
60     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setRadarTrace);
61     /// Set the sound file to be used for this object's impulse engines.
62     /// Requires a string for a filename relative to the resources path.
63     /// Example: setImpulseSoundFile("engine.wav")
64     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setImpulseSoundFile);
65     /// Are the shields online or not. Currently always returns true except for player ships, as only players can turn off shields.
66     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getShieldsActive);
67 
68     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getSharesEnergyWithDocked);
69     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setSharesEnergyWithDocked);
70     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getRepairDocked);
71     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setRepairDocked);
72     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getRestocksScanProbes);
73     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setRestocksScanProbes);
74     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getRestocksMissilesDocked);
75     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setRestocksMissilesDocked);
76 
77     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getLongRangeRadarRange);
78     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getShortRangeRadarRange);
79     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setLongRangeRadarRange);
80     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setShortRangeRadarRange);
81 
82     /// [Depricated]
83     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getFrontShield);
84     /// [Depricated]
85     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getFrontShieldMax);
86     /// [Depricated]
87     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setFrontShield);
88     /// [Depricated]
89     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setFrontShieldMax);
90     /// [Depricated]
91     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getRearShield);
92     /// [Depricated]
93     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, getRearShieldMax);
94     /// [Depricated]
95     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setRearShield);
96     /// [Depricated]
97     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, setRearShieldMax);
98     /// Set a function that will be called if the object is taking damage.
99     /// First argument given to the function will be the object taking damage, the second the instigator SpaceObject (or nil).
100     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, onTakingDamage);
101     /// Set a function that will be called if the object is destroyed by taking damage.
102     /// First argument given to the function will be the object taking damage, the second the instigator SpaceObject that gave the final blow (or nil).
103     REGISTER_SCRIPT_CLASS_FUNCTION(ShipTemplateBasedObject, onDestruction);
104 }
105 
ShipTemplateBasedObject(float collision_range,string multiplayer_name,float multiplayer_significant_range)106 ShipTemplateBasedObject::ShipTemplateBasedObject(float collision_range, string multiplayer_name, float multiplayer_significant_range)
107 : SpaceObject(collision_range, multiplayer_name, multiplayer_significant_range)
108 {
109     setCollisionPhysics(true, true);
110 
111     shield_count = 0;
112     for(int n=0; n<max_shield_count; n++)
113     {
114         shield_level[n] = 0.0;
115         shield_max[n] = 0.0;
116         shield_hit_effect[n] = 0.0;
117     }
118     hull_strength = hull_max = 100.0;
119 
120     long_range_radar_range = 30000.0f;
121     short_range_radar_range = 5000.0f;
122 
123     registerMemberReplication(&template_name);
124     registerMemberReplication(&type_name);
125     registerMemberReplication(&shield_count);
126     for(int n=0; n<max_shield_count; n++)
127     {
128         registerMemberReplication(&shield_level[n], 0.5);
129         registerMemberReplication(&shield_max[n]);
130         registerMemberReplication(&shield_hit_effect[n], 0.5);
131     }
132     registerMemberReplication(&radar_trace);
133     registerMemberReplication(&impulse_sound_file);
134     registerMemberReplication(&hull_strength, 0.5);
135     registerMemberReplication(&hull_max);
136     registerMemberReplication(&long_range_radar_range, 0.5);
137     registerMemberReplication(&short_range_radar_range, 0.5);
138 
139     callsign = "[" + string(getMultiplayerId()) + "]";
140 
141     can_be_destroyed = true;
142     registerMemberReplication(&can_be_destroyed);
143 }
144 
drawShieldsOnRadar(sf::RenderTarget & window,sf::Vector2f position,float scale,float rotation,float sprite_scale,bool show_levels)145 void ShipTemplateBasedObject::drawShieldsOnRadar(sf::RenderTarget& window, sf::Vector2f position, float scale, float rotation, float sprite_scale, bool show_levels)
146 {
147     if (!getShieldsActive())
148         return;
149     if (shield_count == 1)
150     {
151         sf::Sprite objectSprite;
152         textureManager.setTexture(objectSprite, "shield_circle.png");
153         objectSprite.setPosition(position);
154 
155         objectSprite.setScale(sprite_scale * 0.25f * 1.5f, sprite_scale * 0.25f * 1.5f);
156 
157         sf::Color color = sf::Color(255, 255, 255, 64);
158         if (show_levels)
159         {
160             float level = shield_level[0] / shield_max[0];
161             color = Tween<sf::Color>::linear(level, 1.0f, 0.0f, sf::Color(128, 128, 255, 128), sf::Color(255, 0, 0, 64));
162         }
163         if (shield_hit_effect[0] > 0.0)
164         {
165             color = Tween<sf::Color>::linear(shield_hit_effect[0], 0.0f, 1.0f, color, sf::Color(255, 0, 0, 128));
166         }
167         objectSprite.setColor(color);
168 
169         window.draw(objectSprite);
170     }else if (shield_count > 1) {
171         float direction = getRotation()-rotation;
172         float arc = 360.0f / float(shield_count);
173 
174         for(int n=0; n<shield_count; n++)
175         {
176             sf::Color color = sf::Color(255, 255, 255, 64);
177             if (show_levels)
178             {
179                 float level = shield_level[n] / shield_max[n];
180                 color = Tween<sf::Color>::linear(level, 1.0f, 0.0f, sf::Color(128, 128, 255, 128), sf::Color(255, 0, 0, 64));
181             }
182             if (shield_hit_effect[n] > 0.0)
183             {
184                 color = Tween<sf::Color>::linear(shield_hit_effect[n], 0.0f, 1.0f, color, sf::Color(255, 0, 0, 128));
185             }
186             sf::VertexArray a(sf::TrianglesFan, 4);
187             sf::Vector2f delta_a = sf::vector2FromAngle(direction - arc / 2.0f);
188             sf::Vector2f delta_b = sf::vector2FromAngle(direction);
189             sf::Vector2f delta_c = sf::vector2FromAngle(direction + arc / 2.0f);
190             a[0].position = position + delta_b * sprite_scale * 32.0f * 0.05f;
191             a[1].position = a[0].position + delta_a * sprite_scale * 32.0f * 1.5f;
192             a[2].position = a[0].position + delta_b * sprite_scale * 32.0f * 1.5f;
193             a[3].position = a[0].position + delta_c * sprite_scale * 32.0f * 1.5f;
194             a[0].texCoords = sf::Vector2f(128, 128);
195             a[1].texCoords = sf::Vector2f(128, 128) + delta_a * 128.0f;
196             a[2].texCoords = sf::Vector2f(128, 128) + delta_b * 128.0f;
197             a[3].texCoords = sf::Vector2f(128, 128) + delta_c * 128.0f;
198             a[0].color = color;
199             a[1].color = color;
200             a[2].color = color;
201             a[3].color = color;
202             window.draw(a, textureManager.getTexture("shield_circle.png"));
203 
204             direction += arc;
205         }
206     }
207 }
208 
209 #if FEATURE_3D_RENDERING
draw3DTransparent()210 void ShipTemplateBasedObject::draw3DTransparent()
211 {
212     if (shield_count < 1)
213         return;
214 
215     float angle = 0.0;
216     float arc = 360.0f / shield_count;
217     for(int n = 0; n<shield_count; n++)
218     {
219         if (shield_hit_effect[n] > 0)
220         {
221             if (shield_count > 1)
222             {
223                 model_info.renderShield((shield_level[n] / shield_max[n]) * shield_hit_effect[n], angle);
224             }else{
225                 model_info.renderShield((shield_level[n] / shield_max[n]) * shield_hit_effect[n]);
226             }
227         }
228         angle += arc;
229     }
230 }
231 #endif//FEATURE_3D_RENDERING
232 
update(float delta)233 void ShipTemplateBasedObject::update(float delta)
234 {
235     // All ShipTemplateBasedObjects should have a valid template.
236     // If this object lacks a template, or has an inconsistent template...
237     if (!ship_template || ship_template->getName() != template_name)
238     {
239         // Attempt to align the object's template to its reported template name.
240         ship_template = ShipTemplate::getTemplate(template_name);
241 
242         // If the template still doesn't exist, destroy the object.
243         if (!ship_template)
244         {
245             LOG(ERROR) << "ShipTemplateBasedObject with ID " << string(getMultiplayerId()) << " lacked a template, so it was destroyed.";
246             destroy();
247             return;
248         }
249 
250         // If it does exist, set up its collider and model.
251         ship_template->setCollisionData(this);
252         model_info.setData(ship_template->model_data);
253     }
254 
255     for(int n=0; n<shield_count; n++)
256     {
257         if (shield_level[n] < shield_max[n])
258         {
259             shield_level[n] = std::min(shield_max[n], shield_level[n] + delta * getShieldRechargeRate(n));
260         }
261         if (shield_hit_effect[n] > 0)
262         {
263             shield_hit_effect[n] -= delta;
264         }
265     }
266 }
267 
getGMInfo()268 std::unordered_map<string, string> ShipTemplateBasedObject::getGMInfo()
269 {
270     std::unordered_map<string, string> ret;
271     ret[trMark("gm_info", "CallSign")] = callsign;
272     ret[trMark("gm_info", "Type")] = type_name;
273     ret[trMark("gm_info", "Hull")] = string(hull_strength) + "/" + string(hull_max);
274     for(int n=0; n<shield_count; n++)
275     {
276         // Note, translators: this is a compromise.
277         // Because of the deferred translation the variable parameter can't be forwarded, so it'll always be a suffix.
278         ret[trMark("gm_info", "Shield") + string(n + 1)] = string(shield_level[n]) + "/" + string(shield_max[n]);
279     }
280     return ret;
281 }
282 
hasShield()283 bool ShipTemplateBasedObject::hasShield()
284 {
285     for(int n=0; n<shield_count; n++)
286     {
287         if (shield_level[n] < shield_max[n] / 50)
288             return false;
289     }
290     return true;
291 }
292 
takeDamage(float damage_amount,DamageInfo info)293 void ShipTemplateBasedObject::takeDamage(float damage_amount, DamageInfo info)
294 {
295     if (shield_count > 0 && getShieldsActive())
296     {
297         float angle = sf::angleDifference(getRotation(), sf::vector2ToAngle(info.location - getPosition()));
298         if (angle < 0)
299             angle += 360.0f;
300         float arc = 360.0f / float(shield_count);
301         int shield_index = int((angle + arc / 2.0f) / arc);
302         shield_index %= shield_count;
303 
304         float shield_damage = damage_amount * getShieldDamageFactor(info, shield_index);
305         damage_amount -= shield_level[shield_index];
306         shield_level[shield_index] -= shield_damage;
307         if (shield_level[shield_index] < 0)
308         {
309             shield_level[shield_index] = 0.0;
310         } else {
311             shield_hit_effect[shield_index] = 1.0;
312         }
313         if (damage_amount < 0.0)
314         {
315             damage_amount = 0.0;
316         }
317     }
318 
319     if (info.type != DT_EMP && damage_amount > 0.0)
320     {
321         takeHullDamage(damage_amount, info);
322     }
323 
324     if (hull_strength > 0)
325     {
326         if (on_taking_damage.isSet())
327         {
328             if (info.instigator)
329             {
330                 on_taking_damage.call<void>(P<ShipTemplateBasedObject>(this), P<SpaceObject>(info.instigator));
331             } else {
332                 on_taking_damage.call<void>(P<ShipTemplateBasedObject>(this));
333             }
334         }
335     }
336 }
337 
takeHullDamage(float damage_amount,DamageInfo & info)338 void ShipTemplateBasedObject::takeHullDamage(float damage_amount, DamageInfo& info)
339 {
340     hull_strength -= damage_amount;
341     if (hull_strength <= 0.0 && !can_be_destroyed)
342     {
343         hull_strength = 1;
344     }
345     if (hull_strength <= 0.0)
346     {
347         destroyedByDamage(info);
348         if (on_destruction.isSet())
349         {
350             if (info.instigator)
351             {
352                 on_destruction.call<void>(P<ShipTemplateBasedObject>(this), P<SpaceObject>(info.instigator));
353             } else {
354                 on_destruction.call<void>(P<ShipTemplateBasedObject>(this));
355             }
356         }
357         destroy();
358     }
359 }
360 
getShieldDamageFactor(DamageInfo & info,int shield_index)361 float ShipTemplateBasedObject::getShieldDamageFactor(DamageInfo& info, int shield_index)
362 {
363     return 1.0f;
364 }
365 
getShieldRechargeRate(int shield_index)366 float ShipTemplateBasedObject::getShieldRechargeRate(int shield_index)
367 {
368     return 0.3;
369 }
370 
setTemplate(string template_name)371 void ShipTemplateBasedObject::setTemplate(string template_name)
372 {
373     P<ShipTemplate> new_ship_template = ShipTemplate::getTemplate(template_name);
374     this->template_name = template_name;
375     ship_template = new_ship_template;
376     type_name = template_name;
377 
378     hull_strength = hull_max = ship_template->hull;
379     shield_count = ship_template->shield_count;
380     for(int n=0; n<shield_count; n++)
381         shield_level[n] = shield_max[n] = ship_template->shield_level[n];
382 
383     // Set the ship's radar ranges.
384     long_range_radar_range = ship_template->long_range_radar_range;
385     short_range_radar_range = ship_template->short_range_radar_range;
386 
387     radar_trace = ship_template->radar_trace;
388     impulse_sound_file = ship_template->impulse_sound_file;
389 
390     shares_energy_with_docked = ship_template->shares_energy_with_docked;
391     repair_docked = ship_template->repair_docked;
392 
393     ship_template->setCollisionData(this);
394     model_info.setData(ship_template->model_data);
395 
396     //Call the virtual applyTemplateValues function so subclasses can get extra values from the ship templates.
397     applyTemplateValues();
398 }
399 
setShields(std::vector<float> amounts)400 void ShipTemplateBasedObject::setShields(std::vector<float> amounts)
401 {
402     for(int n=0; n<std::min(int(amounts.size()), shield_count); n++)
403     {
404         shield_level[n] = amounts[n];
405     }
406 }
407 
setShieldsMax(std::vector<float> amounts)408 void ShipTemplateBasedObject::setShieldsMax(std::vector<float> amounts)
409 {
410     shield_count = std::min(max_shield_count, int(amounts.size()));
411     for(int n=0; n<shield_count; n++)
412     {
413         shield_max[n] = amounts[n];
414         shield_level[n] = std::min(shield_level[n], shield_max[n]);
415     }
416 }
417 
getShieldSystemForShieldIndex(int index)418 ESystem ShipTemplateBasedObject::getShieldSystemForShieldIndex(int index)
419 {
420     if (shield_count < 2)
421         return SYS_FrontShield;
422     float angle = index * 360.0 / shield_count;
423     if (std::abs(sf::angleDifference(angle, 0.0f)) < 90)
424         return SYS_FrontShield;
425     return SYS_RearShield;
426 }
427 
onTakingDamage(ScriptSimpleCallback callback)428 void ShipTemplateBasedObject::onTakingDamage(ScriptSimpleCallback callback)
429 {
430     this->on_taking_damage = callback;
431 }
432 
onDestruction(ScriptSimpleCallback callback)433 void ShipTemplateBasedObject::onDestruction(ScriptSimpleCallback callback)
434 {
435     this->on_destruction = callback;
436 }
437 
getShieldDataString()438 string ShipTemplateBasedObject::getShieldDataString()
439 {
440     string data = "";
441     for(int n=0; n<shield_count; n++)
442     {
443         if (n > 0)
444             data += ":";
445         data += string(int(shield_level[n]));
446     }
447     return data;
448 }
449