Skip to content

Commit

Permalink
copy/paste entity text data (fixes #57)
Browse files Browse the repository at this point in the history
copying or cutting an entity will add its .ent data to the clipboard, which you can paste into a text editor or another bspguy window via Edit -> "Paste entities from clipboard".
  • Loading branch information
wootguy committed Apr 25, 2024
1 parent 3c2b092 commit 5906898
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/bsp/Entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "util.h"
#include <set>
#include <algorithm>
#include <sstream>

using namespace std;

Expand Down Expand Up @@ -493,3 +494,18 @@ bool Entity::isEverVisible() {

return true;
}

string Entity::serialize() {
stringstream ent_data;

ent_data << "{\n";

for (int k = 0; k < keyOrder.size(); k++) {
string key = keyOrder[k];
ent_data << "\"" << key << "\" \"" << keyvalues[key] << "\"\n";
}

ent_data << "}\n";

return ent_data.str();
}
2 changes: 2 additions & 0 deletions src/bsp/Entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ class Entity
int getMemoryUsage(); // aproximate

bool isEverVisible();

string serialize();
};

104 changes: 104 additions & 0 deletions src/editor/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "Entity.h"
#include "util.h"
#include "globals.h"
#include <sstream>

#include "icons/aaatrigger.h"

Expand Down Expand Up @@ -191,6 +192,109 @@ int CreateEntityCommand::memoryUsage() {
return sizeof(CreateEntityCommand) + entData->getMemoryUsage();
}

//
// Create Entities From Text
//
CreateEntityFromTextCommand::CreateEntityFromTextCommand(string desc, int mapIdx, string textData) : Command(desc, mapIdx) {
this->textData = textData;
this->allowedDuringLoad = true;
}

CreateEntityFromTextCommand::~CreateEntityFromTextCommand() {
}

void CreateEntityFromTextCommand::execute() {
Bsp* map = getBsp();

std::istringstream in(textData);

int lineNum = 0;
int lastBracket = -1;
Entity* ent = NULL;

vector<Entity*> ents;

string line = "";
while (std::getline(in, line))
{
lineNum++;
if (line.length() < 1 || line[0] == '\n')
continue;

if (line[0] == '{')
{
if (lastBracket == 0)
{
logf("clipboard ent text data (line %d): Unexpected '{'\n", lineNum);
continue;
}
lastBracket = 0;

if (ent != NULL)
delete ent;
ent = new Entity();
}
else if (line[0] == '}')
{
if (lastBracket == 1)
logf("clipboard ent text data (line %d): Unexpected '}'\n", lineNum);
lastBracket = 1;

if (ent == NULL)
continue;

if (ent->keyvalues.count("classname"))
ents.push_back(ent);
else
logf("Found unknown classname entity. Skip it.\n");
ent = NULL;

// you can end/start an ent on the same line, you know
if (line.find("{") != string::npos)
{
ent = new Entity();
lastBracket = 0;
}
}
else if (lastBracket == 0 && ent != NULL) // currently defining an entity
{
Keyvalue k(line);
if (k.key.length() && k.value.length())
ent->addKeyvalue(k);
}
}

for (Entity* ent : ents) {
map->ents.push_back(ent);
}
createdEnts = ents.size();
logf("Pasted %d entities from clipboard\n", createdEnts);

refresh();
}

void CreateEntityFromTextCommand::undo() {
Bsp* map = getBsp();

g_app->deselectObject();

for (int i = 0; i < createdEnts; i++) {
delete map->ents[map->ents.size() - 1];
map->ents.pop_back();
}

refresh();
}

void CreateEntityFromTextCommand::refresh() {
BspRenderer* renderer = getBspRenderer();
renderer->preRenderEnts();
g_app->gui->refresh();
}

int CreateEntityFromTextCommand::memoryUsage() {
return sizeof(CreateEntityFromTextCommand) + textData.size();
}

//
// Duplicate BSP Model command
Expand Down
15 changes: 15 additions & 0 deletions src/editor/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ class CreateEntityCommand : public Command {
};


