Line data Source code
1 : /**
2 : * This class holds the functions regarding entities being visible on
3 : * another entity, but tied to their parents location.
4 : */
5 : class TurretHolder
6 : {
7 : Init()
8 : {
9 2 : this.turretPoints = [];
10 :
11 2 : let points = this.template.TurretPoints;
12 2 : for (let point in points)
13 5 : this.turretPoints.push({
14 : "name": point,
15 : "offset": {
16 : "x": +points[point].X,
17 : "y": +points[point].Y,
18 : "z": +points[point].Z
19 : },
20 : "allowedClasses": points[point].AllowedClasses?._string,
21 : "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null,
22 : "entity": null,
23 : "template": points[point].Template,
24 : "ejectable": "Ejectable" in points[point] ? points[point].Ejectable == "true" : true
25 : });
26 : }
27 :
28 : /**
29 : * Add a subunit as specified in the template.
30 : * This function creates an entity and places it on the turret point.
31 : *
32 : * @param {Object} turretPoint - A turret point to (re)create the predefined subunit for.
33 : *
34 : * @return {boolean} - Whether the turret creation has succeeded.
35 : */
36 : CreateSubunit(turretPointName)
37 : {
38 0 : let turretPoint = this.TurretPointByName(turretPointName);
39 0 : if (!turretPoint || turretPoint.entity ||
40 : this.initTurrets?.has(turretPointName) ||
41 : this.reservedTurrets?.has(turretPointName))
42 0 : return false;
43 :
44 0 : let ent = Engine.AddEntity(turretPoint.template);
45 :
46 0 : let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
47 0 : if (cmpOwnership)
48 : {
49 0 : let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
50 0 : cmpEntOwnership?.SetOwner(cmpOwnership.GetOwner());
51 : }
52 :
53 0 : let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
54 0 : return cmpTurretable?.OccupyTurret(this.entity, turretPoint.name, turretPoint.ejectable) || Engine.DestroyEntity(ent);
55 : }
56 :
57 : /**
58 : * @param {string} name - The name of a turret point to reserve, e.g. for promotion.
59 : */
60 : SetReservedTurretPoint(name)
61 : {
62 0 : if (!this.reservedTurrets)
63 0 : this.reservedTurrets = new Set();
64 0 : this.reservedTurrets.add(name);
65 : }
66 :
67 : /**
68 : * @return {Object[]} - An array of the turret points this entity has.
69 : */
70 : GetTurretPoints()
71 : {
72 0 : return this.turretPoints;
73 : }
74 :
75 : /**
76 : * @param {number} entity - The entity to check for.
77 : * @param {Object} turretPoint - The turret point to use.
78 : *
79 : * @return {boolean} - Whether the entity is allowed to occupy the specified turret point.
80 : */
81 : AllowedToOccupyTurretPoint(entity, turretPoint)
82 : {
83 30 : if (!turretPoint || turretPoint.entity)
84 3 : return false;
85 :
86 27 : if (!IsOwnedByMutualAllyOfEntity(entity, this.entity))
87 0 : return false;
88 :
89 27 : if (!turretPoint.allowedClasses)
90 17 : return true;
91 :
92 10 : let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
93 10 : return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses);
94 : }
95 :
96 : /**
97 : * @param {number} entity - The entity to check for.
98 : * @return {boolean} - Whether the entity is allowed to occupy any turret point.
99 : */
100 : CanOccupy(entity)
101 : {
102 8 : return !!this.turretPoints.find(turretPoint => this.AllowedToOccupyTurretPoint(entity, turretPoint));
103 : }
104 :
105 : /**
106 : * Occupy a turret point with the given entity.
107 : * @param {number} entity - The entity to use.
108 : * @param {Object} requestedTurretPoint - Optionally the specific turret point to occupy.
109 : *
110 : * @return {boolean} - Whether the occupation was successful.
111 : */
112 : OccupyTurretPoint(entity, requestedTurretPoint)
113 : {
114 10 : let cmpPositionOccupant = Engine.QueryInterface(entity, IID_Position);
115 10 : if (!cmpPositionOccupant)
116 0 : return false;
117 :
118 10 : let cmpPositionSelf = Engine.QueryInterface(this.entity, IID_Position);
119 10 : if (!cmpPositionSelf)
120 0 : return false;
121 :
122 10 : if (this.OccupiesTurretPoint(entity))
123 0 : return false;
124 :
125 : let turretPoint;
126 10 : if (requestedTurretPoint)
127 : {
128 6 : if (this.AllowedToOccupyTurretPoint(entity, requestedTurretPoint))
129 4 : turretPoint = requestedTurretPoint;
130 : }
131 : else
132 5 : turretPoint = this.turretPoints.find(turret => !turret.entity && this.AllowedToOccupyTurretPoint(entity, turret));
133 :
134 10 : if (!turretPoint)
135 2 : return false;
136 :
137 8 : turretPoint.entity = entity;
138 :
139 : // Angle of turrets:
140 : // Renamed entities (turretPoint != undefined) should keep their angle.
141 : // Otherwise if an angle is given in the turretPoint, use it.
142 : // If no such angle given (usually walls for which outside/inside not well defined), we keep
143 : // the current angle as it was used for garrisoning and thus quite often was from inside to
144 : // outside, except when garrisoning from outWorld where we take as default PI.
145 8 : if (!turretPoint && turretPoint.angle != null)
146 0 : cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + turretPoint.angle);
147 8 : else if (!turretPoint && !cmpPosition.IsInWorld())
148 0 : cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + Math.PI);
149 :
150 8 : cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset);
151 :
152 8 : Engine.PostMessage(this.entity, MT_TurretsChanged, {
153 : "added": [entity],
154 : "removed": []
155 : });
156 :
157 8 : return true;
158 : }
159 :
160 : /**
161 : * @param {number} entity - The entityID of the entity.
162 : * @param {String} turretName - The name of the turret point to occupy.
163 : * @return {boolean} - Whether the occupation has succeeded.
164 : */
165 : OccupyNamedTurretPoint(entity, turretName)
166 : {
167 6 : return this.OccupyTurretPoint(entity, this.TurretPointByName(turretName));
168 : }
169 :
170 : /**
171 : * @param {string} turretPointName - The name of the requested turret point.
172 : * @return {Object} - The requested turret point.
173 : */
174 : TurretPointByName(turretPointName)
175 : {
176 14 : return this.turretPoints.find(turret => turret.name == turretPointName);
177 : }
178 :
179 : /**
180 : * Remove the entity from a turret.
181 : * @param {number} entity - The specific entity to eject.
182 : * @param {boolean} forced - Whether ejection is forced (e.g. due to death or renaming).
183 : * @param {Object} turret - Optionally the turret to abandon.
184 : *
185 : * @return {boolean} - Whether the entity succesfully left us.
186 : */
187 : LeaveTurretPoint(entity, forced, requestedTurretPoint)
188 : {
189 : let turretPoint;
190 8 : if (requestedTurretPoint)
191 : {
192 2 : if (requestedTurretPoint.entity == entity)
193 1 : turretPoint = requestedTurretPoint;
194 : }
195 : else
196 6 : turretPoint = this.GetOccupiedTurretPoint(entity);
197 :
198 8 : if (!turretPoint || (!turretPoint.ejectable && !forced))
199 2 : return false;
200 :
201 6 : turretPoint.entity = null;
202 :
203 6 : Engine.PostMessage(this.entity, MT_TurretsChanged, {
204 : "added": [],
205 : "removed": [entity]
206 : });
207 :
208 6 : return true;
209 : }
210 :
211 : /**
212 : * @param {number} entity - The entity's id.
213 : * @param {Object} turret - Optionally the turret to check.
214 : *
215 : * @return {boolean} - Whether the entity is positioned on a turret of this entity.
216 : */
217 : OccupiesTurretPoint(entity, requestedTurretPoint)
218 : {
219 14 : return requestedTurretPoint ? requestedTurretPoint.entity == entity :
220 : !!this.GetOccupiedTurretPoint(entity);
221 : }
222 :
223 : /**
224 : * @param {number} entity - The entity's id.
225 : * @return {Object} - The turret this entity is positioned on, if applicable.
226 : */
227 : GetOccupiedTurretPoint(entity)
228 : {
229 45 : return this.turretPoints.find(turretPoint => turretPoint.entity == entity);
230 : }
231 :
232 : /**
233 : * @param {number} entity - The entity's id.
234 : * @return {Object} - The turret this entity is positioned on, if applicable.
235 : */
236 : GetOccupiedTurretPointName(entity)
237 : {
238 3 : let turret = this.GetOccupiedTurretPoint(entity);
239 3 : return turret ? turret.name : "";
240 : }
241 :
242 : /**
243 : * @return {number[]} - The turretted entityIDs.
244 : */
245 : GetEntities()
246 : {
247 3 : let entities = [];
248 3 : for (let turretPoint of this.turretPoints)
249 6 : if (turretPoint.entity)
250 3 : entities.push(turretPoint.entity);
251 3 : return entities;
252 : }
253 :
254 : /**
255 : * @return {boolean} - Whether all the turret points are occupied.
256 : */
257 : IsFull()
258 : {
259 0 : return !!this.turretPoints.find(turretPoint => turretPoint.entity == null);
260 : }
261 :
262 : /**
263 : * @return {Object} - Max and min ranges at which entities can occupy any turret.
264 : */
265 : LoadingRange()
266 : {
267 0 : return { "min": 0, "max": +(this.template.LoadingRange || 2) };
268 : }
269 :
270 : /**
271 : * @param {number} ent - The entity ID of the turret to be potentially picked up.
272 : * @return {boolean} - Whether this entity can pick the specified entity up.
273 : */
274 : CanPickup(ent)
275 : {
276 0 : if (!this.template.Pickup || this.IsFull())
277 0 : return false;
278 0 : let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
279 0 : return !!cmpOwner && IsOwnedByPlayer(cmpOwner.GetOwner(), ent);
280 : }
281 :
282 : /**
283 : * @param {number[]} entities - The entities to ask to leave or to kill.
284 : */
285 : EjectOrKill(entities)
286 : {
287 0 : let removedEntities = [];
288 0 : for (let entity of entities)
289 : {
290 0 : let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
291 0 : if (!cmpTurretable || !cmpTurretable.LeaveTurret(true))
292 : {
293 0 : let cmpHealth = Engine.QueryInterface(entity, IID_Health);
294 0 : if (cmpHealth)
295 0 : cmpHealth.Kill();
296 : else
297 0 : Engine.DestroyEntity(entity);
298 0 : removedEntities.push(entity);
299 : }
300 : }
301 0 : if (removedEntities.length)
302 0 : Engine.PostMessage(this.entity, MT_TurretsChanged, {
303 : "added": [],
304 : "removed": removedEntities
305 : });
306 : }
307 :
308 : /**
309 : * Sets an init turret, present from game start. (E.g. set in Atlas.)
310 : * @param {String} turretName - The name of the turret point to be used.
311 : * @param {number} entity - The entity-ID to be placed.
312 : */
313 : SetInitEntity(turretName, entity)
314 : {
315 2 : if (!this.initTurrets)
316 1 : this.initTurrets = new Map();
317 :
318 2 : if (this.initTurrets.has(turretName))
319 0 : warn("The turret position " + turretName + " of entity " +
320 : this.entity + " is already set! Overwriting.");
321 :
322 2 : this.initTurrets.set(turretName, entity);
323 : }
324 :
325 : /**
326 : * Update list of turreted entities when a game inits.
327 : */
328 : OnGlobalSkirmishReplacerReplaced(msg)
329 : {
330 0 : if (!this.initTurrets)
331 0 : return;
332 :
333 0 : if (msg.entity == this.entity)
334 : {
335 0 : let cmpTurretHolder = Engine.QueryInterface(msg.newentity, IID_TurretHolder);
336 0 : if (cmpTurretHolder)
337 0 : cmpTurretHolder.initTurrets = this.initTurrets;
338 : }
339 : else
340 : {
341 0 : let entityIndex = this.initTurrets.indexOf(msg.entity);
342 0 : if (entityIndex != -1)
343 0 : this.initTurrets[entityIndex] = msg.newentity;
344 : }
345 : }
346 :
347 : /**
348 : * Initialise turreted units.
349 : */
350 : OnGlobalInitGame(msg)
351 : {
352 1 : if (!this.initTurrets)
353 0 : return;
354 :
355 1 : for (let [turretPointName, entity] of this.initTurrets)
356 : {
357 2 : let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
358 2 : if (!cmpTurretable || !cmpTurretable.OccupyTurret(this.entity, turretPointName, this.TurretPointByName(turretPointName).ejectable))
359 0 : warn("Entity " + entity + " could not occupy the turret point " +
360 : turretPointName + " of turret holder " + this.entity + ".");
361 : }
362 :
363 1 : delete this.initTurrets;
364 : }
365 :
366 : /**
367 : * @param {Object} msg - { "entity": number, "newentity": number }.
368 : */
369 : OnEntityRenamed(msg)
370 : {
371 0 : for (let entity of this.GetEntities())
372 : {
373 0 : let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
374 0 : if (!cmpTurretable)
375 0 : continue;
376 0 : let currentPoint = this.GetOccupiedTurretPointName(entity);
377 0 : cmpTurretable.LeaveTurret(true);
378 0 : cmpTurretable.OccupyTurret(msg.newentity, currentPoint);
379 : }
380 : }
381 :
382 : /**
383 : * @param {Object} msg - { "entity": number, "from": number, "to": number }.
384 : */
385 : OnOwnershipChanged(msg)
386 : {
387 0 : if (msg.to === INVALID_PLAYER)
388 : {
389 0 : this.EjectOrKill(this.GetEntities());
390 0 : return;
391 : }
392 0 : for (let point of this.turretPoints)
393 : {
394 : // If we were created, create any subunits now.
395 : // This has to be done here (instead of on Init)
396 : // for Ownership ought to be initialised.
397 0 : if (point.template && msg.from === INVALID_PLAYER)
398 : {
399 0 : this.CreateSubunit(point.name);
400 0 : continue;
401 : }
402 0 : if (!point.entity)
403 0 : continue;
404 0 : if (!point.ejectable)
405 : {
406 0 : let cmpTurretOwnership = Engine.QueryInterface(point.entity, IID_Ownership);
407 0 : if (cmpTurretOwnership)
408 0 : cmpTurretOwnership.SetOwner(msg.to);
409 : }
410 0 : else if (!IsOwnedByMutualAllyOfEntity(point.entity, this.entity))
411 : {
412 0 : let cmpTurretable = Engine.QueryInterface(point.entity, IID_Turretable);
413 0 : if (cmpTurretable)
414 0 : cmpTurretable.LeaveTurret();
415 : }
416 : }
417 0 : delete this.reservedTurrets;
418 : }
419 : }
420 :
421 2 : TurretHolder.prototype.Schema =
422 : "<element name='TurretPoints' a:help='Points that will be used to visibly garrison a unit.'>" +
423 : "<oneOrMore>" +
424 : "<element a:help='Element containing the offset coordinates.'>" +
425 : "<anyName/>" +
426 : "<interleave>" +
427 : "<element name='X'>" +
428 : "<data type='decimal'/>" +
429 : "</element>" +
430 : "<element name='Y'>" +
431 : "<data type='decimal'/>" +
432 : "</element>" +
433 : "<element name='Z'>" +
434 : "<data type='decimal'/>" +
435 : "</element>" +
436 : "<optional>" +
437 : "<interleave>" +
438 : "<element name='Template'>" +
439 : "<text/>" +
440 : "</element>" +
441 : "<element name='Ejectable' a:help='Whether this template is tied to the turret position (i.e. not allowed to leave the turret point).'>" +
442 : "<data type='boolean'/>" +
443 : "</element>" +
444 : "</interleave>" +
445 : "</optional>" +
446 : "<optional>" +
447 : "<element name='AllowedClasses' a:help='If specified, only entities matching the given classes will be able to use this turret.'>" +
448 : "<attribute name='datatype'>" +
449 : "<value>tokens</value>" +
450 : "</attribute>" +
451 : "<text/>" +
452 : "</element>" +
453 : "</optional>"+
454 : "<optional>" +
455 : "<element name='Angle' a:help='Angle in degrees relative to the turretHolder direction.'>" +
456 : "<data type='decimal'/>" +
457 : "</element>" +
458 : "</optional>" +
459 : "</interleave>" +
460 : "</element>" +
461 : "</oneOrMore>" +
462 : "</element>" +
463 : "<optional>" +
464 : "<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to occupy a turret point. Should be about 2.0 for land entities and preferably greater for ships.'>" +
465 : "<ref name='nonNegativeDecimal'/>" +
466 : "</element>" +
467 : "</optional>"
468 2 : "<optional>" +
469 : "<element name='Pickup' a:help='This entity will try to move to pick up units to be turreted.'>" +
470 : "<data type='boolean'/>" +
471 : "</element>" +
472 : "</optional>";
473 :
474 2 : Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder);
|