Line data Source code
1 : /* Copyright (C) 2023 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include <cfloat>
21 : #include <map>
22 :
23 : #include "MessageHandler.h"
24 : #include "../CommandProc.h"
25 : #include "../SimState.h"
26 : #include "../View.h"
27 :
28 : #include "graphics/GameView.h"
29 : #include "graphics/Model.h"
30 : #include "graphics/ObjectBase.h"
31 : #include "graphics/ObjectEntry.h"
32 : #include "graphics/ObjectManager.h"
33 : #include "graphics/Terrain.h"
34 : #include "graphics/Unit.h"
35 : #include "lib/utf8.h"
36 : #include "maths/MathUtil.h"
37 : #include "maths/Matrix3D.h"
38 : #include "ps/CLogger.h"
39 : #include "ps/Game.h"
40 : #include "ps/World.h"
41 : #include "renderer/Renderer.h"
42 : #include "renderer/WaterManager.h"
43 : #include "simulation2/Simulation2.h"
44 : #include "simulation2/components/ICmpObstruction.h"
45 : #include "simulation2/components/ICmpOwnership.h"
46 : #include "simulation2/components/ICmpPosition.h"
47 : #include "simulation2/components/ICmpPlayer.h"
48 : #include "simulation2/components/ICmpPlayerManager.h"
49 : #include "simulation2/components/ICmpSelectable.h"
50 : #include "simulation2/components/ICmpTemplateManager.h"
51 : #include "simulation2/components/ICmpVisual.h"
52 : #include "simulation2/helpers/Selection.h"
53 : #include "ps/XML/XMLWriter.h"
54 :
55 : namespace AtlasMessage
56 : {
57 :
58 : namespace
59 : {
60 0 : bool SortObjectsList(const sObjectsListItem& a, const sObjectsListItem& b)
61 : {
62 0 : return wcscmp(a.name.c_str(), b.name.c_str()) < 0;
63 : }
64 : } // anonymous namespace
65 :
66 : // Helpers for object constraints
67 0 : bool CheckEntityObstruction(entity_id_t ent)
68 : {
69 0 : CmpPtr<ICmpObstruction> cmpObstruction(*g_Game->GetSimulation2(), ent);
70 0 : if (cmpObstruction)
71 : {
72 0 : ICmpObstruction::EFoundationCheck result = cmpObstruction->CheckFoundation("default");
73 0 : if (result != ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
74 0 : return false;
75 : }
76 0 : return true;
77 : }
78 :
79 0 : void CheckObstructionAndUpdateVisual(entity_id_t id)
80 : {
81 0 : CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), id);
82 0 : if (cmpVisual)
83 : {
84 0 : if (!CheckEntityObstruction(id))
85 0 : cmpVisual->SetShadingColor(fixed::FromDouble(1.4), fixed::FromDouble(0.4), fixed::FromDouble(0.4), fixed::FromDouble(1));
86 : else
87 0 : cmpVisual->SetShadingColor(fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1));
88 : }
89 0 : }
90 :
91 0 : QUERYHANDLER(GetObjectsList)
92 : {
93 0 : std::vector<sObjectsListItem> objects;
94 :
95 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
96 0 : if (cmpTemplateManager)
97 : {
98 0 : std::vector<std::string> names = cmpTemplateManager->FindTemplatesWithRootName(true, "Entity");
99 :
100 0 : for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it)
101 : {
102 0 : std::wstring name(it->begin(), it->end());
103 :
104 0 : sObjectsListItem e;
105 0 : e.id = name;
106 0 : if (name.substr(0, 6) == L"actor|")
107 : {
108 0 : e.name = name.substr(6);
109 0 : e.type = 1;
110 : }
111 : else
112 : {
113 0 : e.name = name;
114 0 : e.type = 0;
115 : }
116 0 : objects.push_back(e);
117 : }
118 : }
119 :
120 0 : std::sort(objects.begin(), objects.end(), SortObjectsList);
121 0 : msg->objects = objects;
122 0 : }
123 :
124 :
125 1 : static std::vector<entity_id_t> g_Selection;
126 : typedef std::map<player_id_t, CColor> PlayerColorMap;
127 :
128 : // Helper function to find color of player owning the given entity,
129 : // returns white if entity has no owner. Uses caching to avoid
130 : // expensive script calls.
131 0 : static CColor GetOwnerPlayerColor(PlayerColorMap& colorMap, entity_id_t id)
132 : {
133 : // Default color - white
134 0 : CColor color(1.0f, 1.0f, 1.0f, 1.0f);
135 :
136 0 : CSimulation2& sim = *g_Game->GetSimulation2();
137 0 : CmpPtr<ICmpOwnership> cmpOwnership(sim, id);
138 0 : if (cmpOwnership)
139 : {
140 0 : player_id_t owner = cmpOwnership->GetOwner();
141 0 : if (colorMap.find(owner) != colorMap.end())
142 0 : return colorMap[owner];
143 : else
144 : {
145 0 : CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
146 0 : entity_id_t playerEnt = cmpPlayerManager->GetPlayerByID(owner);
147 0 : CmpPtr<ICmpPlayer> cmpPlayer(sim, playerEnt);
148 0 : if (cmpPlayer)
149 : {
150 0 : colorMap[owner] = cmpPlayer->GetDisplayedColor();
151 0 : color = colorMap[owner];
152 : }
153 : }
154 : }
155 0 : return color;
156 : }
157 :
158 0 : MESSAGEHANDLER(SetSelectionPreview)
159 : {
160 0 : CSimulation2& sim = *g_Game->GetSimulation2();
161 :
162 : // Cache player colors for performance
163 0 : PlayerColorMap playerColors;
164 :
165 : // Clear old selection rings
166 0 : for (size_t i = 0; i < g_Selection.size(); ++i)
167 : {
168 : // We can't set only alpha here, because that won't trigger desaturation
169 : // so we set the complete color (not too evil since it's cached)
170 0 : CmpPtr<ICmpSelectable> cmpSelectable(sim, g_Selection[i]);
171 0 : if (cmpSelectable)
172 : {
173 0 : CColor color = GetOwnerPlayerColor(playerColors, g_Selection[i]);
174 0 : color.a = 0.0f;
175 0 : cmpSelectable->SetSelectionHighlight(color, false);
176 : }
177 : }
178 :
179 0 : g_Selection = *msg->ids;
180 :
181 : // Set new selection rings
182 0 : for (size_t i = 0; i < g_Selection.size(); ++i)
183 : {
184 0 : CmpPtr<ICmpSelectable> cmpSelectable(sim, g_Selection[i]);
185 0 : if (cmpSelectable)
186 0 : cmpSelectable->SetSelectionHighlight(GetOwnerPlayerColor(playerColors, g_Selection[i]), true);
187 : }
188 0 : }
189 :
190 0 : QUERYHANDLER(GetObjectSettings)
191 : {
192 0 : AtlasView* view = AtlasView::GetView(msg->view);
193 0 : CSimulation2* simulation = view->GetSimulation2();
194 :
195 0 : sObjectSettings settings;
196 0 : settings.player = 0;
197 :
198 0 : CmpPtr<ICmpOwnership> cmpOwnership(*simulation, view->GetEntityId(msg->id));
199 0 : if (cmpOwnership)
200 : {
201 0 : int32_t player = cmpOwnership->GetOwner();
202 0 : if (player != -1)
203 0 : settings.player = player;
204 : }
205 :
206 : // TODO: selections
207 :
208 : /*
209 : // Get the unit's possible variants and selected variants
210 : std::vector<std::vector<CStr> > groups = unit->GetObject().m_Base->GetVariantGroups();
211 : const std::set<CStr>& selections = unit->GetActorSelections();
212 :
213 : // Iterate over variant groups
214 : std::vector<std::vector<std::wstring> > variantgroups;
215 : std::set<std::wstring> selections_set;
216 : variantgroups.reserve(groups.size());
217 : for (size_t i = 0; i < groups.size(); ++i)
218 : {
219 : // Copy variants into output structure
220 :
221 : std::vector<std::wstring> group;
222 : group.reserve(groups[i].size());
223 : int choice = -1;
224 :
225 : for (size_t j = 0; j < groups[i].size(); ++j)
226 : {
227 : group.push_back(CStrW(groups[i][j]));
228 :
229 : // Find the first string in 'selections' that matches one of this
230 : // group's variants
231 : if (choice == -1)
232 : if (selections.find(groups[i][j]) != selections.end())
233 : choice = (int)j;
234 : }
235 :
236 : // Assuming one of the variants was selected (which it really ought
237 : // to be), remember that one's name
238 : if (choice != -1)
239 : selections_set.insert(CStrW(groups[i][choice]));
240 :
241 : variantgroups.push_back(group);
242 : }
243 :
244 : settings.variantgroups = variantgroups;
245 : settings.selections = std::vector<std::wstring> (selections_set.begin(), selections_set.end()); // convert set->vector
246 : */
247 :
248 0 : msg->settings = settings;
249 0 : }
250 :
251 0 : QUERYHANDLER(GetObjectMapSettings)
252 : {
253 0 : std::vector<entity_id_t> ids = *msg->ids;
254 :
255 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
256 0 : ENSURE(cmpTemplateManager);
257 :
258 0 : XMLWriter_File exampleFile;
259 : {
260 0 : XMLWriter_Element entitiesTag(exampleFile, "Entities");
261 : {
262 0 : for (entity_id_t id : ids)
263 : {
264 0 : XMLWriter_Element entityTag(exampleFile, "Entity");
265 : {
266 : //Template name
267 0 : entityTag.Setting("Template", cmpTemplateManager->GetCurrentTemplateName(id));
268 :
269 : //Player
270 0 : CmpPtr<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), id);
271 0 : if (cmpOwnership)
272 0 : entityTag.Setting("Player", static_cast<int>(cmpOwnership->GetOwner()));
273 :
274 : //Adding position to make some relative position later
275 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
276 0 : if (cmpPosition)
277 : {
278 0 : CFixedVector3D pos = cmpPosition->GetPosition();
279 0 : CFixedVector3D rot = cmpPosition->GetRotation();
280 : {
281 0 : XMLWriter_Element positionTag(exampleFile, "Position");
282 0 : positionTag.Attribute("x", pos.X);
283 0 : positionTag.Attribute("z", pos.Z);
284 : // TODO: height offset etc
285 : }
286 : {
287 0 : XMLWriter_Element orientationTag(exampleFile, "Orientation");
288 0 : orientationTag.Attribute("y", rot.Y);
289 : // TODO: X, Z maybe
290 : }
291 : }
292 :
293 : // Adding actor seed
294 0 : CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), id);
295 0 : if (cmpVisual)
296 0 : entityTag.Setting("ActorSeed", static_cast<unsigned int>(cmpVisual->GetActorSeed()));
297 : }
298 : }
299 : }
300 : }
301 :
302 0 : const CStr& data = exampleFile.GetOutput();
303 0 : msg->xmldata = data.FromUTF8();
304 0 : }
305 :
306 :
307 0 : BEGIN_COMMAND(SetObjectSettings)
308 : {
309 : player_id_t m_PlayerOld, m_PlayerNew;
310 : std::set<CStr> m_SelectionsOld, m_SelectionsNew;
311 :
312 0 : void Do()
313 : {
314 0 : sObjectSettings settings = msg->settings;
315 :
316 0 : AtlasView* view = AtlasView::GetView(msg->view);
317 0 : CSimulation2* simulation = view->GetSimulation2();
318 :
319 0 : CmpPtr<ICmpOwnership> cmpOwnership(*simulation, view->GetEntityId(msg->id));
320 0 : m_PlayerOld = 0;
321 0 : if (cmpOwnership)
322 : {
323 0 : int32_t player = cmpOwnership->GetOwner();
324 0 : if (player != -1)
325 0 : m_PlayerOld = player;
326 : }
327 :
328 : // TODO: selections
329 : // m_SelectionsOld = unit->GetActorSelections();
330 :
331 0 : m_PlayerNew = (player_id_t)settings.player;
332 :
333 0 : std::vector<std::wstring> selections = *settings.selections;
334 0 : for (std::vector<std::wstring>::iterator it = selections.begin(); it != selections.end(); ++it)
335 : {
336 0 : m_SelectionsNew.insert(CStrW(*it).ToUTF8());
337 : }
338 :
339 0 : Redo();
340 0 : }
341 :
342 0 : void Redo()
343 : {
344 0 : Set(m_PlayerNew, m_SelectionsNew);
345 0 : }
346 :
347 0 : void Undo()
348 : {
349 0 : Set(m_PlayerOld, m_SelectionsOld);
350 0 : }
351 :
352 : private:
353 0 : void Set(player_id_t player, const std::set<CStr>& UNUSED(selections))
354 : {
355 0 : AtlasView* view = AtlasView::GetView(msg->view);
356 0 : CSimulation2* simulation = view->GetSimulation2();
357 :
358 0 : CmpPtr<ICmpOwnership> cmpOwnership(*simulation, view->GetEntityId(msg->id));
359 0 : if (cmpOwnership)
360 0 : cmpOwnership->SetOwner(player);
361 :
362 : // TODO: selections
363 : // unit->SetActorSelections(selections);
364 0 : }
365 : };
366 0 : END_COMMAND(SetObjectSettings);
367 :
368 : //////////////////////////////////////////////////////////////////////////
369 :
370 1 : static CStrW g_PreviewUnitName;
371 : static entity_id_t g_PreviewEntityID = INVALID_ENTITY;
372 1 : static std::vector<entity_id_t> g_PreviewEntitiesID;
373 :
374 0 : static CVector3D GetUnitPos(const Position& pos, bool floating)
375 : {
376 0 : static CVector3D vec;
377 0 : vec = pos.GetWorldSpace(vec, floating); // if msg->pos is 'Unchanged', use the previous pos
378 :
379 : // Clamp the position to the edges of the world:
380 :
381 : // Use 'Clamp' with a value slightly less than the width, so that converting
382 : // to integer (rounding towards zero) will put it on the tile inside the edge
383 : // instead of just outside
384 0 : float mapWidth = (g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide()-1)*TERRAIN_TILE_SIZE;
385 0 : float delta = 1e-6f; // fraction of map width - must be > FLT_EPSILON
386 :
387 0 : float xOnMap = Clamp(vec.X, 0.f, mapWidth * (1.f - delta));
388 0 : float zOnMap = Clamp(vec.Z, 0.f, mapWidth * (1.f - delta));
389 :
390 : // Don't waste time with GetExactGroundLevel unless we've changed
391 0 : if (xOnMap != vec.X || zOnMap != vec.Z)
392 : {
393 0 : vec.X = xOnMap;
394 0 : vec.Z = zOnMap;
395 0 : vec.Y = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(xOnMap, zOnMap);
396 : }
397 :
398 0 : return vec;
399 : }
400 :
401 0 : QUERYHANDLER(GetCurrentSelection)
402 : {
403 0 : msg->ids = g_Selection;
404 0 : }
405 :
406 0 : MESSAGEHANDLER(ObjectPreviewToEntity)
407 : {
408 : UNUSED2(msg);
409 :
410 0 : if (g_PreviewEntitiesID.size() == 0)
411 0 : return;
412 :
413 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
414 0 : ENSURE(cmpTemplateManager);
415 :
416 0 : PlayerColorMap playerColor;
417 :
418 : //I need to re create the objects finally delete preview objects
419 0 : for (entity_id_t ent : g_PreviewEntitiesID)
420 : {
421 : //Get template name (without the "preview|" prefix)
422 0 : std::wstring wTemplateName = wstring_from_utf8(cmpTemplateManager->GetCurrentTemplateName(ent).substr(8));
423 : //Create new entity
424 0 : entity_id_t new_ent = g_Game->GetSimulation2()->AddEntity(wTemplateName);
425 0 : if (new_ent == INVALID_ENTITY)
426 0 : continue;
427 :
428 : //get position, get rotation
429 0 : CmpPtr<ICmpPosition> cmpPositionNew(*g_Game->GetSimulation2(), new_ent);
430 0 : CmpPtr<ICmpPosition> cmpPositionOld(*g_Game->GetSimulation2(), ent);
431 :
432 0 : if (cmpPositionNew && cmpPositionOld)
433 : {
434 0 : CVector3D pos = cmpPositionOld->GetPosition();
435 0 : cmpPositionNew->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z));
436 :
437 : //now rotate
438 0 : CFixedVector3D rotation = cmpPositionOld->GetRotation();
439 0 : cmpPositionNew->SetYRotation(rotation.Y);
440 : }
441 :
442 : //get owner
443 0 : CmpPtr<ICmpOwnership> cmpOwnershipNew(*g_Game->GetSimulation2(), new_ent);
444 0 : CmpPtr<ICmpOwnership> cmpOwnershipOld(*g_Game->GetSimulation2(), ent);
445 0 : if (cmpOwnershipNew && cmpOwnershipOld)
446 0 : cmpOwnershipNew->SetOwner(cmpOwnershipOld->GetOwner());
447 :
448 : //getVisual
449 0 : CmpPtr<ICmpVisual> cmpVisualNew(*g_Game->GetSimulation2(), new_ent);
450 0 : CmpPtr<ICmpVisual> cmpVisualOld(*g_Game->GetSimulation2(), ent);
451 0 : if (cmpVisualNew && cmpVisualOld)
452 0 : cmpVisualNew->SetActorSeed(cmpVisualOld->GetActorSeed());
453 :
454 : //Update g_selectedObject and higligth
455 0 : g_Selection.push_back(new_ent);
456 0 : CmpPtr<ICmpSelectable> cmpSelectable(*g_Game->GetSimulation2(), new_ent);
457 0 : if (cmpSelectable)
458 0 : cmpSelectable->SetSelectionHighlight(GetOwnerPlayerColor(playerColor, new_ent), true);
459 :
460 0 : g_Game->GetSimulation2()->DestroyEntity(ent);
461 : }
462 0 : g_PreviewEntitiesID.clear();
463 :
464 : }
465 :
466 0 : MESSAGEHANDLER(MoveObjectPreview)
467 : {
468 0 : if (g_PreviewEntitiesID.size()==0)
469 0 : return;
470 :
471 : //TODO:Change pivot
472 0 : entity_id_t referenceEntity = *g_PreviewEntitiesID.begin();
473 :
474 : // All selected objects move relative to a pivot object,
475 : // so get its position and whether it's floating
476 0 : CFixedVector3D referencePos;
477 :
478 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), referenceEntity);
479 0 : if (cmpPosition && cmpPosition->IsInWorld())
480 0 : referencePos = cmpPosition->GetPosition();
481 :
482 :
483 : // Calculate directional vector of movement for pivot object,
484 : // we apply the same movement to all objects
485 0 : CVector3D targetPos = GetUnitPos(msg->pos, true);
486 0 : CFixedVector3D fTargetPos(entity_pos_t::FromFloat(targetPos.X), entity_pos_t::FromFloat(targetPos.Y), entity_pos_t::FromFloat(targetPos.Z));
487 0 : CFixedVector3D dir = fTargetPos - referencePos;
488 :
489 0 : for (const entity_id_t id : g_PreviewEntitiesID)
490 : {
491 0 : CmpPtr<ICmpPosition> cmpPreviewPosition(*g_Game->GetSimulation2(), id);
492 0 : if (cmpPreviewPosition)
493 : {
494 0 : CFixedVector3D posFinal;
495 0 : if (cmpPreviewPosition->IsInWorld())
496 : {
497 : // Calculate this object's position
498 0 : CFixedVector3D posFixed = cmpPreviewPosition->GetPosition();
499 0 : posFinal = posFixed + dir;
500 : }
501 0 : cmpPreviewPosition->JumpTo(posFinal.X, posFinal.Z);
502 : }
503 :
504 0 : CheckObstructionAndUpdateVisual(id);
505 : }
506 : }
507 :
508 0 : MESSAGEHANDLER(ObjectPreview)
509 : {
510 : // If the selection has changed...
511 0 : if (*msg->id != g_PreviewUnitName || (!msg->cleanObjectPreviews))
512 : {
513 : // Delete old entity
514 0 : if (g_PreviewEntityID != INVALID_ENTITY && msg->cleanObjectPreviews)
515 : {
516 : //Time to delete all preview objects
517 0 : for (entity_id_t ent : g_PreviewEntitiesID)
518 0 : g_Game->GetSimulation2()->DestroyEntity(ent);
519 0 : g_PreviewEntitiesID.clear();
520 : }
521 :
522 : // Create the new entity
523 0 : if ((*msg->id).empty())
524 0 : g_PreviewEntityID = INVALID_ENTITY;
525 : else
526 : {
527 0 : g_PreviewEntityID = g_Game->GetSimulation2()->AddLocalEntity(L"preview|" + *msg->id);
528 0 : g_PreviewEntitiesID.push_back(g_PreviewEntityID);
529 : }
530 :
531 :
532 0 : g_PreviewUnitName = *msg->id;
533 : }
534 :
535 0 : if (g_PreviewEntityID != INVALID_ENTITY)
536 : {
537 : // Update the unit's position and orientation:
538 :
539 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), g_PreviewEntityID);
540 0 : if (cmpPosition)
541 : {
542 0 : CVector3D pos = GetUnitPos(msg->pos, cmpPosition->CanFloat());
543 0 : cmpPosition->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z));
544 :
545 : float angle;
546 0 : if (msg->usetarget)
547 : {
548 : // Aim from pos towards msg->target
549 0 : CVector3D target = msg->target->GetWorldSpace(pos.Y);
550 0 : angle = atan2(target.X-pos.X, target.Z-pos.Z);
551 : }
552 : else
553 : {
554 0 : angle = msg->angle;
555 : }
556 :
557 0 : cmpPosition->SetYRotation(entity_angle_t::FromFloat(angle));
558 : }
559 :
560 : // TODO: handle random variations somehow
561 :
562 0 : CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), g_PreviewEntityID);
563 0 : if (cmpVisual)
564 0 : cmpVisual->SetActorSeed(msg->actorseed);
565 :
566 0 : CmpPtr<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), g_PreviewEntityID);
567 0 : if (cmpOwnership)
568 0 : cmpOwnership->SetOwner((player_id_t)msg->settings->player);
569 :
570 0 : CheckObstructionAndUpdateVisual(g_PreviewEntityID);
571 : }
572 0 : }
573 :
574 0 : BEGIN_COMMAND(CreateObject)
575 : {
576 : CVector3D m_Pos;
577 : float m_Angle;
578 : player_id_t m_Player;
579 : entity_id_t m_EntityID;
580 : u32 m_ActorSeed;
581 :
582 0 : void Do()
583 : {
584 : // Calculate the position/orientation to create this unit with
585 :
586 0 : m_Pos = GetUnitPos(msg->pos, true); // don't really care about floating
587 :
588 0 : if (msg->usetarget)
589 : {
590 : // Aim from m_Pos towards msg->target
591 0 : CVector3D target = msg->target->GetWorldSpace(m_Pos.Y);
592 0 : m_Angle = atan2(target.X-m_Pos.X, target.Z-m_Pos.Z);
593 : }
594 : else
595 : {
596 0 : m_Angle = msg->angle;
597 : }
598 :
599 0 : m_Player = (player_id_t)msg->settings->player;
600 0 : m_ActorSeed = msg->actorseed;
601 : // TODO: variation/selection strings
602 :
603 0 : Redo();
604 0 : }
605 :
606 0 : void Redo()
607 : {
608 0 : m_EntityID = g_Game->GetSimulation2()->AddEntity(*msg->id);
609 0 : if (m_EntityID == INVALID_ENTITY)
610 0 : return;
611 :
612 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), m_EntityID);
613 0 : if (cmpPosition)
614 : {
615 0 : cmpPosition->JumpTo(entity_pos_t::FromFloat(m_Pos.X), entity_pos_t::FromFloat(m_Pos.Z));
616 0 : cmpPosition->SetYRotation(entity_angle_t::FromFloat(m_Angle));
617 : }
618 :
619 0 : CmpPtr<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), m_EntityID);
620 0 : if (cmpOwnership)
621 0 : cmpOwnership->SetOwner(m_Player);
622 :
623 0 : CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), m_EntityID);
624 0 : if (cmpVisual)
625 : {
626 0 : cmpVisual->SetActorSeed(m_ActorSeed);
627 : // TODO: variation/selection strings
628 : }
629 : }
630 :
631 0 : void Undo()
632 : {
633 0 : if (m_EntityID != INVALID_ENTITY)
634 : {
635 0 : g_Game->GetSimulation2()->DestroyEntity(m_EntityID);
636 0 : m_EntityID = INVALID_ENTITY;
637 : }
638 0 : }
639 : };
640 0 : END_COMMAND(CreateObject)
641 :
642 :
643 0 : QUERYHANDLER(PickObject)
644 : {
645 : float x, y;
646 0 : msg->pos->GetScreenSpace(x, y);
647 :
648 : // Normally this function would be called with a player ID to check LOS,
649 : // but in Atlas the entire map is revealed, so just pass INVALID_PLAYER
650 0 : entity_id_t ent = EntitySelection::PickEntityAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, INVALID_PLAYER, msg->selectActors);;
651 :
652 0 : if (ent == INVALID_ENTITY)
653 0 : msg->id = INVALID_ENTITY;
654 : else
655 : {
656 0 : msg->id = ent;
657 : // Calculate offset of object from original mouse click position
658 : // so it gets moved by that offset
659 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), ent);
660 0 : if (!cmpPosition || !cmpPosition->IsInWorld())
661 : {
662 : // error
663 0 : msg->offsetx = msg->offsety = 0;
664 : }
665 : else
666 : {
667 0 : CFixedVector3D fixed = cmpPosition->GetPosition();
668 0 : CVector3D centre = CVector3D(fixed.X.ToFloat(), fixed.Y.ToFloat(), fixed.Z.ToFloat());
669 :
670 : float cx, cy;
671 0 : g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre, cx, cy);
672 :
673 0 : msg->offsetx = (int)(cx - x);
674 0 : msg->offsety = (int)(cy - y);
675 : }
676 : }
677 0 : }
678 :
679 :
680 0 : QUERYHANDLER(PickObjectsInRect)
681 : {
682 : float x0, y0, x1, y1;
683 0 : msg->start->GetScreenSpace(x0, y0);
684 0 : msg->end->GetScreenSpace(x1, y1);
685 :
686 : // Since owner selections are meaningless in Atlas, use INVALID_PLAYER
687 0 : msg->ids = EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, INVALID_PLAYER, msg->selectActors);
688 0 : }
689 :
690 :
691 0 : QUERYHANDLER(PickSimilarObjects)
692 : {
693 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
694 0 : ENSURE(cmpTemplateManager);
695 :
696 0 : entity_id_t ent = msg->id;
697 0 : std::string templateName = cmpTemplateManager->GetCurrentTemplateName(ent);
698 :
699 : // If unit has ownership, only pick units from the same player
700 0 : player_id_t owner = INVALID_PLAYER;
701 0 : CmpPtr<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), ent);
702 0 : if (cmpOwnership)
703 0 : owner = cmpOwnership->GetOwner();
704 :
705 0 : msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, owner, false, true, true, false);
706 0 : }
707 :
708 0 : MESSAGEHANDLER(ResetSelectionColor)
709 : {
710 : UNUSED2(msg);
711 :
712 0 : for (entity_id_t ent : g_Selection)
713 : {
714 0 : CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), ent);
715 0 : if (cmpVisual)
716 0 : cmpVisual->SetShadingColor(fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1));
717 : }
718 0 : }
719 :
720 0 : BEGIN_COMMAND(MoveObjects)
721 : {
722 : // Mapping from object to position
723 : std::map<entity_id_t, CVector3D> m_PosOld, m_PosNew;
724 :
725 0 : void Do()
726 : {
727 0 : std::vector<entity_id_t> ids = *msg->ids;
728 :
729 : // All selected objects move relative to a pivot object,
730 : // so get its position and whether it's floating
731 0 : CVector3D pivotPos(0, 0, 0);
732 0 : bool pivotFloating = false;
733 :
734 0 : CmpPtr<ICmpPosition> cmpPositionPivot(*g_Game->GetSimulation2(), (entity_id_t)msg->pivot);
735 0 : if (cmpPositionPivot && cmpPositionPivot->IsInWorld())
736 : {
737 0 : pivotFloating = cmpPositionPivot->CanFloat();
738 0 : CFixedVector3D pivotFixed = cmpPositionPivot->GetPosition();
739 0 : pivotPos = CVector3D(pivotFixed.X.ToFloat(), pivotFixed.Y.ToFloat(), pivotFixed.Z.ToFloat());
740 : }
741 :
742 : // Calculate directional vector of movement for pivot object,
743 : // we apply the same movement to all objects
744 0 : CVector3D targetPos = GetUnitPos(msg->pos, pivotFloating);
745 0 : CVector3D dir = targetPos - pivotPos;
746 :
747 0 : for (entity_id_t id : ids)
748 : {
749 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
750 0 : if (!cmpPosition || !cmpPosition->IsInWorld())
751 : {
752 : // error
753 0 : m_PosOld[id] = m_PosNew[id] = CVector3D(0, 0, 0);
754 : }
755 : else
756 : {
757 : // Calculate this object's position
758 0 : CFixedVector3D posFixed = cmpPosition->GetPosition();
759 0 : CVector3D pos = CVector3D(posFixed.X.ToFloat(), posFixed.Y.ToFloat(), posFixed.Z.ToFloat());
760 0 : m_PosNew[id] = pos + dir;
761 0 : m_PosOld[id] = pos;
762 : }
763 : }
764 :
765 0 : SetPos(m_PosNew);
766 0 : }
767 :
768 0 : void SetPos(const std::map<entity_id_t, CVector3D>& map)
769 : {
770 0 : for (const std::pair<const entity_id_t, CVector3D>& p : map)
771 : {
772 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), p.first);
773 0 : if (!cmpPosition)
774 0 : return;
775 :
776 : // Set 2D position, ignoring height
777 0 : cmpPosition->JumpTo(entity_pos_t::FromFloat(p.second.X), entity_pos_t::FromFloat(p.second.Z));
778 :
779 0 : CheckObstructionAndUpdateVisual(p.first);
780 : }
781 : }
782 :
783 0 : void Redo()
784 : {
785 0 : SetPos(m_PosNew);
786 0 : }
787 :
788 0 : void Undo()
789 : {
790 0 : SetPos(m_PosOld);
791 0 : }
792 :
793 0 : void MergeIntoPrevious(cMoveObjects* prev)
794 : {
795 : // TODO: do something valid if prev selection != this selection
796 0 : ENSURE(*(prev->msg->ids) == *(msg->ids));
797 0 : prev->m_PosNew = m_PosNew;
798 0 : }
799 : };
800 0 : END_COMMAND(MoveObjects)
801 :
802 0 : BEGIN_COMMAND(RotateObjectsFromCenterPoint)
803 : {
804 : std::map<entity_id_t, CVector3D> m_PosOld, m_PosNew;
805 : std::map<entity_id_t, float> m_AngleOld, m_AngleNew;
806 : CVector3D m_CenterPoint;
807 : float m_AngleInitialRotation;
808 :
809 0 : void Do()
810 : {
811 0 : std::vector<entity_id_t> ids = *msg->ids;
812 :
813 0 : CVector3D minPos;
814 0 : CVector3D maxPos;
815 :
816 0 : bool first = true;
817 :
818 : // Compute min position and max position
819 0 : for (entity_id_t id : ids)
820 : {
821 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
822 0 : if (!cmpPosition)
823 0 : continue;
824 :
825 0 : CVector3D pos = cmpPosition->GetPosition();
826 :
827 0 : m_PosOld[id] = cmpPosition->GetPosition();
828 0 : m_AngleOld[id] = cmpPosition->GetRotation().Y.ToFloat();
829 :
830 0 : if (first)
831 : {
832 0 : first = false;
833 0 : minPos = pos;
834 0 : maxPos = pos;
835 0 : m_CenterPoint.Y = pos.Y;
836 0 : continue;
837 : }
838 :
839 0 : if (pos.X < minPos.X)
840 0 : minPos.X = pos.X;
841 :
842 0 : if (pos.X > maxPos.X)
843 0 : maxPos.X = pos.X;
844 :
845 0 : if (pos.Z < minPos.Z)
846 0 : minPos.Z = pos.Z;
847 :
848 0 : if (pos.Z > maxPos.Z)
849 0 : maxPos.Z = pos.Z;
850 : }
851 :
852 : // Calculate objects center point
853 0 : m_CenterPoint.X = minPos.X + ((maxPos.X - minPos.X) * 0.5);
854 0 : m_CenterPoint.Z = minPos.Z + ((maxPos.Z - minPos.Z) * 0.5);
855 :
856 0 : CVector3D target = msg->target->GetWorldSpace(m_CenterPoint.Y);
857 0 : m_AngleInitialRotation = atan2(target.X-m_CenterPoint.X, target.Z-m_CenterPoint.Z);
858 0 : }
859 :
860 0 : void SetPos(const std::map<entity_id_t, CVector3D>& position, const std::map<entity_id_t, float>& angle)
861 : {
862 0 : for (const std::pair<const entity_id_t, CVector3D>& p : position)
863 : {
864 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), p.first);
865 0 : if (!cmpPosition)
866 0 : return;
867 :
868 : // Set 2D position, ignoring height
869 0 : cmpPosition->JumpTo(entity_pos_t::FromFloat(p.second.X), entity_pos_t::FromFloat(p.second.Z));
870 :
871 0 : if (msg->rotateObject)
872 0 : cmpPosition->SetYRotation(entity_angle_t::FromFloat(angle.at(p.first)));
873 :
874 : }
875 :
876 0 : for (const std::pair<const entity_id_t, CVector3D>& p : position)
877 0 : CheckObstructionAndUpdateVisual(p.first);
878 : }
879 :
880 0 : void Redo()
881 : {
882 0 : SetPos(m_PosNew, m_AngleNew);
883 0 : }
884 :
885 0 : void RecalculateRotation(Position newPoint)
886 : {
887 0 : std::vector<entity_id_t> ids = *msg->ids;
888 :
889 0 : CVector3D target = newPoint.GetWorldSpace(m_CenterPoint.Y);
890 0 : float newAngle = atan2(target.X-m_CenterPoint.X, target.Z-m_CenterPoint.Z);
891 :
892 0 : float globalAngle = m_AngleInitialRotation - newAngle;
893 :
894 : // Recalculate positions
895 0 : for (entity_id_t id : ids)
896 : {
897 0 : CVector3D pos = m_PosOld[id];
898 0 : float angle = atan2(pos.X - m_CenterPoint.X, pos.Z - m_CenterPoint.Z);
899 0 : float localAngle = angle + (globalAngle - angle);
900 0 : float xCos = cosf(localAngle);
901 0 : float xSin = sinf(localAngle);
902 :
903 0 : pos.X -= m_CenterPoint.X;
904 0 : pos.Z -= m_CenterPoint.Z;
905 :
906 0 : float newX = pos.X * xCos - pos.Z * xSin;
907 0 : float newZ = pos.X * xSin + pos.Z * xCos;
908 :
909 0 : pos.X = newX + m_CenterPoint.X;
910 0 : pos.Z = newZ + m_CenterPoint.Z;
911 :
912 0 : m_PosNew[id] = pos;
913 :
914 0 : m_AngleNew[id] = m_AngleOld[id] - globalAngle;
915 : }
916 :
917 0 : SetPos(m_PosNew, m_AngleNew);
918 0 : }
919 :
920 0 : void Undo()
921 : {
922 0 : SetPos(m_PosOld, m_AngleOld);
923 0 : }
924 :
925 0 : void MergeIntoPrevious(cRotateObjectsFromCenterPoint* prev)
926 : {
927 : // TODO: do something valid if prev unit != this unit
928 0 : ENSURE(*prev->msg->ids == *msg->ids);
929 0 : m_PosOld = prev->m_PosOld;
930 0 : m_AngleInitialRotation = prev->m_AngleInitialRotation;
931 0 : m_AngleOld = prev->m_AngleOld;
932 0 : m_CenterPoint = prev->m_CenterPoint;
933 :
934 0 : RecalculateRotation(msg->target);
935 0 : }
936 : };
937 0 : END_COMMAND(RotateObjectsFromCenterPoint)
938 :
939 0 : BEGIN_COMMAND(RotateObject)
940 : {
941 : std::map<entity_id_t, float> m_AngleOld, m_AngleNew;
942 :
943 0 : void Do()
944 : {
945 0 : std::vector<entity_id_t> ids = *msg->ids;
946 :
947 0 : for (entity_id_t id : ids)
948 : {
949 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
950 0 : if (!cmpPosition)
951 0 : return;
952 :
953 0 : m_AngleOld[id] = cmpPosition->GetRotation().Y.ToFloat();
954 :
955 0 : CMatrix3D transform = cmpPosition->GetInterpolatedTransform(0.f);
956 0 : CVector3D pos = transform.GetTranslation();
957 0 : CVector3D target = msg->target->GetWorldSpace(pos.Y);
958 0 : m_AngleNew[id] = atan2(target.X-pos.X, target.Z-pos.Z);
959 : }
960 :
961 0 : SetAngle(m_AngleNew);
962 : }
963 :
964 0 : void SetAngle(const std::map<entity_id_t, float>& angles)
965 : {
966 0 : for (const std::pair<const entity_id_t, float>& p : angles)
967 : {
968 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), p.first);
969 0 : if (!cmpPosition)
970 0 : return;
971 :
972 0 : cmpPosition->SetYRotation(entity_angle_t::FromFloat(p.second));
973 : }
974 : }
975 :
976 0 : void Redo()
977 : {
978 0 : SetAngle(m_AngleNew);
979 0 : }
980 :
981 0 : void Undo()
982 : {
983 0 : SetAngle(m_AngleOld);
984 0 : }
985 :
986 0 : void MergeIntoPrevious(cRotateObject* prev)
987 : {
988 : // TODO: do something valid if prev unit != this unit
989 0 : ENSURE(*prev->msg->ids == *msg->ids);
990 0 : prev->m_AngleNew = m_AngleNew;
991 0 : }
992 : };
993 0 : END_COMMAND(RotateObject)
994 :
995 :
996 0 : BEGIN_COMMAND(DeleteObjects)
997 : {
998 : // Saved copy of the important aspects of a unit, to allow undo
999 0 : struct OldObject
1000 : {
1001 : entity_id_t entityID;
1002 : CStr templateName;
1003 : player_id_t owner;
1004 : CFixedVector3D pos;
1005 : CFixedVector3D rot;
1006 : u32 actorSeed;
1007 : };
1008 :
1009 : std::vector<OldObject> oldObjects;
1010 :
1011 0 : cDeleteObjects()
1012 0 : {
1013 0 : }
1014 :
1015 0 : void Do()
1016 : {
1017 0 : Redo();
1018 0 : }
1019 :
1020 0 : void Redo()
1021 : {
1022 0 : CSimulation2& sim = *g_Game->GetSimulation2();
1023 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(sim, SYSTEM_ENTITY);
1024 0 : ENSURE(cmpTemplateManager);
1025 :
1026 0 : std::vector<entity_id_t> ids = *msg->ids;
1027 0 : for (size_t i = 0; i < ids.size(); ++i)
1028 : {
1029 0 : OldObject obj;
1030 :
1031 0 : obj.entityID = (entity_id_t)ids[i];
1032 0 : obj.templateName = cmpTemplateManager->GetCurrentTemplateName(obj.entityID);
1033 :
1034 0 : CmpPtr<ICmpOwnership> cmpOwnership(sim, obj.entityID);
1035 0 : if (cmpOwnership)
1036 0 : obj.owner = cmpOwnership->GetOwner();
1037 :
1038 0 : CmpPtr<ICmpPosition> cmpPosition(sim, obj.entityID);
1039 0 : if (cmpPosition)
1040 : {
1041 0 : obj.pos = cmpPosition->GetPosition();
1042 0 : obj.rot = cmpPosition->GetRotation();
1043 : }
1044 :
1045 0 : CmpPtr<ICmpVisual> cmpVisual(sim, obj.entityID);
1046 0 : if (cmpVisual)
1047 0 : obj.actorSeed = cmpVisual->GetActorSeed();
1048 :
1049 0 : oldObjects.push_back(obj);
1050 0 : g_Game->GetSimulation2()->DestroyEntity(obj.entityID);
1051 : }
1052 0 : g_Game->GetSimulation2()->FlushDestroyedEntities();
1053 0 : }
1054 :
1055 0 : void Undo()
1056 : {
1057 0 : CSimulation2& sim = *g_Game->GetSimulation2();
1058 :
1059 0 : for (size_t i = 0; i < oldObjects.size(); ++i)
1060 : {
1061 0 : entity_id_t ent = sim.AddEntity(oldObjects[i].templateName.FromUTF8(), oldObjects[i].entityID);
1062 0 : if (ent == INVALID_ENTITY)
1063 : {
1064 0 : LOGERROR("Failed to load entity template '%s'", oldObjects[i].templateName.c_str());
1065 : }
1066 : else
1067 : {
1068 0 : CmpPtr<ICmpPosition> cmpPosition(sim, oldObjects[i].entityID);
1069 0 : if (cmpPosition)
1070 : {
1071 0 : cmpPosition->JumpTo(oldObjects[i].pos.X, oldObjects[i].pos.Z);
1072 0 : cmpPosition->SetXZRotation(oldObjects[i].rot.X, oldObjects[i].rot.Z);
1073 0 : cmpPosition->SetYRotation(oldObjects[i].rot.Y);
1074 : }
1075 :
1076 0 : CmpPtr<ICmpOwnership> cmpOwnership(sim, oldObjects[i].entityID);
1077 0 : if (cmpOwnership)
1078 0 : cmpOwnership->SetOwner(oldObjects[i].owner);
1079 :
1080 0 : CmpPtr<ICmpVisual> cmpVisual(sim, oldObjects[i].entityID);
1081 0 : if (cmpVisual)
1082 0 : cmpVisual->SetActorSeed(oldObjects[i].actorSeed);
1083 : }
1084 : }
1085 :
1086 0 : oldObjects.clear();
1087 0 : }
1088 : };
1089 0 : END_COMMAND(DeleteObjects)
1090 :
1091 0 : QUERYHANDLER(GetPlayerObjects)
1092 : {
1093 0 : std::vector<entity_id_t> ids;
1094 0 : player_id_t playerID = msg->player;
1095 :
1096 0 : const CSimulation2::InterfaceListUnordered& cmps = g_Game->GetSimulation2()->GetEntitiesWithInterfaceUnordered(IID_Ownership);
1097 0 : for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
1098 : {
1099 0 : if (static_cast<ICmpOwnership*>(eit->second)->GetOwner() == playerID)
1100 : {
1101 0 : ids.push_back(eit->first);
1102 : }
1103 : }
1104 :
1105 0 : msg->ids = ids;
1106 0 : }
1107 :
1108 0 : MESSAGEHANDLER(SetBandbox)
1109 : {
1110 0 : AtlasView::GetView_Game()->SetBandbox(msg->show, (float)msg->sx0, (float)msg->sy0, (float)msg->sx1, (float)msg->sy1);
1111 0 : }
1112 :
1113 0 : QUERYHANDLER(GetSelectedObjectsTemplateNames)
1114 : {
1115 0 : std::vector<entity_id_t> ids = *msg->ids;
1116 0 : std::vector<std::string> names;
1117 :
1118 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
1119 0 : ENSURE(cmpTemplateManager);
1120 :
1121 0 : for (size_t i = 0; i < ids.size(); ++i)
1122 : {
1123 0 : entity_id_t id = (entity_id_t)ids[i];
1124 0 : std::string templateName = cmpTemplateManager->GetCurrentTemplateName(id);
1125 0 : names.push_back(templateName);
1126 : }
1127 :
1128 0 : std::sort(names.begin(), names.end());
1129 0 : msg->names = names;
1130 0 : }
1131 :
1132 3 : } // namespace AtlasMessage
|