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