/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include #include #include "MessageHandler.h" #include "../CommandProc.h" #include "../SimState.h" #include "../View.h" #include "graphics/GameView.h" #include "graphics/Model.h" #include "graphics/ObjectBase.h" #include "graphics/ObjectEntry.h" #include "graphics/ObjectManager.h" #include "graphics/Terrain.h" #include "graphics/Unit.h" #include "lib/utf8.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/helpers/Selection.h" #include "ps/XML/XMLWriter.h" namespace AtlasMessage { namespace { bool SortObjectsList(const sObjectsListItem& a, const sObjectsListItem& b) { return wcscmp(a.name.c_str(), b.name.c_str()) < 0; } } // anonymous namespace // Helpers for object constraints bool CheckEntityObstruction(entity_id_t ent) { CmpPtr cmpObstruction(*g_Game->GetSimulation2(), ent); if (cmpObstruction) { ICmpObstruction::EFoundationCheck result = cmpObstruction->CheckFoundation("default"); if (result != ICmpObstruction::FOUNDATION_CHECK_SUCCESS) return false; } return true; } void CheckObstructionAndUpdateVisual(entity_id_t id) { CmpPtr cmpVisual(*g_Game->GetSimulation2(), id); if (cmpVisual) { if (!CheckEntityObstruction(id)) cmpVisual->SetShadingColor(fixed::FromDouble(1.4), fixed::FromDouble(0.4), fixed::FromDouble(0.4), fixed::FromDouble(1)); else cmpVisual->SetShadingColor(fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1)); } } QUERYHANDLER(GetObjectsList) { std::vector objects; CmpPtr cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpTemplateManager) { std::vector names = cmpTemplateManager->FindAllTemplates(true); for (std::vector::iterator it = names.begin(); it != names.end(); ++it) { std::wstring name(it->begin(), it->end()); sObjectsListItem e; e.id = name; if (name.substr(0, 6) == L"actor|") { e.name = name.substr(6); e.type = 1; } else { e.name = name; e.type = 0; } objects.push_back(e); } } std::sort(objects.begin(), objects.end(), SortObjectsList); msg->objects = objects; } static std::vector g_Selection; typedef std::map PlayerColorMap; // Helper function to find color of player owning the given entity, // returns white if entity has no owner. Uses caching to avoid // expensive script calls. static CColor GetOwnerPlayerColor(PlayerColorMap& colorMap, entity_id_t id) { // Default color - white CColor color(1.0f, 1.0f, 1.0f, 1.0f); CSimulation2& sim = *g_Game->GetSimulation2(); CmpPtr cmpOwnership(sim, id); if (cmpOwnership) { player_id_t owner = cmpOwnership->GetOwner(); if (colorMap.find(owner) != colorMap.end()) return colorMap[owner]; else { CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY); entity_id_t playerEnt = cmpPlayerManager->GetPlayerByID(owner); CmpPtr cmpPlayer(sim, playerEnt); if (cmpPlayer) { colorMap[owner] = cmpPlayer->GetDisplayedColor(); color = colorMap[owner]; } } } return color; } MESSAGEHANDLER(SetSelectionPreview) { CSimulation2& sim = *g_Game->GetSimulation2(); // Cache player colors for performance PlayerColorMap playerColors; // Clear old selection rings for (size_t i = 0; i < g_Selection.size(); ++i) { // We can't set only alpha here, because that won't trigger desaturation // so we set the complete color (not too evil since it's cached) CmpPtr cmpSelectable(sim, g_Selection[i]); if (cmpSelectable) { CColor color = GetOwnerPlayerColor(playerColors, g_Selection[i]); color.a = 0.0f; cmpSelectable->SetSelectionHighlight(color, false); } } g_Selection = *msg->ids; // Set new selection rings for (size_t i = 0; i < g_Selection.size(); ++i) { CmpPtr cmpSelectable(sim, g_Selection[i]); if (cmpSelectable) cmpSelectable->SetSelectionHighlight(GetOwnerPlayerColor(playerColors, g_Selection[i]), true); } } QUERYHANDLER(GetObjectSettings) { AtlasView* view = AtlasView::GetView(msg->view); CSimulation2* simulation = view->GetSimulation2(); sObjectSettings settings; settings.player = 0; CmpPtr cmpOwnership(*simulation, view->GetEntityId(msg->id)); if (cmpOwnership) { int32_t player = cmpOwnership->GetOwner(); if (player != -1) settings.player = player; } // TODO: selections /* // Get the unit's possible variants and selected variants std::vector > groups = unit->GetObject().m_Base->GetVariantGroups(); const std::set& selections = unit->GetActorSelections(); // Iterate over variant groups std::vector > variantgroups; std::set selections_set; variantgroups.reserve(groups.size()); for (size_t i = 0; i < groups.size(); ++i) { // Copy variants into output structure std::vector group; group.reserve(groups[i].size()); int choice = -1; for (size_t j = 0; j < groups[i].size(); ++j) { group.push_back(CStrW(groups[i][j])); // Find the first string in 'selections' that matches one of this // group's variants if (choice == -1) if (selections.find(groups[i][j]) != selections.end()) choice = (int)j; } // Assuming one of the variants was selected (which it really ought // to be), remember that one's name if (choice != -1) selections_set.insert(CStrW(groups[i][choice])); variantgroups.push_back(group); } settings.variantgroups = variantgroups; settings.selections = std::vector (selections_set.begin(), selections_set.end()); // convert set->vector */ msg->settings = settings; } QUERYHANDLER(GetObjectMapSettings) { std::vector ids = *msg->ids; CmpPtr cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); ENSURE(cmpTemplateManager); XMLWriter_File exampleFile; { XMLWriter_Element entitiesTag(exampleFile, "Entities"); { for (entity_id_t id : ids) { XMLWriter_Element entityTag(exampleFile, "Entity"); { //Template name entityTag.Setting("Template", cmpTemplateManager->GetCurrentTemplateName(id)); //Player CmpPtr cmpOwnership(*g_Game->GetSimulation2(), id); if (cmpOwnership) entityTag.Setting("Player", static_cast(cmpOwnership->GetOwner())); //Adding position to make some relative position later CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); if (cmpPosition) { CFixedVector3D pos = cmpPosition->GetPosition(); CFixedVector3D rot = cmpPosition->GetRotation(); { XMLWriter_Element positionTag(exampleFile, "Position"); positionTag.Attribute("x", pos.X); positionTag.Attribute("z", pos.Z); // TODO: height offset etc } { XMLWriter_Element orientationTag(exampleFile, "Orientation"); orientationTag.Attribute("y", rot.Y); // TODO: X, Z maybe } } // Adding actor seed CmpPtr cmpVisual(*g_Game->GetSimulation2(), id); if (cmpVisual) entityTag.Setting("ActorSeed", static_cast(cmpVisual->GetActorSeed())); } } } } const CStr& data = exampleFile.GetOutput(); msg->xmldata = data.FromUTF8(); } BEGIN_COMMAND(SetObjectSettings) { player_id_t m_PlayerOld, m_PlayerNew; std::set m_SelectionsOld, m_SelectionsNew; void Do() { sObjectSettings settings = msg->settings; AtlasView* view = AtlasView::GetView(msg->view); CSimulation2* simulation = view->GetSimulation2(); CmpPtr cmpOwnership(*simulation, view->GetEntityId(msg->id)); m_PlayerOld = 0; if (cmpOwnership) { int32_t player = cmpOwnership->GetOwner(); if (player != -1) m_PlayerOld = player; } // TODO: selections // m_SelectionsOld = unit->GetActorSelections(); m_PlayerNew = (player_id_t)settings.player; std::vector selections = *settings.selections; for (std::vector::iterator it = selections.begin(); it != selections.end(); ++it) { m_SelectionsNew.insert(CStrW(*it).ToUTF8()); } Redo(); } void Redo() { Set(m_PlayerNew, m_SelectionsNew); } void Undo() { Set(m_PlayerOld, m_SelectionsOld); } private: void Set(player_id_t player, const std::set& UNUSED(selections)) { AtlasView* view = AtlasView::GetView(msg->view); CSimulation2* simulation = view->GetSimulation2(); CmpPtr cmpOwnership(*simulation, view->GetEntityId(msg->id)); if (cmpOwnership) cmpOwnership->SetOwner(player); // TODO: selections // unit->SetActorSelections(selections); } }; END_COMMAND(SetObjectSettings); ////////////////////////////////////////////////////////////////////////// static CStrW g_PreviewUnitName; static entity_id_t g_PreviewEntityID = INVALID_ENTITY; static std::vector g_PreviewEntitiesID; static CVector3D GetUnitPos(const Position& pos, bool floating) { static CVector3D vec; vec = pos.GetWorldSpace(vec, floating); // if msg->pos is 'Unchanged', use the previous pos // Clamp the position to the edges of the world: // Use 'Clamp' with a value slightly less than the width, so that converting // to integer (rounding towards zero) will put it on the tile inside the edge // instead of just outside const float mapWidth = (g_Game->GetWorld()->GetTerrain().GetVerticesPerSide()-1)*TERRAIN_TILE_SIZE; float delta = 1e-6f; // fraction of map width - must be > FLT_EPSILON float xOnMap = Clamp(vec.X, 0.f, mapWidth * (1.f - delta)); float zOnMap = Clamp(vec.Z, 0.f, mapWidth * (1.f - delta)); // Don't waste time with GetExactGroundLevel unless we've changed if (xOnMap != vec.X || zOnMap != vec.Z) { vec.X = xOnMap; vec.Z = zOnMap; vec.Y = g_Game->GetWorld()->GetTerrain().GetExactGroundLevel(xOnMap, zOnMap); } return vec; } QUERYHANDLER(GetCurrentSelection) { msg->ids = g_Selection; } MESSAGEHANDLER(ObjectPreviewToEntity) { UNUSED2(msg); if (g_PreviewEntitiesID.size() == 0) return; CmpPtr cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); ENSURE(cmpTemplateManager); PlayerColorMap playerColor; //I need to re create the objects finally delete preview objects for (entity_id_t ent : g_PreviewEntitiesID) { //Get template name (without the "preview|" prefix) std::wstring wTemplateName = wstring_from_utf8(cmpTemplateManager->GetCurrentTemplateName(ent).substr(8)); //Create new entity entity_id_t new_ent = g_Game->GetSimulation2()->AddEntity(wTemplateName); if (new_ent == INVALID_ENTITY) continue; //get position, get rotation CmpPtr cmpPositionNew(*g_Game->GetSimulation2(), new_ent); CmpPtr cmpPositionOld(*g_Game->GetSimulation2(), ent); if (cmpPositionNew && cmpPositionOld) { CVector3D pos = cmpPositionOld->GetPosition(); cmpPositionNew->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z)); //now rotate CFixedVector3D rotation = cmpPositionOld->GetRotation(); cmpPositionNew->SetYRotation(rotation.Y); } //get owner CmpPtr cmpOwnershipNew(*g_Game->GetSimulation2(), new_ent); CmpPtr cmpOwnershipOld(*g_Game->GetSimulation2(), ent); if (cmpOwnershipNew && cmpOwnershipOld) cmpOwnershipNew->SetOwner(cmpOwnershipOld->GetOwner()); //getVisual CmpPtr cmpVisualNew(*g_Game->GetSimulation2(), new_ent); CmpPtr cmpVisualOld(*g_Game->GetSimulation2(), ent); if (cmpVisualNew && cmpVisualOld) cmpVisualNew->SetActorSeed(cmpVisualOld->GetActorSeed()); //Update g_selectedObject and higligth g_Selection.push_back(new_ent); CmpPtr cmpSelectable(*g_Game->GetSimulation2(), new_ent); if (cmpSelectable) cmpSelectable->SetSelectionHighlight(GetOwnerPlayerColor(playerColor, new_ent), true); g_Game->GetSimulation2()->DestroyEntity(ent); } g_PreviewEntitiesID.clear(); } MESSAGEHANDLER(MoveObjectPreview) { if (g_PreviewEntitiesID.size()==0) return; //TODO:Change pivot entity_id_t referenceEntity = *g_PreviewEntitiesID.begin(); // All selected objects move relative to a pivot object, // so get its position and whether it's floating CFixedVector3D referencePos; CmpPtr cmpPosition(*g_Game->GetSimulation2(), referenceEntity); if (cmpPosition && cmpPosition->IsInWorld()) referencePos = cmpPosition->GetPosition(); // Calculate directional vector of movement for pivot object, // we apply the same movement to all objects CVector3D targetPos = GetUnitPos(msg->pos, true); CFixedVector3D fTargetPos(entity_pos_t::FromFloat(targetPos.X), entity_pos_t::FromFloat(targetPos.Y), entity_pos_t::FromFloat(targetPos.Z)); CFixedVector3D dir = fTargetPos - referencePos; for (const entity_id_t id : g_PreviewEntitiesID) { CmpPtr cmpPreviewPosition(*g_Game->GetSimulation2(), id); if (cmpPreviewPosition) { CFixedVector3D posFinal; if (cmpPreviewPosition->IsInWorld()) { // Calculate this object's position CFixedVector3D posFixed = cmpPreviewPosition->GetPosition(); posFinal = posFixed + dir; } cmpPreviewPosition->JumpTo(posFinal.X, posFinal.Z); } CheckObstructionAndUpdateVisual(id); } } MESSAGEHANDLER(ObjectPreview) { // If the selection has changed... if (*msg->id != g_PreviewUnitName || (!msg->cleanObjectPreviews)) { // Delete old entity if (g_PreviewEntityID != INVALID_ENTITY && msg->cleanObjectPreviews) { //Time to delete all preview objects for (entity_id_t ent : g_PreviewEntitiesID) g_Game->GetSimulation2()->DestroyEntity(ent); g_PreviewEntitiesID.clear(); } // Create the new entity if ((*msg->id).empty()) g_PreviewEntityID = INVALID_ENTITY; else { g_PreviewEntityID = g_Game->GetSimulation2()->AddLocalEntity(L"preview|" + *msg->id); g_PreviewEntitiesID.push_back(g_PreviewEntityID); } g_PreviewUnitName = *msg->id; } if (g_PreviewEntityID != INVALID_ENTITY) { // Update the unit's position and orientation: CmpPtr cmpPosition(*g_Game->GetSimulation2(), g_PreviewEntityID); if (cmpPosition) { CVector3D pos = GetUnitPos(msg->pos, cmpPosition->CanFloat()); cmpPosition->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z)); float angle; if (msg->usetarget) { // Aim from pos towards msg->target CVector3D target = msg->target->GetWorldSpace(pos.Y); angle = atan2(target.X-pos.X, target.Z-pos.Z); } else { angle = msg->angle; } cmpPosition->SetYRotation(entity_angle_t::FromFloat(angle)); } // TODO: handle random variations somehow CmpPtr cmpVisual(*g_Game->GetSimulation2(), g_PreviewEntityID); if (cmpVisual) cmpVisual->SetActorSeed(msg->actorseed); CmpPtr cmpOwnership(*g_Game->GetSimulation2(), g_PreviewEntityID); if (cmpOwnership) cmpOwnership->SetOwner((player_id_t)msg->settings->player); CheckObstructionAndUpdateVisual(g_PreviewEntityID); } } BEGIN_COMMAND(CreateObject) { CVector3D m_Pos; float m_Angle; player_id_t m_Player; entity_id_t m_EntityID; u32 m_ActorSeed; void Do() { // Calculate the position/orientation to create this unit with m_Pos = GetUnitPos(msg->pos, true); // don't really care about floating if (msg->usetarget) { // Aim from m_Pos towards msg->target CVector3D target = msg->target->GetWorldSpace(m_Pos.Y); m_Angle = atan2(target.X-m_Pos.X, target.Z-m_Pos.Z); } else { m_Angle = msg->angle; } m_Player = (player_id_t)msg->settings->player; m_ActorSeed = msg->actorseed; // TODO: variation/selection strings Redo(); } void Redo() { m_EntityID = g_Game->GetSimulation2()->AddEntity(*msg->id); if (m_EntityID == INVALID_ENTITY) return; CmpPtr cmpPosition(*g_Game->GetSimulation2(), m_EntityID); if (cmpPosition) { cmpPosition->JumpTo(entity_pos_t::FromFloat(m_Pos.X), entity_pos_t::FromFloat(m_Pos.Z)); cmpPosition->SetYRotation(entity_angle_t::FromFloat(m_Angle)); } CmpPtr cmpOwnership(*g_Game->GetSimulation2(), m_EntityID); if (cmpOwnership) cmpOwnership->SetOwner(m_Player); CmpPtr cmpVisual(*g_Game->GetSimulation2(), m_EntityID); if (cmpVisual) { cmpVisual->SetActorSeed(m_ActorSeed); // TODO: variation/selection strings } } void Undo() { if (m_EntityID != INVALID_ENTITY) { g_Game->GetSimulation2()->DestroyEntity(m_EntityID); m_EntityID = INVALID_ENTITY; } } }; END_COMMAND(CreateObject) QUERYHANDLER(PickObject) { float x, y; msg->pos->GetScreenSpace(x, y); // Normally this function would be called with a player ID to check LOS, // but in Atlas the entire map is revealed, so just pass INVALID_PLAYER entity_id_t ent = EntitySelection::PickEntityAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, INVALID_PLAYER, msg->selectActors);; if (ent == INVALID_ENTITY) msg->id = INVALID_ENTITY; else { msg->id = ent; // Calculate offset of object from original mouse click position // so it gets moved by that offset CmpPtr cmpPosition(*g_Game->GetSimulation2(), ent); if (!cmpPosition || !cmpPosition->IsInWorld()) { // error msg->offsetx = msg->offsety = 0; } else { CFixedVector3D fixed = cmpPosition->GetPosition(); CVector3D centre = CVector3D(fixed.X.ToFloat(), fixed.Y.ToFloat(), fixed.Z.ToFloat()); float cx, cy; g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre, cx, cy); msg->offsetx = (int)(cx - x); msg->offsety = (int)(cy - y); } } } QUERYHANDLER(PickObjectsInRect) { float x0, y0, x1, y1; msg->start->GetScreenSpace(x0, y0); msg->end->GetScreenSpace(x1, y1); // Since owner selections are meaningless in Atlas, use INVALID_PLAYER msg->ids = EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, INVALID_PLAYER, msg->selectActors); } QUERYHANDLER(PickSimilarObjects) { CmpPtr cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); ENSURE(cmpTemplateManager); entity_id_t ent = msg->id; std::string templateName = cmpTemplateManager->GetCurrentTemplateName(ent); // If unit has ownership, only pick units from the same player player_id_t owner = INVALID_PLAYER; CmpPtr cmpOwnership(*g_Game->GetSimulation2(), ent); if (cmpOwnership) owner = cmpOwnership->GetOwner(); msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, owner, false, true, true, false); } MESSAGEHANDLER(ResetSelectionColor) { UNUSED2(msg); for (entity_id_t ent : g_Selection) { CmpPtr cmpVisual(*g_Game->GetSimulation2(), ent); if (cmpVisual) cmpVisual->SetShadingColor(fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1), fixed::FromDouble(1)); } } BEGIN_COMMAND(MoveObjects) { // Mapping from object to position std::map m_PosOld, m_PosNew; void Do() { std::vector ids = *msg->ids; // All selected objects move relative to a pivot object, // so get its position and whether it's floating CVector3D pivotPos(0, 0, 0); bool pivotFloating = false; CmpPtr cmpPositionPivot(*g_Game->GetSimulation2(), (entity_id_t)msg->pivot); if (cmpPositionPivot && cmpPositionPivot->IsInWorld()) { pivotFloating = cmpPositionPivot->CanFloat(); CFixedVector3D pivotFixed = cmpPositionPivot->GetPosition(); pivotPos = CVector3D(pivotFixed.X.ToFloat(), pivotFixed.Y.ToFloat(), pivotFixed.Z.ToFloat()); } // Calculate directional vector of movement for pivot object, // we apply the same movement to all objects CVector3D targetPos = GetUnitPos(msg->pos, pivotFloating); CVector3D dir = targetPos - pivotPos; for (entity_id_t id : ids) { CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); if (!cmpPosition || !cmpPosition->IsInWorld()) { // error m_PosOld[id] = m_PosNew[id] = CVector3D(0, 0, 0); } else { // Calculate this object's position CFixedVector3D posFixed = cmpPosition->GetPosition(); CVector3D pos = CVector3D(posFixed.X.ToFloat(), posFixed.Y.ToFloat(), posFixed.Z.ToFloat()); m_PosNew[id] = pos + dir; m_PosOld[id] = pos; } } SetPos(m_PosNew); } void SetPos(const std::map& map) { for (const std::pair& p : map) { CmpPtr cmpPosition(*g_Game->GetSimulation2(), p.first); if (!cmpPosition) return; // Set 2D position, ignoring height cmpPosition->JumpTo(entity_pos_t::FromFloat(p.second.X), entity_pos_t::FromFloat(p.second.Z)); CheckObstructionAndUpdateVisual(p.first); } } void Redo() { SetPos(m_PosNew); } void Undo() { SetPos(m_PosOld); } void MergeIntoPrevious(cMoveObjects* prev) { // TODO: do something valid if prev selection != this selection ENSURE(*(prev->msg->ids) == *(msg->ids)); prev->m_PosNew = m_PosNew; } }; END_COMMAND(MoveObjects) BEGIN_COMMAND(RotateObjectsFromCenterPoint) { std::map m_PosOld, m_PosNew; std::map m_AngleOld, m_AngleNew; CVector3D m_CenterPoint; float m_AngleInitialRotation; void Do() { std::vector ids = *msg->ids; CVector3D minPos; CVector3D maxPos; bool first = true; // Compute min position and max position for (entity_id_t id : ids) { CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); if (!cmpPosition) continue; CVector3D pos = cmpPosition->GetPosition(); m_PosOld[id] = cmpPosition->GetPosition(); m_AngleOld[id] = cmpPosition->GetRotation().Y.ToFloat(); if (first) { first = false; minPos = pos; maxPos = pos; m_CenterPoint.Y = pos.Y; continue; } if (pos.X < minPos.X) minPos.X = pos.X; if (pos.X > maxPos.X) maxPos.X = pos.X; if (pos.Z < minPos.Z) minPos.Z = pos.Z; if (pos.Z > maxPos.Z) maxPos.Z = pos.Z; } // Calculate objects center point m_CenterPoint.X = minPos.X + ((maxPos.X - minPos.X) * 0.5); m_CenterPoint.Z = minPos.Z + ((maxPos.Z - minPos.Z) * 0.5); CVector3D target = msg->target->GetWorldSpace(m_CenterPoint.Y); m_AngleInitialRotation = atan2(target.X-m_CenterPoint.X, target.Z-m_CenterPoint.Z); } void SetPos(const std::map& position, const std::map& angle) { for (const std::pair& p : position) { CmpPtr cmpPosition(*g_Game->GetSimulation2(), p.first); if (!cmpPosition) return; // Set 2D position, ignoring height cmpPosition->JumpTo(entity_pos_t::FromFloat(p.second.X), entity_pos_t::FromFloat(p.second.Z)); if (msg->rotateObject) cmpPosition->SetYRotation(entity_angle_t::FromFloat(angle.at(p.first))); } for (const std::pair& p : position) CheckObstructionAndUpdateVisual(p.first); } void Redo() { SetPos(m_PosNew, m_AngleNew); } void RecalculateRotation(Position newPoint) { std::vector ids = *msg->ids; CVector3D target = newPoint.GetWorldSpace(m_CenterPoint.Y); float newAngle = atan2(target.X-m_CenterPoint.X, target.Z-m_CenterPoint.Z); float globalAngle = m_AngleInitialRotation - newAngle; // Recalculate positions for (entity_id_t id : ids) { CVector3D pos = m_PosOld[id]; float angle = atan2(pos.X - m_CenterPoint.X, pos.Z - m_CenterPoint.Z); float localAngle = angle + (globalAngle - angle); float xCos = cosf(localAngle); float xSin = sinf(localAngle); pos.X -= m_CenterPoint.X; pos.Z -= m_CenterPoint.Z; float newX = pos.X * xCos - pos.Z * xSin; float newZ = pos.X * xSin + pos.Z * xCos; pos.X = newX + m_CenterPoint.X; pos.Z = newZ + m_CenterPoint.Z; m_PosNew[id] = pos; m_AngleNew[id] = m_AngleOld[id] - globalAngle; } SetPos(m_PosNew, m_AngleNew); } void Undo() { SetPos(m_PosOld, m_AngleOld); } void MergeIntoPrevious(cRotateObjectsFromCenterPoint* prev) { // TODO: do something valid if prev unit != this unit ENSURE(*prev->msg->ids == *msg->ids); m_PosOld = prev->m_PosOld; m_AngleInitialRotation = prev->m_AngleInitialRotation; m_AngleOld = prev->m_AngleOld; m_CenterPoint = prev->m_CenterPoint; RecalculateRotation(msg->target); } }; END_COMMAND(RotateObjectsFromCenterPoint) BEGIN_COMMAND(RotateObject) { std::map m_AngleOld, m_AngleNew; void Do() { std::vector ids = *msg->ids; for (entity_id_t id : ids) { CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); if (!cmpPosition) return; m_AngleOld[id] = cmpPosition->GetRotation().Y.ToFloat(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(0.f); CVector3D pos = transform.GetTranslation(); CVector3D target = msg->target->GetWorldSpace(pos.Y); m_AngleNew[id] = atan2(target.X-pos.X, target.Z-pos.Z); } SetAngle(m_AngleNew); } void SetAngle(const std::map& angles) { for (const std::pair& p : angles) { CmpPtr cmpPosition(*g_Game->GetSimulation2(), p.first); if (!cmpPosition) return; cmpPosition->SetYRotation(entity_angle_t::FromFloat(p.second)); } } void Redo() { SetAngle(m_AngleNew); } void Undo() { SetAngle(m_AngleOld); } void MergeIntoPrevious(cRotateObject* prev) { // TODO: do something valid if prev unit != this unit ENSURE(*prev->msg->ids == *msg->ids); prev->m_AngleNew = m_AngleNew; } }; END_COMMAND(RotateObject) BEGIN_COMMAND(DeleteObjects) { // Saved copy of the important aspects of a unit, to allow undo struct OldObject { entity_id_t entityID; CStr templateName; player_id_t owner; CFixedVector3D pos; CFixedVector3D rot; u32 actorSeed; }; std::vector oldObjects; cDeleteObjects() { } void Do() { Redo(); } void Redo() { CSimulation2& sim = *g_Game->GetSimulation2(); CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY); ENSURE(cmpTemplateManager); std::vector ids = *msg->ids; for (size_t i = 0; i < ids.size(); ++i) { OldObject obj; obj.entityID = (entity_id_t)ids[i]; obj.templateName = cmpTemplateManager->GetCurrentTemplateName(obj.entityID); CmpPtr cmpOwnership(sim, obj.entityID); if (cmpOwnership) obj.owner = cmpOwnership->GetOwner(); CmpPtr cmpPosition(sim, obj.entityID); if (cmpPosition) { obj.pos = cmpPosition->GetPosition(); obj.rot = cmpPosition->GetRotation(); } CmpPtr cmpVisual(sim, obj.entityID); if (cmpVisual) obj.actorSeed = cmpVisual->GetActorSeed(); oldObjects.push_back(obj); g_Game->GetSimulation2()->DestroyEntity(obj.entityID); } g_Game->GetSimulation2()->FlushDestroyedEntities(); } void Undo() { CSimulation2& sim = *g_Game->GetSimulation2(); for (size_t i = 0; i < oldObjects.size(); ++i) { entity_id_t ent = sim.AddEntity(oldObjects[i].templateName.FromUTF8(), oldObjects[i].entityID); if (ent == INVALID_ENTITY) { LOGERROR("Failed to load entity template '%s'", oldObjects[i].templateName.c_str()); } else { CmpPtr cmpPosition(sim, oldObjects[i].entityID); if (cmpPosition) { cmpPosition->JumpTo(oldObjects[i].pos.X, oldObjects[i].pos.Z); cmpPosition->SetXZRotation(oldObjects[i].rot.X, oldObjects[i].rot.Z); cmpPosition->SetYRotation(oldObjects[i].rot.Y); } CmpPtr cmpOwnership(sim, oldObjects[i].entityID); if (cmpOwnership) cmpOwnership->SetOwner(oldObjects[i].owner); CmpPtr cmpVisual(sim, oldObjects[i].entityID); if (cmpVisual) cmpVisual->SetActorSeed(oldObjects[i].actorSeed); } } oldObjects.clear(); } }; END_COMMAND(DeleteObjects) QUERYHANDLER(GetPlayerObjects) { std::vector ids; player_id_t playerID = msg->player; const CSimulation2::InterfaceListUnordered& cmps = g_Game->GetSimulation2()->GetEntitiesWithInterfaceUnordered(IID_Ownership); for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit) { if (static_cast(eit->second)->GetOwner() == playerID) { ids.push_back(eit->first); } } msg->ids = ids; } MESSAGEHANDLER(SetBandbox) { AtlasView::GetView_Game()->SetBandbox(msg->show, (float)msg->sx0, (float)msg->sy0, (float)msg->sx1, (float)msg->sy1); } QUERYHANDLER(GetSelectedObjectsTemplateNames) { std::vector ids = *msg->ids; std::vector names; CmpPtr cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); ENSURE(cmpTemplateManager); for (size_t i = 0; i < ids.size(); ++i) { entity_id_t id = (entity_id_t)ids[i]; std::string templateName = cmpTemplateManager->GetCurrentTemplateName(id); names.push_back(templateName); } std::sort(names.begin(), names.end()); msg->names = names; } } // namespace AtlasMessage