mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
- Changed the obstruction check to use the unit's passability class. Ref: https://code.wildfiregames.com/rP16149 Fix: #8053
1137 lines
30 KiB
C++
1137 lines
30 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include <cfloat>
|
|
#include <map>
|
|
|
|
#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/ICmpUnitMotion.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<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>& UNUSED(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)
|
|
{
|
|
UNUSED2(msg);
|
|
|
|
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)
|
|
{
|
|
UNUSED2(msg);
|
|
|
|
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
|