source: ps/trunk/binaries/data/mods/public/simulation/helpers/Transform.js@ 25221

Last change on this file since 25221 was 25221, checked in by Freagarach, 3 years ago

Fix promoting entities going back to default stance.

Introduced in r25192 / rP25192.
Brings back the less intrusive bug of transforming while garrisoned of turreted and unloading results in the default stance. That needs to be fixed again.

Differential revision: D3822

  • Property svn:eol-style set to native
File size: 10.7 KB
Line 
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.
4function 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 */
162function 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
173function 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
249function 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
263function 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
284Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
285Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);
Note: See TracBrowser for help on using the repository browser.