mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 13:53:57 -07:00
Make include-what-you-use happy with some files in source/tools/atlas and fix what needs to be fixed. Add "source" to include path for AtlasUI target to allow for absolute includes. In the future all but one should be removed. Drop check for at least boost 1.40. Ref: #8086 Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
1149 lines
31 KiB
C++
1149 lines
31 KiB
C++
/* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "MessageHandler.h"
|
|
|
|
#include "graphics/Camera.h"
|
|
#include "graphics/Color.h"
|
|
#include "graphics/GameView.h"
|
|
#include "graphics/Terrain.h"
|
|
#include "lib/debug.h"
|
|
#include "lib/types.h"
|
|
#include "lib/utf8.h"
|
|
#include "maths/Fixed.h"
|
|
#include "maths/FixedVector3D.h"
|
|
#include "maths/MathUtil.h"
|
|
#include "maths/Matrix3D.h"
|
|
#include "maths/Vector2D.h"
|
|
#include "maths/Vector3D.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/CStr.h"
|
|
#include "ps/Game.h"
|
|
#include "ps/World.h"
|
|
#include "ps/XML/XMLWriter.h"
|
|
#include "simulation2/Simulation2.h"
|
|
#include "simulation2/components/ICmpObstruction.h"
|
|
#include "simulation2/components/ICmpOwnership.h"
|
|
#include "simulation2/components/ICmpPlayer.h"
|
|
#include "simulation2/components/ICmpPlayerManager.h"
|
|
#include "simulation2/components/ICmpPosition.h"
|
|
#include "simulation2/components/ICmpSelectable.h"
|
|
#include "simulation2/components/ICmpTemplateManager.h"
|
|
#include "simulation2/components/ICmpUnitMotion.h"
|
|
#include "simulation2/components/ICmpVisual.h"
|
|
#include "simulation2/helpers/Player.h"
|
|
#include "simulation2/helpers/Position.h"
|
|
#include "simulation2/helpers/Selection.h"
|
|
#include "simulation2/system/Component.h"
|
|
#include "simulation2/system/Entity.h"
|
|
#include "tools/atlas/GameInterface/CommandProc.h"
|
|
#include "tools/atlas/GameInterface/Messages.h"
|
|
#include "tools/atlas/GameInterface/Shareable.h"
|
|
#include "tools/atlas/GameInterface/SharedTypes.h"
|
|
#include "tools/atlas/GameInterface/View.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cwchar>
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
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<ICmpObstruction> cmpObstruction(*g_Game->GetSimulation2(), ent);
|
|
if (cmpObstruction)
|
|
{
|
|
std::string passClassName = "default";
|
|
CmpPtr<ICmpUnitMotion> cmpUnitMotion(*g_Game->GetSimulation2(), ent);
|
|
if (cmpUnitMotion)
|
|
passClassName = cmpUnitMotion->GetPassabilityClassName();
|
|
|
|
ICmpObstruction::EFoundationCheck result = cmpObstruction->CheckFoundation(passClassName);
|
|
if (result != ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CheckObstructionAndUpdateVisual(entity_id_t id)
|
|
{
|
|
CmpPtr<ICmpVisual> 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<sObjectsListItem> objects;
|
|
|
|
CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
|
|
if (cmpTemplateManager)
|
|
{
|
|
std::vector<std::string> names = cmpTemplateManager->FindAllTemplates(true);
|
|
|
|
for (std::vector<std::string>::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<entity_id_t> g_Selection;
|
|
typedef std::map<player_id_t, CColor> 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<ICmpOwnership> cmpOwnership(sim, id);
|
|
if (cmpOwnership)
|
|
{
|
|
player_id_t owner = cmpOwnership->GetOwner();
|
|
if (colorMap.find(owner) != colorMap.end())
|
|
return colorMap[owner];
|
|
else
|
|
{
|
|
CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
|
|
entity_id_t playerEnt = cmpPlayerManager->GetPlayerByID(owner);
|
|
CmpPtr<ICmpPlayer> 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<ICmpSelectable> 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<ICmpSelectable> 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<ICmpOwnership> 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<std::vector<CStr> > groups = unit->GetObject().m_Base->GetVariantGroups();
|
|
const std::set<CStr>& selections = unit->GetActorSelections();
|
|
|
|
// Iterate over variant groups
|
|
std::vector<std::vector<std::wstring> > variantgroups;
|
|
std::set<std::wstring> selections_set;
|
|
variantgroups.reserve(groups.size());
|
|
for (size_t i = 0; i < groups.size(); ++i)
|
|
{
|
|
// Copy variants into output structure
|
|
|
|
std::vector<std::wstring> 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<std::wstring> (selections_set.begin(), selections_set.end()); // convert set->vector
|
|
*/
|
|
|
|
msg->settings = settings;
|
|
}
|
|
|
|
QUERYHANDLER(GetObjectMapSettings)
|
|
{
|
|
std::vector<entity_id_t> ids = *msg->ids;
|
|
|
|
CmpPtr<ICmpTemplateManager> 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<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), id);
|
|
if (cmpOwnership)
|
|
entityTag.Setting("Player", static_cast<int>(cmpOwnership->GetOwner()));
|
|
|
|
//Adding position to make some relative position later
|
|
CmpPtr<ICmpPosition> 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<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), id);
|
|
if (cmpVisual)
|
|
entityTag.Setting("ActorSeed", static_cast<unsigned int>(cmpVisual->GetActorSeed()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const CStr& data = exampleFile.GetOutput();
|
|
msg->xmldata = data.FromUTF8();
|
|
}
|
|
|
|
|
|
BEGIN_COMMAND(SetObjectSettings)
|
|
{
|
|
player_id_t m_PlayerOld, m_PlayerNew;
|
|
std::set<CStr> m_SelectionsOld, m_SelectionsNew;
|
|
|
|
void Do()
|
|
{
|
|
sObjectSettings settings = msg->settings;
|
|
|
|
AtlasView* view = AtlasView::GetView(msg->view);
|
|
CSimulation2* simulation = view->GetSimulation2();
|
|
|
|
CmpPtr<ICmpOwnership> 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<std::wstring> selections = *settings.selections;
|
|
for (std::vector<std::wstring>::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<CStr>& /*selections*/)
|
|
{
|
|
AtlasView* view = AtlasView::GetView(msg->view);
|
|
CSimulation2* simulation = view->GetSimulation2();
|
|
|
|
CmpPtr<ICmpOwnership> 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<entity_id_t> 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)
|
|
{
|
|
if (g_PreviewEntitiesID.size() == 0)
|
|
return;
|
|
|
|
CmpPtr<ICmpTemplateManager> 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<ICmpPosition> cmpPositionNew(*g_Game->GetSimulation2(), new_ent);
|
|
CmpPtr<ICmpPosition> 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<ICmpOwnership> cmpOwnershipNew(*g_Game->GetSimulation2(), new_ent);
|
|
CmpPtr<ICmpOwnership> cmpOwnershipOld(*g_Game->GetSimulation2(), ent);
|
|
if (cmpOwnershipNew && cmpOwnershipOld)
|
|
cmpOwnershipNew->SetOwner(cmpOwnershipOld->GetOwner());
|
|
|
|
//getVisual
|
|
CmpPtr<ICmpVisual> cmpVisualNew(*g_Game->GetSimulation2(), new_ent);
|
|
CmpPtr<ICmpVisual> cmpVisualOld(*g_Game->GetSimulation2(), ent);
|
|
if (cmpVisualNew && cmpVisualOld)
|
|
cmpVisualNew->SetActorSeed(cmpVisualOld->GetActorSeed());
|
|
|
|
//Update g_selectedObject and higligth
|
|
g_Selection.push_back(new_ent);
|
|
CmpPtr<ICmpSelectable> 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<ICmpPosition> 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<ICmpPosition> 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<ICmpPosition> 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<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), g_PreviewEntityID);
|
|
if (cmpVisual)
|
|
cmpVisual->SetActorSeed(msg->actorseed);
|
|
|
|
CmpPtr<ICmpOwnership> 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<ICmpPosition> 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<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), m_EntityID);
|
|
if (cmpOwnership)
|
|
cmpOwnership->SetOwner(m_Player);
|
|
|
|
CmpPtr<ICmpVisual> 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<ICmpPosition> 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());
|
|
|
|
const CVector2D screenPos{g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre)};
|
|
|
|
msg->offsetx = static_cast<int>(screenPos.X - x);
|
|
msg->offsety = static_cast<int>(screenPos.Y - 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<ICmpTemplateManager> 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<ICmpOwnership> 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)
|
|
{
|
|
for (entity_id_t ent : g_Selection)
|
|
{
|
|
CmpPtr<ICmpVisual> 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<entity_id_t, CVector3D> m_PosOld, m_PosNew;
|
|
|
|
void Do()
|
|
{
|
|
std::vector<entity_id_t> 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<ICmpPosition> 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<ICmpPosition> 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<entity_id_t, CVector3D>& map)
|
|
{
|
|
for (const std::pair<const entity_id_t, CVector3D>& p : map)
|
|
{
|
|
CmpPtr<ICmpPosition> 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<entity_id_t, CVector3D> m_PosOld, m_PosNew;
|
|
std::map<entity_id_t, float> m_AngleOld, m_AngleNew;
|
|
CVector3D m_CenterPoint;
|
|
float m_AngleInitialRotation;
|
|
|
|
void Do()
|
|
{
|
|
std::vector<entity_id_t> ids = *msg->ids;
|
|
|
|
CVector3D minPos;
|
|
CVector3D maxPos;
|
|
|
|
bool first = true;
|
|
|
|
// Compute min position and max position
|
|
for (entity_id_t id : ids)
|
|
{
|
|
CmpPtr<ICmpPosition> 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<entity_id_t, CVector3D>& position, const std::map<entity_id_t, float>& angle)
|
|
{
|
|
for (const std::pair<const entity_id_t, CVector3D>& p : position)
|
|
{
|
|
CmpPtr<ICmpPosition> 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<const entity_id_t, CVector3D>& p : position)
|
|
CheckObstructionAndUpdateVisual(p.first);
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
SetPos(m_PosNew, m_AngleNew);
|
|
}
|
|
|
|
void RecalculateRotation(Position newPoint)
|
|
{
|
|
std::vector<entity_id_t> 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<entity_id_t, float> m_AngleOld, m_AngleNew;
|
|
|
|
void Do()
|
|
{
|
|
std::vector<entity_id_t> ids = *msg->ids;
|
|
|
|
for (entity_id_t id : ids)
|
|
{
|
|
CmpPtr<ICmpPosition> 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<entity_id_t, float>& angles)
|
|
{
|
|
for (const std::pair<const entity_id_t, float>& p : angles)
|
|
{
|
|
CmpPtr<ICmpPosition> 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<OldObject> oldObjects;
|
|
|
|
cDeleteObjects()
|
|
{
|
|
}
|
|
|
|
void Do()
|
|
{
|
|
Redo();
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
CSimulation2& sim = *g_Game->GetSimulation2();
|
|
CmpPtr<ICmpTemplateManager> cmpTemplateManager(sim, SYSTEM_ENTITY);
|
|
ENSURE(cmpTemplateManager);
|
|
|
|
std::vector<entity_id_t> 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<ICmpOwnership> cmpOwnership(sim, obj.entityID);
|
|
if (cmpOwnership)
|
|
obj.owner = cmpOwnership->GetOwner();
|
|
|
|
CmpPtr<ICmpPosition> cmpPosition(sim, obj.entityID);
|
|
if (cmpPosition)
|
|
{
|
|
obj.pos = cmpPosition->GetPosition();
|
|
obj.rot = cmpPosition->GetRotation();
|
|
}
|
|
|
|
CmpPtr<ICmpVisual> 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<ICmpPosition> 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<ICmpOwnership> cmpOwnership(sim, oldObjects[i].entityID);
|
|
if (cmpOwnership)
|
|
cmpOwnership->SetOwner(oldObjects[i].owner);
|
|
|
|
CmpPtr<ICmpVisual> cmpVisual(sim, oldObjects[i].entityID);
|
|
if (cmpVisual)
|
|
cmpVisual->SetActorSeed(oldObjects[i].actorSeed);
|
|
}
|
|
}
|
|
|
|
oldObjects.clear();
|
|
}
|
|
};
|
|
END_COMMAND(DeleteObjects)
|
|
|
|
QUERYHANDLER(GetPlayerObjects)
|
|
{
|
|
std::vector<entity_id_t> 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<ICmpOwnership*>(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<entity_id_t> ids = *msg->ids;
|
|
std::vector<std::string> names;
|
|
|
|
CmpPtr<ICmpTemplateManager> 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
|