1 #include "weaponTube.h"
2 #include "spaceObjects/missiles/EMPMissile.h"
3 #include "spaceObjects/missiles/homingMissile.h"
4 #include "spaceObjects/mine.h"
5 #include "spaceObjects/missiles/nuke.h"
6 #include "spaceObjects/missiles/hvli.h"
7 #include "spaceObjects/spaceship.h"
8 
WeaponTube()9 WeaponTube::WeaponTube()
10 {
11     parent = nullptr;
12 
13     load_time = 8.0;
14     direction = 0;
15     type_allowed_mask = (1 << MW_Count) - 1;
16     type_loaded = MW_None;
17     state = WTS_Empty;
18     delay = 0.0;
19     tube_index = 0;
20     size = MS_Medium;
21 }
22 
setParent(SpaceShip * parent)23 void WeaponTube::setParent(SpaceShip* parent)
24 {
25     assert(!this->parent);
26     this->parent = parent;
27 
28     parent->registerMemberReplication(&load_time);
29     parent->registerMemberReplication(&type_allowed_mask);
30     parent->registerMemberReplication(&direction);
31     parent->registerMemberReplication(&size);
32 
33     parent->registerMemberReplication(&type_loaded);
34     parent->registerMemberReplication(&state);
35     parent->registerMemberReplication(&delay, 0.5);
36 }
37 
getLoadTimeConfig()38 float WeaponTube::getLoadTimeConfig()
39 {
40     return load_time;
41 }
42 
setLoadTimeConfig(float load_time)43 void WeaponTube::setLoadTimeConfig(float load_time)
44 {
45     this->load_time = load_time;
46 }
47 
setIndex(int index)48 void WeaponTube::setIndex(int index)
49 {
50     tube_index = index;
51 }
52 
setDirection(float direction)53 void WeaponTube::setDirection(float direction)
54 {
55     this->direction = direction;
56 }
57 
getDirection()58 float WeaponTube::getDirection()
59 {
60     return direction;
61 }
62 
startLoad(EMissileWeapons type)63 void WeaponTube::startLoad(EMissileWeapons type)
64 {
65     if (!canLoad(type))
66         return;
67     if (state != WTS_Empty)
68         return;
69     if (parent->weapon_storage[type] <= 0)
70         return;
71 
72     state = WTS_Loading;
73     delay = load_time;
74     parent->forceMemberReplicationUpdate(&delay);
75     type_loaded = type;
76     parent->weapon_storage[type]--;
77 }
78 
startUnload()79 void WeaponTube::startUnload()
80 {
81     if (state == WTS_Loaded)
82     {
83         state = WTS_Unloading;
84         delay = load_time;
85         parent->forceMemberReplicationUpdate(&delay);
86     }
87 }
88 
fire(float target_angle)89 void WeaponTube::fire(float target_angle)
90 {
91     parent->didAnOffensiveAction();
92 
93     if (parent->docking_state != DS_NotDocking) return;
94     if (parent->current_warp > 0.0) return;
95     if (state != WTS_Loaded) return;
96 
97     if (type_loaded == MW_HVLI)
98     {
99         fire_count = 5;
100         state = WTS_Firing;
101         delay = 0.0;
102     }else{
103         spawnProjectile(target_angle);
104         state = WTS_Empty;
105         type_loaded = MW_None;
106     }
107 }
108 
spawnProjectile(float target_angle)109 void WeaponTube::spawnProjectile(float target_angle)
110 {
111     sf::Vector2f fireLocation = parent->getPosition() + sf::rotateVector(parent->ship_template->model_data->getTubePosition2D(tube_index), parent->getRotation());
112     switch(type_loaded)
113     {
114     case MW_Homing:
115         {
116             P<HomingMissile> missile = new HomingMissile();
117             missile->owner = parent;
118             missile->setFactionId(parent->getFactionId());
119             missile->target_id = parent->target_id;
120             missile->setPosition(fireLocation);
121             missile->setRotation(parent->getRotation() + direction);
122             missile->target_angle = target_angle;
123             missile->category_modifier = MissileWeaponData::convertSizeToCategoryModifier(size);
124         }
125         break;
126     case MW_Nuke:
127         {
128             P<Nuke> missile = new Nuke();
129             missile->owner = parent;
130             missile->setFactionId(parent->getFactionId());
131             missile->target_id = parent->target_id;
132             missile->setPosition(fireLocation);
133             missile->setRotation(parent->getRotation() + direction);
134             missile->target_angle = target_angle;
135             missile->category_modifier = MissileWeaponData::convertSizeToCategoryModifier(size);
136         }
137         break;
138     case MW_Mine:
139         {
140             P<Mine> missile = new Mine();
141             missile->owner = parent;
142             missile->setFactionId(parent->getFactionId());
143             missile->setPosition(fireLocation);
144             missile->setRotation(parent->getRotation() + direction);
145             missile->eject();
146         }
147         break;
148     case MW_HVLI:
149         {
150             P<HVLI> missile = new HVLI();
151             missile->owner = parent;
152             missile->setFactionId(parent->getFactionId());
153             missile->setPosition(fireLocation);
154             missile->setRotation(parent->getRotation() + direction);
155             missile->target_angle = parent->getRotation() + direction;
156             missile->category_modifier = MissileWeaponData::convertSizeToCategoryModifier(size);
157         }
158         break;
159     case MW_EMP:
160         {
161             P<EMPMissile> missile = new EMPMissile();
162             missile->owner = parent;
163             missile->setFactionId(parent->getFactionId());
164             missile->target_id = parent->target_id;
165             missile->setPosition(fireLocation);
166             missile->setRotation(parent->getRotation() + direction);
167             missile->target_angle = target_angle;
168             missile->category_modifier = MissileWeaponData::convertSizeToCategoryModifier(size);
169         }
170         break;
171     default:
172         break;
173     }
174 }
175 
canLoad(EMissileWeapons type)176 bool WeaponTube::canLoad(EMissileWeapons type)
177 {
178     if (type <= MW_None || type >= MW_Count)
179         return false;
180     if (type_allowed_mask & (1 << type))
181         return true;
182     return false;
183 }
184 
canOnlyLoad(EMissileWeapons type)185 bool WeaponTube::canOnlyLoad(EMissileWeapons type)
186 {
187     if (type_allowed_mask == (1U << type))
188         return true;
189     return false;
190 }
191 
allowLoadOf(EMissileWeapons type)192 void WeaponTube::allowLoadOf(EMissileWeapons type)
193 {
194     type_allowed_mask |= (1 << type);
195 }
196 
disallowLoadOf(EMissileWeapons type)197 void WeaponTube::disallowLoadOf(EMissileWeapons type)
198 {
199     type_allowed_mask &=~(1 << type);
200 }
201 
forceUnload()202 void WeaponTube::forceUnload()
203 {
204     if (state != WTS_Empty && type_loaded != MW_None)
205     {
206         state = WTS_Empty;
207         if (parent->weapon_storage[type_loaded] < parent->weapon_storage_max[type_loaded])
208             parent->weapon_storage[type_loaded] ++;
209         type_loaded = MW_None;
210     }
211 }
212 
update(float delta)213 void WeaponTube::update(float delta)
214 {
215     if (delay > 0.0)
216     {
217         delay -= delta * parent->getSystemEffectiveness(SYS_MissileSystem);
218     }else{
219         switch(state)
220         {
221         case WTS_Loading:
222             state = WTS_Loaded;
223             break;
224         case WTS_Unloading:
225             state = WTS_Empty;
226             if (parent->weapon_storage[type_loaded] < parent->weapon_storage_max[type_loaded])
227                 parent->weapon_storage[type_loaded] ++;
228             type_loaded = MW_None;
229             break;
230         case WTS_Firing:
231             if (game_server)
232             {
233                 spawnProjectile(0);
234 
235                 fire_count -= 1;
236                 if (fire_count > 0)
237                 {
238                     delay = 1.5;
239                 }
240                 else
241                 {
242                     state = WTS_Empty;
243                     type_loaded = MW_None;
244                 }
245             }
246             break;
247         default:
248             break;
249         }
250     }
251 }
252 
isEmpty()253 bool WeaponTube::isEmpty()
254 {
255     return state == WTS_Empty;
256 }
257 
isLoaded()258 bool WeaponTube::isLoaded()
259 {
260     return state == WTS_Loaded;
261 }
262 
isLoading()263 bool WeaponTube::isLoading()
264 {
265     return state == WTS_Loading;
266 }
267 
isUnloading()268 bool WeaponTube::isUnloading()
269 {
270     return state == WTS_Unloading;
271 }
272 
isFiring()273 bool WeaponTube::isFiring()
274 {
275     return state == WTS_Firing;
276 }
277 
getLoadProgress()278 float WeaponTube::getLoadProgress()
279 {
280     return 1.0 - delay / load_time;
281 }
282 
getUnloadProgress()283 float WeaponTube::getUnloadProgress()
284 {
285     return delay / load_time;
286 }
287 
getLoadType()288 EMissileWeapons WeaponTube::getLoadType()
289 {
290     return type_loaded;
291 }
292 
getTubeName()293 string WeaponTube::getTubeName()
294 {
295     if (std::abs(sf::angleDifference(0.0f, direction)) <= 45)
296         return tr("tube","Front");
297     if (std::abs(sf::angleDifference(90.0f, direction)) < 45)
298         return tr("tube","Right");
299     if (std::abs(sf::angleDifference(-90.0f, direction)) < 45)
300         return tr("tube","Left");
301     if (std::abs(sf::angleDifference(180.0f, direction)) <= 45)
302         return tr("tube","Rear");
303     return "?" + string(direction);
304 }
305 
calculateFiringSolution(P<SpaceObject> target)306 float WeaponTube::calculateFiringSolution(P<SpaceObject> target)
307 {
308     if (!target)
309         return std::numeric_limits<float>::infinity();
310     const MissileWeaponData& data = MissileWeaponData::getDataFor(type_loaded);
311     if (data.turnrate == 0.0f)  //If the missile cannot turn, we cannot find a firing solution.
312         return std::numeric_limits<float>::infinity();
313 
314     sf::Vector2f target_position = target->getPosition();
315     sf::Vector2f target_velocity = target->getVelocity();
316     float target_velocity_length = sf::length(target_velocity);
317     float missile_angle = sf::vector2ToAngle(target_position - parent->getPosition());
318     float turn_radius = ((360.0f / data.turnrate) * data.speed) / (2.0f * M_PI);
319     float missile_exit_angle = parent->getRotation() + direction;
320 
321     for(int iterations=0; iterations<10; iterations++)
322     {
323         float angle_diff = sf::angleDifference(missile_angle, missile_exit_angle);
324 
325         float left_or_right = 90;
326         if (angle_diff > 0)
327             left_or_right = -90;
328 
329         sf::Vector2f turn_center = parent->getPosition() + sf::vector2FromAngle(missile_exit_angle + left_or_right) * turn_radius;
330         sf::Vector2f turn_exit = turn_center + sf::vector2FromAngle(missile_angle - left_or_right) * turn_radius;
331         if (target_velocity_length < 1.0f)
332         {
333             //If the target is almost standing still, just target the position directly instead of using the velocity of the target in the calculations.
334             float time_missile = sf::length(turn_exit - target_position) / data.speed;
335             sf::Vector2f interception = turn_exit + sf::vector2FromAngle(missile_angle) * data.speed * time_missile;
336             if ((interception - target_position) < target->getRadius() / 2)
337                 return missile_angle;
338             missile_angle = sf::vector2ToAngle(target_position - turn_exit);
339         }
340         else
341         {
342             sf::Vector2f missile_velocity = sf::vector2FromAngle(missile_angle) * data.speed;
343             //Calculate the position where missile and the target will cross each others path.
344             sf::Vector2f intersection = sf::lineLineIntersection(target_position, target_position + target_velocity, turn_exit, turn_exit + missile_velocity);
345             //Calculate the time it will take for the target and missile to reach the intersection
346             float turn_time = fabs(angle_diff) / data.turnrate;
347             float time_target = sf::length((target_position - intersection)) / target_velocity_length;
348             float time_missile = sf::length(turn_exit - intersection) / data.speed + turn_time;
349             //Calculate the time in which the radius will be on the intersection, to know in which time range we need to hit.
350             float time_radius = (target->getRadius() / 2.0) / target_velocity_length;//TODO: This value could be improved, as it is allowed to be bigger when the angle between the missile and the ship is low
351             // When both the missile and the target are at the same position at the same time, we can take a shot!
352             if (fabsf(time_target - time_missile) < time_radius)
353                 return missile_angle;
354 
355             //When we cannot hit the target with this setup yet. Calculate a new intersection target, and aim for that.
356             float guessed_impact_time = (time_target * target_velocity_length / (target_velocity_length + data.speed)) + (time_missile * data.speed / (target_velocity_length + data.speed));
357             sf::Vector2f new_target_position = target_position + target_velocity * guessed_impact_time;
358             missile_angle = sf::vector2ToAngle(new_target_position - turn_exit);
359         }
360     }
361     return std::numeric_limits<float>::infinity();
362 }
363 
setSize(EMissileSizes size)364 void WeaponTube::setSize(EMissileSizes size)
365 {
366     this->size = size;
367 }
368 
getSize()369 EMissileSizes WeaponTube::getSize()
370 {
371     return size;
372 }
373