class CreateEntityFromTextCommand : public Command {
public:
string textData;
int createdEnts;

CreateEntityFromTextCommand(string desc, int mapIdx, string textData);
~CreateEntityFromTextCommand();

void execute();
void undo();
void refresh();
int memoryUsage();
};


class DuplicateBspModelCommand : public Command {
public:
int oldModelIdx;
Expand Down
9 changes: 9 additions & 0 deletions src/editor/Gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,15 @@ void Gui::drawMenuBar() {
if (ImGui::MenuItem("Paste at original origin", 0, false, entSelected && app->copiedEnt != NULL)) {
app->pasteEnt(true);
}

const char* clipBoardText = ImGui::GetClipboardText();
if (ImGui::MenuItem("Paste entities from clipboard", 0, false, clipBoardText && clipBoardText[0] == '{')) {
app->pasteEntsFromText(clipBoardText);
}
tooltip(g, "Creates entities from text data. You can use this to transfer entities "
"from one bspguy window to another, or paste from .ent file text. Copy any entity "
"in the viewer then paste to a text editor to see the format of the text data.");

if (ImGui::MenuItem("Delete", "Del", false, nonWorldspawnEntSelected)) {
app->deleteEnt();
}
Expand Down
29 changes: 29 additions & 0 deletions src/editor/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2721,6 +2721,8 @@ void Renderer::cutEnt() {
DeleteEntityCommand* deleteCommand = new DeleteEntityCommand("Cut Entity", pickInfo);
deleteCommand->execute();
pushUndoCommand(deleteCommand);

ImGui::SetClipboardText(copiedEnt->serialize().c_str());
}

void Renderer::copyEnt() {
Expand All @@ -2733,6 +2735,8 @@ void Renderer::copyEnt() {
Bsp* map = mapRenderers[pickInfo.mapIdx]->map;
copiedEnt = new Entity();
*copiedEnt = *map->ents[pickInfo.entIdx];

ImGui::SetClipboardText(copiedEnt->serialize().c_str());
}

void Renderer::pasteEnt(bool noModifyOrigin) {
Expand Down Expand Up @@ -2770,6 +2774,31 @@ void Renderer::pasteEnt(bool noModifyOrigin) {
selectEnt(map, map->ents.size() - 1);
}

void Renderer::pasteEntsFromText(string text) {
Bsp* map = pickInfo.map;

CreateEntityFromTextCommand* createCommand =
new CreateEntityFromTextCommand("Paste entities from clipboard", pickInfo.mapIdx, text);
createCommand->execute();
pushUndoCommand(createCommand);

if (createCommand->createdEnts == 1) {
Entity* createdEnt = map->ents[map->ents.size()-1];
vec3 oldOrigin = getEntOrigin(map, createdEnt);
vec3 modelOffset = getEntOffset(map, createdEnt);
vec3 mapOffset = mapRenderers[pickInfo.mapIdx]->mapOffset;

vec3 moveDist = (cameraOrigin + cameraForward * 100) - oldOrigin;
vec3 newOri = (oldOrigin + moveDist) - (modelOffset + mapOffset);
vec3 rounded = gridSnappingEnabled ? snapToGrid(newOri) : newOri;
createdEnt->setOrAddKeyvalue("origin", rounded.toKeyvalueString(!gridSnappingEnabled));
createCommand->refresh();
}

pickInfo.valid = true;
selectEnt(map, map->ents.size() - 1);
}

void Renderer::deleteEnt() {
if (!pickInfo.valid || pickInfo.entIdx <= 0)
return;
Expand Down
2 changes: 2 additions & 0 deletions src/editor/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Renderer {
friend class EditEntityCommand;
friend class DeleteEntityCommand;
friend class CreateEntityCommand;
friend class CreateEntityFromTextCommand;
friend class DuplicateBspModelCommand;
friend class CreateBspModelCommand;
friend class EditBspModelCommand;
Expand Down Expand Up @@ -256,6 +257,7 @@ class Renderer {
void cutEnt();
void copyEnt();
void pasteEnt(bool noModifyOrigin);
void pasteEntsFromText(string text);
void deleteEnt();
void scaleSelectedObject(float x, float y, float z);
void scaleSelectedObject(vec3 dir, vec3 fromDir);
Expand Down

0 comments on commit 5906898

Please sign in to comment.