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