Line data Source code
1 : // Helper functions to change an entity's template and check if the transformation is possible
2 :
3 : // returns the ID of the new entity or INVALID_ENTITY.
4 : function ChangeEntityTemplate(oldEnt, newTemplate)
5 : {
6 : // Done un/packing, copy our parameters to the final entity
7 4 : var newEnt = Engine.AddEntity(newTemplate);
8 4 : if (newEnt == INVALID_ENTITY)
9 : {
10 0 : error("Transform.js: Error replacing entity " + oldEnt + " for a '" + newTemplate + "'");
11 0 : return INVALID_ENTITY;
12 : }
13 :
14 4 : Engine.ProfileStart("Transform");
15 :
16 4 : const cmpVisual = Engine.QueryInterface(oldEnt, IID_Visual);
17 4 : const cmpNewVisual = Engine.QueryInterface(newEnt, IID_Visual);
18 4 : if (cmpVisual && cmpNewVisual)
19 0 : cmpNewVisual.SetActorSeed(cmpVisual.GetActorSeed());
20 :
21 4 : var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position);
22 4 : var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position);
23 4 : if (cmpPosition && cmpNewPosition)
24 : {
25 4 : if (cmpPosition.IsInWorld())
26 : {
27 4 : let pos = cmpPosition.GetPosition2D();
28 4 : cmpNewPosition.JumpTo(pos.x, pos.y);
29 : }
30 4 : let rot = cmpPosition.GetRotation();
31 4 : cmpNewPosition.SetYRotation(rot.y);
32 4 : cmpNewPosition.SetXZRotation(rot.x, rot.z);
33 4 : cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
34 : }
35 :
36 : // Prevent spawning subunits on occupied positions.
37 4 : let cmpTurretHolder = Engine.QueryInterface(oldEnt, IID_TurretHolder);
38 4 : let cmpNewTurretHolder = Engine.QueryInterface(newEnt, IID_TurretHolder);
39 4 : if (cmpTurretHolder && cmpNewTurretHolder)
40 0 : for (let entity of cmpTurretHolder.GetEntities())
41 0 : cmpNewTurretHolder.SetReservedTurretPoint(cmpTurretHolder.GetOccupiedTurretPointName(entity));
42 :
43 : let owner;
44 4 : let cmpTerritoryDecay = Engine.QueryInterface(newEnt, IID_TerritoryDecay);
45 4 : if (cmpTerritoryDecay && cmpTerritoryDecay.HasTerritoryOwnership() && cmpNewPosition)
46 : {
47 1 : let pos = cmpNewPosition.GetPosition2D();
48 1 : let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
49 1 : owner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
50 : }
51 : else
52 : {
53 3 : let cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
54 3 : if (cmpOwnership)
55 3 : owner = cmpOwnership.GetOwner();
56 : }
57 4 : let cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
58 4 : if (cmpNewOwnership)
59 4 : cmpNewOwnership.SetOwner(owner);
60 :
61 4 : CopyControlGroups(oldEnt, newEnt);
62 :
63 : // Rescale capture points
64 4 : var cmpCapturable = Engine.QueryInterface(oldEnt, IID_Capturable);
65 4 : var cmpNewCapturable = Engine.QueryInterface(newEnt, IID_Capturable);
66 4 : if (cmpCapturable && cmpNewCapturable)
67 : {
68 0 : let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
69 0 : let newCapturePoints = cmpCapturable.GetCapturePoints().map(v => v / scale);
70 0 : cmpNewCapturable.SetCapturePoints(newCapturePoints);
71 : }
72 :
73 : // Maintain current health level
74 4 : var cmpHealth = Engine.QueryInterface(oldEnt, IID_Health);
75 4 : var cmpNewHealth = Engine.QueryInterface(newEnt, IID_Health);
76 4 : if (cmpHealth && cmpNewHealth)
77 : {
78 0 : var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
79 0 : cmpNewHealth.SetHitpoints(cmpNewHealth.GetMaxHitpoints() * healthLevel);
80 : }
81 :
82 4 : let cmpPromotion = Engine.QueryInterface(oldEnt, IID_Promotion);
83 4 : let cmpNewPromotion = Engine.QueryInterface(newEnt, IID_Promotion);
84 4 : if (cmpPromotion && cmpNewPromotion)
85 : {
86 0 : cmpPromotion.SetPromotedEntity(newEnt);
87 0 : cmpNewPromotion.IncreaseXp(cmpPromotion.GetCurrentXp());
88 : }
89 :
90 4 : let cmpResGatherer = Engine.QueryInterface(oldEnt, IID_ResourceGatherer);
91 4 : let cmpNewResGatherer = Engine.QueryInterface(newEnt, IID_ResourceGatherer);
92 4 : if (cmpResGatherer && cmpNewResGatherer)
93 : {
94 0 : let carriedResources = cmpResGatherer.GetCarryingStatus();
95 0 : cmpNewResGatherer.GiveResources(carriedResources);
96 0 : cmpNewResGatherer.SetLastCarriedType(cmpResGatherer.GetLastCarriedType());
97 : }
98 :
99 : // Maintain the list of guards
100 4 : let cmpGuard = Engine.QueryInterface(oldEnt, IID_Guard);
101 4 : let cmpNewGuard = Engine.QueryInterface(newEnt, IID_Guard);
102 4 : if (cmpGuard && cmpNewGuard)
103 : {
104 0 : let entities = cmpGuard.GetEntities();
105 0 : if (entities.length)
106 : {
107 0 : cmpNewGuard.SetEntities(entities);
108 0 : for (let ent of entities)
109 : {
110 0 : let cmpEntUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
111 0 : if (cmpEntUnitAI)
112 0 : cmpEntUnitAI.SetGuardOf(newEnt);
113 : }
114 : }
115 : }
116 :
117 4 : let cmpStatusEffectsReceiver = Engine.QueryInterface(oldEnt, IID_StatusEffectsReceiver);
118 4 : let cmpNewStatusEffectsReceiver = Engine.QueryInterface(newEnt, IID_StatusEffectsReceiver);
119 4 : if (cmpStatusEffectsReceiver && cmpNewStatusEffectsReceiver)
120 : {
121 0 : let activeStatus = cmpStatusEffectsReceiver.GetActiveStatuses();
122 0 : for (let status in activeStatus)
123 : {
124 0 : let newStatus = activeStatus[status];
125 0 : if (newStatus.Duration)
126 0 : newStatus.Duration -= newStatus._timeElapsed;
127 0 : cmpNewStatusEffectsReceiver.ApplyStatus({ [status]: newStatus }, newStatus.source.entity, newStatus.source.owner);
128 : }
129 : }
130 :
131 4 : TransferGarrisonedUnits(oldEnt, newEnt);
132 :
133 4 : Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
134 :
135 : // UnitAI generally needs other components to be properly initialised.
136 4 : let cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
137 4 : let cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
138 4 : if (cmpUnitAI && cmpNewUnitAI)
139 : {
140 0 : let pos = cmpUnitAI.GetHeldPosition();
141 0 : if (pos)
142 0 : cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
143 0 : cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
144 0 : cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
145 0 : let guarded = cmpUnitAI.IsGuardOf();
146 0 : if (guarded)
147 : {
148 0 : let cmpGuarded = Engine.QueryInterface(guarded, IID_Guard);
149 0 : if (cmpGuarded)
150 : {
151 0 : cmpGuarded.RenameGuard(oldEnt, newEnt);
152 0 : cmpNewUnitAI.SetGuardOf(guarded);
153 : }
154 : }
155 : }
156 :
157 4 : if (cmpPosition && cmpPosition.IsInWorld())
158 4 : cmpPosition.MoveOutOfWorld();
159 :
160 4 : Engine.ProfileStop();
161 :
162 4 : Engine.DestroyEntity(oldEnt);
163 :
164 4 : return newEnt;
165 : }
166 :
167 : /**
168 : * Copy over the obstruction control group IDs.
169 : * This is needed to ensure that when a group of structures with the same
170 : * control groups is replaced by a new entity, they remains in the same control group(s).
171 : * This is the mechanism that is used to e.g. enable wall pieces to be built closely
172 : * together, ignoring their mutual obstruction shapes (since they would
173 : * otherwise be prevented from being built so closely together).
174 : */
175 : function CopyControlGroups(oldEnt, newEnt)
176 : {
177 4 : let cmpObstruction = Engine.QueryInterface(oldEnt, IID_Obstruction);
178 4 : let cmpNewObstruction = Engine.QueryInterface(newEnt, IID_Obstruction);
179 4 : if (cmpObstruction && cmpNewObstruction)
180 : {
181 0 : cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
182 0 : cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
183 : }
184 : }
185 :
186 : function ObstructionsBlockingTemplateChange(ent, templateArg)
187 : {
188 0 : var previewEntity = Engine.AddEntity("preview|"+templateArg);
189 :
190 0 : if (previewEntity == INVALID_ENTITY)
191 0 : return true;
192 :
193 0 : CopyControlGroups(ent, previewEntity);
194 0 : var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions);
195 0 : var cmpPosition = Engine.QueryInterface(ent, IID_Position);
196 0 : var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
197 :
198 0 : var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position);
199 :
200 : // Return false if no ownership as BuildRestrictions.CheckPlacement needs an owner and I have no idea if false or true is better
201 : // Plus there are no real entities without owners currently.
202 0 : if (!cmpBuildRestrictions || !cmpPosition || !cmpOwnership)
203 0 : return DeleteEntityAndReturn(previewEntity, cmpPosition, null, null, cmpNewPosition, false);
204 :
205 0 : var pos = cmpPosition.GetPosition2D();
206 0 : var angle = cmpPosition.GetRotation();
207 : // move us away to prevent our own obstruction from blocking the upgrade.
208 0 : cmpPosition.MoveOutOfWorld();
209 :
210 0 : cmpNewPosition.JumpTo(pos.x, pos.y);
211 0 : cmpNewPosition.SetYRotation(angle.y);
212 :
213 0 : var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership);
214 0 : cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
215 :
216 0 : var checkPlacement = cmpBuildRestrictions.CheckPlacement();
217 :
218 0 : if (checkPlacement && !checkPlacement.success)
219 0 : return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
220 :
221 0 : var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
222 0 : var template = cmpTemplateManager.GetTemplate(cmpTemplateManager.GetCurrentTemplateName(ent));
223 0 : var newTemplate = cmpTemplateManager.GetTemplate(templateArg);
224 :
225 : // Check if units are blocking our template change
226 0 : if (template.Obstruction && newTemplate.Obstruction)
227 : {
228 : // This only needs to be done if the new template is strictly bigger than the old one
229 : // "Obstructions" are annoying to test so just check.
230 0 : if (newTemplate.Obstruction.Obstructions ||
231 :
232 : newTemplate.Obstruction.Static && template.Obstruction.Static &&
233 : (newTemplate.Obstruction.Static["@width"] > template.Obstruction.Static["@width"] ||
234 : newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Static["@depth"]) ||
235 : newTemplate.Obstruction.Static && template.Obstruction.Unit &&
236 : (newTemplate.Obstruction.Static["@width"] > template.Obstruction.Unit["@radius"] ||
237 : newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Unit["@radius"]) ||
238 :
239 : newTemplate.Obstruction.Unit && template.Obstruction.Unit &&
240 : newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Unit["@radius"] ||
241 : newTemplate.Obstruction.Unit && template.Obstruction.Static &&
242 : (newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@width"] ||
243 : newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@depth"]))
244 : {
245 0 : var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction);
246 0 : if (cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag())
247 : {
248 : // Remove all obstructions at the new entity, especially animal corpses
249 0 : for (let ent of cmpNewObstruction.GetEntitiesDeletedUponConstruction())
250 0 : Engine.DestroyEntity(ent);
251 :
252 0 : let collisions = cmpNewObstruction.GetEntitiesBlockingConstruction();
253 0 : if (collisions.length)
254 0 : return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
255 : }
256 : }
257 : }
258 :
259 0 : return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
260 : }
261 :
262 : function DeleteEntityAndReturn(ent, cmpPosition, position, angle, cmpNewPosition, ret)
263 : {
264 : // prevent preview from interfering in the world
265 0 : cmpNewPosition.MoveOutOfWorld();
266 0 : if (position !== null)
267 : {
268 0 : cmpPosition.JumpTo(position.x, position.y);
269 0 : cmpPosition.SetYRotation(angle.y);
270 : }
271 :
272 0 : Engine.DestroyEntity(ent);
273 0 : return ret;
274 : }
275 :
276 : function TransferGarrisonedUnits(oldEnt, newEnt)
277 : {
278 : // Transfer garrisoned units if possible, or unload them
279 4 : let cmpOldGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder);
280 4 : if (!cmpOldGarrison || !cmpOldGarrison.GetEntities().length)
281 4 : return;
282 :
283 0 : let cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder);
284 0 : let entities = cmpOldGarrison.GetEntities().slice();
285 0 : for (let ent of entities)
286 : {
287 0 : cmpOldGarrison.Unload(ent);
288 0 : if (!cmpNewGarrison)
289 0 : continue;
290 0 : let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
291 0 : if (!cmpGarrisonable)
292 0 : continue;
293 0 : cmpGarrisonable.Garrison(newEnt);
294 : }
295 : }
296 :
297 2 : Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
298 2 : Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);
|