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