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