forked from 0ad/0ad
Ykkrosh
d39a4fac21
# Attempted to make compilation faster by including as little as possible in some .h files, and moving it into .cpp. Fixed BaseTechCollection memory leak. This was SVN commit r3992.
680 lines
15 KiB
C++
680 lines
15 KiB
C++
#include "precompiled.h"
|
|
|
|
#include <float.h>
|
|
|
|
#include "MessageHandler.h"
|
|
#include "../CommandProc.h"
|
|
|
|
#include "graphics/GameView.h"
|
|
#include "graphics/Model.h"
|
|
#include "graphics/ObjectManager.h"
|
|
#include "graphics/Terrain.h"
|
|
#include "graphics/Unit.h"
|
|
#include "graphics/UnitManager.h"
|
|
#include "lib/ogl.h"
|
|
#include "maths/MathUtil.h"
|
|
#include "maths/Matrix3D.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/Game.h"
|
|
#include "ps/World.h"
|
|
#include "simulation/BaseEntityCollection.h"
|
|
#include "simulation/Entity.h"
|
|
#include "simulation/EntityManager.h"
|
|
|
|
#define LOG_CATEGORY "editor"
|
|
|
|
namespace AtlasMessage {
|
|
|
|
static bool SortObjectsList(const sObjectsListItem& a, const sObjectsListItem& b)
|
|
{
|
|
return wcscmp(a.name.c_str(), b.name.c_str()) < 0;
|
|
}
|
|
|
|
QUERYHANDLER(GetObjectsList)
|
|
{
|
|
std::vector<sObjectsListItem> objects;
|
|
{
|
|
std::vector<CStrW> names;
|
|
g_EntityTemplateCollection.getBaseEntityNames(names);
|
|
for (std::vector<CStrW>::iterator it = names.begin(); it != names.end(); ++it)
|
|
{
|
|
//CBaseEntity* baseent = g_EntityTemplateCollection.getTemplate(*it);
|
|
sObjectsListItem e;
|
|
e.id = L"(e) " + *it;
|
|
e.name = *it; //baseent->m_Tag
|
|
e.type = 0;
|
|
objects.push_back(e);
|
|
}
|
|
}
|
|
|
|
{
|
|
std::vector<CStr> names;
|
|
//g_ObjMan.GetPropObjectNames(names);
|
|
g_ObjMan.GetAllObjectNames(names);
|
|
for (std::vector<CStr>::iterator it = names.begin(); it != names.end(); ++it)
|
|
{
|
|
sObjectsListItem e;
|
|
e.id = L"(n) " + CStrW(*it);
|
|
e.name = CStrW(*it).AfterFirst(/*L"props/"*/ L"actors/");
|
|
e.type = 1;
|
|
objects.push_back(e);
|
|
}
|
|
}
|
|
std::sort(objects.begin(), objects.end(), SortObjectsList);
|
|
msg->objects = objects;
|
|
}
|
|
|
|
|
|
static std::vector<ObjectID> g_Selection;
|
|
void AtlasRenderSelection()
|
|
{
|
|
glDisable(GL_DEPTH_TEST);
|
|
for (size_t i = 0; i < g_Selection.size(); ++i)
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(g_Selection[i]);
|
|
if (unit)
|
|
{
|
|
if (unit->GetEntity())
|
|
{
|
|
unit->GetEntity()->renderSelectionOutline();
|
|
}
|
|
else
|
|
{
|
|
const CBound& bound = unit->GetModel()->GetBounds();
|
|
// Expand bounds by 10% around the centre
|
|
CVector3D centre;
|
|
bound.GetCentre(centre);
|
|
CVector3D a = (bound[0] - centre) * 1.1f + centre;
|
|
CVector3D b = (bound[1] - centre) * 1.1f + centre;
|
|
float h = g_Game->GetWorld()->GetTerrain()->getExactGroundLevel(centre.X, centre.Z);
|
|
glColor3f(0.8f, 0.8f, 0.8f);
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex3f(a.X, h, a.Z);
|
|
glVertex3f(a.X, h, b.Z);
|
|
glVertex3f(b.X, h, b.Z);
|
|
glVertex3f(b.X, h, a.Z);
|
|
glEnd();
|
|
}
|
|
}
|
|
}
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
MESSAGEHANDLER(SetSelectionPreview)
|
|
{
|
|
g_Selection = *msg->ids;
|
|
}
|
|
|
|
QUERYHANDLER(GetObjectSettings)
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
sObjectSettings settings;
|
|
settings.player = unit->GetPlayerID();
|
|
|
|
// Get the unit's possible variants and selected variants
|
|
std::vector<std::vector<CStrW> > groups = unit->GetObject()->m_Base->GetVariantGroups();
|
|
const std::set<CStrW>& 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(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(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;
|
|
}
|
|
|
|
BEGIN_COMMAND(SetObjectSettings)
|
|
|
|
int m_PlayerOld, m_PlayerNew;
|
|
std::set<CStrW> m_SelectionsOld, m_SelectionsNew;
|
|
|
|
void Do()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
sObjectSettings settings = msg->settings;
|
|
|
|
m_PlayerOld = unit->GetPlayerID();
|
|
m_PlayerNew = settings.player;
|
|
|
|
m_SelectionsOld = unit->GetActorSelections();
|
|
|
|
std::vector<std::wstring> selections = *settings.selections;
|
|
copy(selections.begin(), selections.end(),
|
|
std::insert_iterator<std::set<CStrW> >(m_SelectionsNew, m_SelectionsNew.begin()));
|
|
|
|
Redo();
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
unit->SetPlayerID(m_PlayerNew);
|
|
unit->SetActorSelections(m_SelectionsNew);
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
unit->SetPlayerID(m_PlayerOld);
|
|
unit->SetActorSelections(m_SelectionsOld);
|
|
}
|
|
|
|
END_COMMAND(SetObjectSettings);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
static CUnit* g_PreviewUnit = NULL;
|
|
static CStrW g_PreviewUnitID;
|
|
|
|
// Returns roughly the largest number smaller than f (i.e. closer to zero)
|
|
static float flt_minus_epsilon(float f)
|
|
{
|
|
return f - (FLT_EPSILON * f);
|
|
}
|
|
|
|
static CVector3D GetUnitPos(const Position& pos)
|
|
{
|
|
static CVector3D vec;
|
|
pos.GetWorldSpace(vec, vec); // if msg->pos is 'Unchanged', use the previous pos
|
|
|
|
float xOnMap = clamp(vec.X, 0.f, flt_minus_epsilon((g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide()-1)*CELL_SIZE));
|
|
float zOnMap = clamp(vec.Z, 0.f, flt_minus_epsilon((g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide()-1)*CELL_SIZE));
|
|
if (xOnMap != vec.X || zOnMap != vec.Z)
|
|
{
|
|
vec.X = xOnMap;
|
|
vec.Z = zOnMap;
|
|
vec.Y = g_Game->GetWorld()->GetTerrain()->getExactGroundLevel(xOnMap, zOnMap);
|
|
}
|
|
|
|
return vec;
|
|
}
|
|
|
|
static bool ParseObjectName(const CStrW& obj, bool& isEntity, CStrW& name)
|
|
{
|
|
if (obj.substr(0, 4) == L"(e) ")
|
|
{
|
|
isEntity = true;
|
|
name = obj.substr(4);
|
|
return true;
|
|
}
|
|
else if (obj.substr(0, 4) == L"(n) ")
|
|
{
|
|
isEntity = false;
|
|
name = obj.substr(4);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MESSAGEHANDLER(ObjectPreview)
|
|
{
|
|
if (*msg->id != g_PreviewUnitID)
|
|
{
|
|
// Delete old unit
|
|
if (g_PreviewUnit)
|
|
{
|
|
g_UnitMan.RemoveUnit(g_PreviewUnit);
|
|
delete g_PreviewUnit;
|
|
g_PreviewUnit = NULL;
|
|
}
|
|
|
|
bool isEntity;
|
|
CStrW name;
|
|
if (ParseObjectName(*msg->id, isEntity, name))
|
|
{
|
|
std::set<CStrW> selections; // TODO: get selections from user
|
|
|
|
// Create new unit
|
|
if (isEntity)
|
|
{
|
|
CBaseEntity* base = g_EntityTemplateCollection.getTemplate(name);
|
|
if (base) // (ignore errors)
|
|
{
|
|
g_PreviewUnit = g_UnitMan.CreateUnit(base->m_actorName, NULL, selections);
|
|
// TODO: variations
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_PreviewUnit = g_UnitMan.CreateUnit(CStr(name), NULL, selections);
|
|
}
|
|
}
|
|
|
|
g_PreviewUnitID = *msg->id;
|
|
}
|
|
|
|
if (g_PreviewUnit)
|
|
{
|
|
// Update the unit's position and orientation:
|
|
|
|
CVector3D pos = GetUnitPos(msg->pos);
|
|
|
|
float s, c;
|
|
|
|
if (msg->usetarget)
|
|
{
|
|
// Aim from pos towards msg->target
|
|
CVector3D target;
|
|
msg->target->GetWorldSpace(target, pos.Y);
|
|
CVector2D dir(target.X-pos.X, target.Z-pos.Z);
|
|
dir = dir.normalize();
|
|
s = dir.x;
|
|
c = dir.y;
|
|
}
|
|
else
|
|
{
|
|
s = sin(msg->angle);
|
|
c = cos(msg->angle);
|
|
}
|
|
|
|
CMatrix3D m;
|
|
m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = pos.X;
|
|
m._21 = 0.0f; m._22 = 1.0f; m._23 = 0.0f; m._24 = pos.Y;
|
|
m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = pos.Z;
|
|
m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f;
|
|
g_PreviewUnit->GetModel()->SetTransform(m);
|
|
|
|
// Update the unit's player colour:
|
|
g_PreviewUnit->SetPlayerID(msg->settings->player);
|
|
}
|
|
}
|
|
|
|
BEGIN_COMMAND(CreateObject)
|
|
|
|
CVector3D m_Pos;
|
|
float m_Angle;
|
|
int m_ID;
|
|
int m_Player;
|
|
|
|
void Do()
|
|
{
|
|
// Calculate the position/orientation to create this unit with
|
|
|
|
m_Pos = GetUnitPos(msg->pos);
|
|
|
|
if (msg->usetarget)
|
|
{
|
|
// Aim from m_Pos towards msg->target
|
|
CVector3D target;
|
|
msg->target->GetWorldSpace(target, m_Pos.Y);
|
|
CVector2D dir(target.X-m_Pos.X, target.Z-m_Pos.Z);
|
|
m_Angle = atan2(dir.x, dir.y);
|
|
}
|
|
else
|
|
{
|
|
m_Angle = msg->angle;
|
|
}
|
|
|
|
// TODO: variations too
|
|
m_Player = msg->settings->player;
|
|
|
|
// Get a new ID, for future reference to this unit
|
|
m_ID = g_UnitMan.GetNewID();
|
|
|
|
Redo();
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
bool isEntity;
|
|
CStrW name;
|
|
if (ParseObjectName(*msg->id, isEntity, name))
|
|
{
|
|
std::set<CStrW> selections;
|
|
|
|
if (isEntity)
|
|
{
|
|
CBaseEntity* base = g_EntityTemplateCollection.getTemplate(name);
|
|
if (! base)
|
|
LOG(ERROR, LOG_CATEGORY, "Failed to load entity template '%ls'", name.c_str());
|
|
else
|
|
{
|
|
HEntity ent = g_EntityManager.create(base, m_Pos, m_Angle, selections);
|
|
|
|
if (! ent)
|
|
{
|
|
LOG(ERROR, LOG_CATEGORY, "Failed to create entity of type '%ls'", name.c_str());
|
|
}
|
|
else if (! ent->m_actor)
|
|
{
|
|
// We don't want to allow entities with no actors, because
|
|
// they'll be be invisible and will confuse scenario designers
|
|
LOG(ERROR, LOG_CATEGORY, "Failed to create entity of type '%ls'", name.c_str());
|
|
ent->kill();
|
|
}
|
|
else
|
|
{
|
|
ent->SetPlayer(g_Game->GetPlayer(m_Player));
|
|
ent->m_actor->SetID(m_ID);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CUnit* unit = g_UnitMan.CreateUnit(CStr(name), NULL, selections);
|
|
if (! unit)
|
|
{
|
|
LOG(ERROR, LOG_CATEGORY, "Failed to load nonentity actor '%ls'", name.c_str());
|
|
}
|
|
else
|
|
{
|
|
unit->SetID(m_ID);
|
|
|
|
float s = sin(m_Angle);
|
|
float c = cos(m_Angle);
|
|
|
|
CMatrix3D m;
|
|
m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = m_Pos.X;
|
|
m._21 = 0.0f; m._22 = 1.0f; m._23 = 0.0f; m._24 = m_Pos.Y;
|
|
m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = m_Pos.Z;
|
|
m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f;
|
|
unit->GetModel()->SetTransform(m);
|
|
|
|
unit->SetPlayerID(m_Player);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(m_ID);
|
|
if (unit)
|
|
{
|
|
if (unit->GetEntity())
|
|
unit->GetEntity()->kill();
|
|
else
|
|
{
|
|
g_UnitMan.RemoveUnit(unit);
|
|
delete unit;
|
|
}
|
|
}
|
|
}
|
|
|
|
END_COMMAND(CreateObject)
|
|
|
|
|
|
QUERYHANDLER(PickObject)
|
|
{
|
|
float x, y;
|
|
msg->pos->GetScreenSpace(x, y);
|
|
|
|
CVector3D rayorigin, raydir;
|
|
g_Game->GetView()->GetCamera()->BuildCameraRay((int)x, (int)y, rayorigin, raydir);
|
|
|
|
CUnit* target = g_UnitMan.PickUnit(rayorigin, raydir);
|
|
|
|
if (target)
|
|
msg->id = target->GetID();
|
|
else
|
|
msg->id = -1;
|
|
|
|
if (target)
|
|
{
|
|
// Get screen coordinates of the point on the ground underneath the
|
|
// object's model-centre, so that callers know the offset to use when
|
|
// working out the screen coordinates to move the object to.
|
|
// (TODO: http://trac.0ad.homeip.net/ticket/99)
|
|
CVector3D centre = target->GetModel()->GetTransform().GetTranslation();
|
|
centre.Y = g_Game->GetWorld()->GetTerrain()->getExactGroundLevel(centre.X, centre.Z);
|
|
float cx, cy;
|
|
g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre, cx, cy);
|
|
|
|
msg->offsetx = (int)(cx - x);
|
|
msg->offsety = (int)(cy - y);
|
|
}
|
|
else
|
|
{
|
|
msg->offsetx = msg->offsety = 0;
|
|
}
|
|
}
|
|
|
|
|
|
BEGIN_COMMAND(MoveObject)
|
|
|
|
CVector3D m_PosOld, m_PosNew;
|
|
|
|
void Do()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
if (unit->GetEntity())
|
|
{
|
|
m_PosOld = unit->GetEntity()->m_position;
|
|
}
|
|
else
|
|
{
|
|
CMatrix3D m = unit->GetModel()->GetTransform();
|
|
m_PosOld = m.GetTranslation();
|
|
}
|
|
|
|
m_PosNew = GetUnitPos(msg->pos);
|
|
|
|
SetPos(m_PosNew);
|
|
}
|
|
|
|
void SetPos(CVector3D& pos)
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
if (unit->GetEntity())
|
|
{
|
|
unit->GetEntity()->m_position = pos;
|
|
}
|
|
else
|
|
{
|
|
CMatrix3D m = unit->GetModel()->GetTransform();
|
|
m.Translate(pos - m.GetTranslation());
|
|
unit->GetModel()->SetTransform(m);
|
|
}
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
SetPos(m_PosNew);
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
SetPos(m_PosOld);
|
|
}
|
|
|
|
void MergeIntoPrevious(cMoveObject* prev)
|
|
{
|
|
// TODO: do something valid if prev unit != this unit
|
|
debug_assert(prev->msg->id == msg->id);
|
|
prev->m_PosNew = m_PosNew;
|
|
}
|
|
|
|
END_COMMAND(MoveObject)
|
|
|
|
|
|
BEGIN_COMMAND(RotateObject)
|
|
|
|
float m_AngleOld, m_AngleNew;
|
|
CMatrix3D m_TransformOld, m_TransformNew;
|
|
|
|
void Do()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
if (unit->GetEntity())
|
|
{
|
|
m_AngleOld = unit->GetEntity()->m_orientation.Y;
|
|
if (msg->usetarget)
|
|
{
|
|
CVector3D& pos = unit->GetEntity()->m_position;
|
|
CVector3D target;
|
|
msg->target->GetWorldSpace(target, pos.Y);
|
|
CVector2D dir(target.X-pos.X, target.Z-pos.Z);
|
|
m_AngleNew = atan2(dir.x, dir.y);
|
|
}
|
|
else
|
|
{
|
|
m_AngleNew = msg->angle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_TransformOld = unit->GetModel()->GetTransform();
|
|
|
|
CVector3D pos = unit->GetModel()->GetTransform().GetTranslation();
|
|
|
|
float s, c;
|
|
if (msg->usetarget)
|
|
{
|
|
CVector3D target;
|
|
msg->target->GetWorldSpace(target, pos.Y);
|
|
CVector2D dir(target.X-pos.X, target.Z-pos.Z);
|
|
dir = dir.normalize();
|
|
s = dir.x;
|
|
c = dir.y;
|
|
}
|
|
else
|
|
{
|
|
s = sinf(msg->angle);
|
|
c = cosf(msg->angle);
|
|
}
|
|
CMatrix3D& m = m_TransformNew;
|
|
m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = pos.X;
|
|
m._21 = 0.0f; m._22 = 1.0f; m._23 = 0.0f; m._24 = pos.Y;
|
|
m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = pos.Z;
|
|
m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f;
|
|
}
|
|
|
|
SetAngle(m_AngleNew, m_TransformNew);
|
|
}
|
|
|
|
void SetAngle(float angle, CMatrix3D& transform)
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
if (unit->GetEntity())
|
|
{
|
|
unit->GetEntity()->m_orientation.Y = angle;
|
|
}
|
|
else
|
|
{
|
|
unit->GetModel()->SetTransform(transform);
|
|
}
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
SetAngle(m_AngleNew, m_TransformNew);
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
SetAngle(m_AngleOld, m_TransformOld);
|
|
}
|
|
|
|
void MergeIntoPrevious(cRotateObject* prev)
|
|
{
|
|
// TODO: do something valid if prev unit != this unit
|
|
debug_assert(prev->msg->id == msg->id);
|
|
prev->m_AngleNew = m_AngleNew;
|
|
prev->m_TransformNew = m_TransformNew;
|
|
}
|
|
|
|
END_COMMAND(RotateObject)
|
|
|
|
|
|
BEGIN_COMMAND(DeleteObject)
|
|
|
|
CUnit* m_UnitInLimbo;
|
|
|
|
void Construct()
|
|
{
|
|
m_UnitInLimbo = NULL;
|
|
}
|
|
|
|
void Destruct()
|
|
{
|
|
if (m_UnitInLimbo)
|
|
{
|
|
if (m_UnitInLimbo->GetEntity())
|
|
m_UnitInLimbo->GetEntity()->kill();
|
|
else
|
|
delete m_UnitInLimbo;
|
|
}
|
|
}
|
|
|
|
void Do()
|
|
{
|
|
Redo();
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
CUnit* unit = g_UnitMan.FindByID(msg->id);
|
|
if (! unit) return;
|
|
|
|
if (unit->GetEntity())
|
|
{
|
|
// HACK: I don't know the proper way of undoably deleting entities...
|
|
unit->GetEntity()->m_destroyed = true;
|
|
}
|
|
|
|
g_UnitMan.RemoveUnit(unit);
|
|
m_UnitInLimbo = unit;
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
if (m_UnitInLimbo->GetEntity())
|
|
m_UnitInLimbo->GetEntity()->m_destroyed = false;
|
|
|
|
g_UnitMan.AddUnit(m_UnitInLimbo);
|
|
m_UnitInLimbo = NULL;
|
|
}
|
|
|
|
END_COMMAND(DeleteObject)
|
|
|
|
|
|
}
|