Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
42 KB
Referenced Files
None
Subscribers
None
diff --git a/src/Commands.cpp b/src/Commands.cpp
index 4ec9de3..7e1576d 100644
--- a/src/Commands.cpp
+++ b/src/Commands.cpp
@@ -1,1035 +1,1057 @@
/*
* Copyright (C) 2018 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
// An undo/redo system based on <http://www.codeproject.com/Articles/2500/A-Basic-Undo-Redo-Framework-For-C>.
// Originally written by squarecross <https://forum.freegamedev.net/viewtopic.php?f=48&t=5432>.
#include "Commands.h"
#include "GameObjects.h"
#include "Block.h"
#include "LevelEditor.h"
#include "Globals.h"
#include "Functions.h"
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <stdio.h>
#include "libs/tinyformat/tinyformat.h"
Command::~Command() {
}
//////////////////////////////MoveGameObjectCommand///////////////////////////////////
MoveGameObjectCommand::MoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, int x, int y, int w, int h)
: editor(levelEditor), objects(1, gameObject)
, resizeCommand(NULL)
{
SDL_Rect r = gameObject->getBox();
r.x = x;
r.y = y;
if (w >= 0) r.w = w;
if (h >= 0) r.h = h;
newPosition.push_back(r);
init();
}
MoveGameObjectCommand::MoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, int dx, int dy)
: editor(levelEditor), objects(gameObjects)
, resizeCommand(NULL)
{
for (auto obj : objects) {
SDL_Rect r = obj->getBox();
r.x += dx;
r.y += dy;
newPosition.push_back(r);
}
init();
}
void MoveGameObjectCommand::init() {
//Initialize old position.
for (auto obj : objects) {
SDL_Rect r = obj->getBox();
oldPosition.push_back(r);
}
// Create resize command if necessary.
resizeCommand = ResizeLevelCommand::createAndShiftIfNecessary(editor, newPosition);
}
MoveGameObjectCommand::~MoveGameObjectCommand(){
if (resizeCommand) delete resizeCommand;
}
void MoveGameObjectCommand::execute(){
// First resize the level if necessary.
if (resizeCommand) resizeCommand->execute();
// Set the obj at its new position.
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = newPosition[i];
GameObject *obj = objects[i];
obj->setBaseLocation(r.x, r.y);
obj->setBaseSize(r.w, r.h);
}
}
void MoveGameObjectCommand::unexecute() {
// First undo the resize if necessary.
if (resizeCommand) resizeCommand->unexecute();
// Set the obj at its old position.
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = oldPosition[i];
GameObject *obj = objects[i];
obj->setBaseLocation(r.x, r.y);
obj->setBaseSize(r.w, r.h);
}
}
ResizeLevelCommand::ResizeLevelCommand(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy)
: editor(levelEditor), newLevelWidth(newWidth), newLevelHeight(newHeight), diffx(diffx), diffy(diffy)
{
oldLevelWidth = LEVEL_WIDTH;
oldLevelHeight = LEVEL_HEIGHT;
}
ResizeLevelCommand::~ResizeLevelCommand() {
}
void ResizeLevelCommand::execute() {
resizeLevel(editor, newLevelWidth, newLevelHeight, diffx, diffy);
}
void ResizeLevelCommand::unexecute() {
resizeLevel(editor, oldLevelWidth, oldLevelHeight, -diffx, -diffy);
}
void ResizeLevelCommand::resizeLevel(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy) {
LEVEL_WIDTH = newWidth;
LEVEL_HEIGHT = newHeight;
if (diffx != 0 || diffy != 0) {
camera.x += diffx;
camera.y += diffy;
for (unsigned int o = 0; o < levelEditor->levelObjects.size(); o++){
SDL_Rect r = levelEditor->levelObjects[o]->getBox();
levelEditor->levelObjects[o]->setBaseLocation(r.x + diffx, r.y + diffy);
}
for (auto it = levelEditor->sceneryLayers.begin(); it != levelEditor->sceneryLayers.end(); ++it) {
for (auto o : it->second->objects){
SDL_Rect r = o->getBox();
o->setBaseLocation(r.x + diffx, r.y + diffy);
}
}
}
}
ResizeLevelCommand* ResizeLevelCommand::createAndShiftIfNecessary(LevelEditor* levelEditor, std::vector<SDL_Rect>& position) {
// Calculate new level size, shift, etc.
int newLevelWidth = LEVEL_WIDTH;
int newLevelHeight = LEVEL_HEIGHT;
int diffx = 0, diffy = 0;
for (int i = 0; i < (int)position.size(); i++) {
const SDL_Rect &r = position[i];
if (r.x + r.w > newLevelWidth) {
newLevelWidth = r.x + r.w;
}
if (r.y + r.h > newLevelHeight) {
newLevelHeight = r.y + r.h;
}
if (r.x + diffx < 0) diffx = -r.x;
if (r.y + diffy < 0) diffy = -r.y;
}
newLevelWidth += diffx;
newLevelHeight += diffy;
if (newLevelWidth != LEVEL_WIDTH || newLevelHeight != LEVEL_HEIGHT || diffx || diffy) {
if (diffx || diffy) {
for (int i = 0; i < (int)position.size(); i++) {
SDL_Rect &r = position[i];
r.x += diffx;
r.y += diffy;
}
}
return new ResizeLevelCommand(levelEditor, newLevelWidth, newLevelHeight, diffx, diffy);
} else {
return NULL;
}
}
std::string ResizeLevelCommand::describe() {
return _("Resize level");
}
//////////////////////////////AddRemoveGameObjectCommand///////////////////////////////////
AddRemoveGameObjectCommand::AddRemoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, bool isAdd_)
: editor(levelEditor), objects(1, gameObject), isAdd(isAdd_), ownObject(isAdd_)
, resizeCommand(NULL), removeStartCommand(NULL), oldTriggers(NULL)
{
init();
}
AddRemoveGameObjectCommand::AddRemoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, bool isAdd_)
: editor(levelEditor), objects(gameObjects), isAdd(isAdd_), ownObject(isAdd_)
, resizeCommand(NULL), removeStartCommand(NULL), oldTriggers(NULL)
{
init();
}
void AddRemoveGameObjectCommand::init() {
if (isAdd) {
theLayer = editor->selectedLayer;
// adjust the object position if necessary
std::vector<SDL_Rect> position;
for (auto obj : objects) {
position.push_back(obj->getBox());
}
resizeCommand = ResizeLevelCommand::createAndShiftIfNecessary(editor, position);
if (resizeCommand) {
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = position[i];
objects[i]->setBaseLocation(r.x, r.y);
}
}
// remove the existing player/shadow start if necessary
std::vector<bool> typesToBeRemoved((int)TYPE_MAX, false);
bool removeSomething = false;
for (auto obj : objects) {
if (obj->type == TYPE_START_PLAYER || obj->type == TYPE_START_SHADOW) {
typesToBeRemoved[obj->type] = true;
removeSomething = true;
}
}
if (removeSomething) {
std::vector<GameObject*> objectsToBeRemoved;
for (auto obj : editor->levelObjects) {
if (typesToBeRemoved[obj->type]) objectsToBeRemoved.push_back(obj);
}
if (!objectsToBeRemoved.empty()) {
removeStartCommand = new AddRemoveGameObjectCommand(editor, objectsToBeRemoved, false);
}
}
}
}
void AddRemoveGameObjectCommand::backupTriggers() {
if (oldTriggers == NULL) oldTriggers = new LevelEditor::Triggers(editor->triggers);
}
AddRemoveGameObjectCommand::~AddRemoveGameObjectCommand(){
//Remove the objects if we own them.
if (ownObject) {
for (auto obj : objects) {
delete obj;
}
}
//Delete internal commands.
if (resizeCommand) delete resizeCommand;
if (removeStartCommand) delete removeStartCommand;
//Delete old triggers
if (oldTriggers) delete oldTriggers;
}
void AddRemoveGameObjectCommand::addGameObject(){
// some sanity check
assert(ownObject);
// Remove old start position if necessary.
if (removeStartCommand) removeStartCommand->execute();
// Resize the level if necessary.
if (resizeCommand) resizeCommand->execute();
// Add each object to level
for (int index = 0; index < (int)objects.size(); index++) {
GameObject *obj = objects[index];
//Increase totalCollectables everytime we add a new collectable
if (obj->type == TYPE_COLLECTABLE) {
editor->totalCollectables++;
}
Block* block = dynamic_cast<Block*>(obj);
Scenery* scenery = dynamic_cast<Scenery*>(obj);
//Add it to the levelObjects.
if (theLayer.empty()) {
assert(block != NULL);
editor->levelObjects.push_back(block);
} else {
assert(scenery != NULL);
auto it = editor->sceneryLayers.find(theLayer);
assert(it != editor->sceneryLayers.end());
it->second->objects.push_back(scenery);
}
//GameObject type specific stuff.
switch (obj->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
case TYPE_PORTAL:
{
//If block doesn't have an id (new object).
if (block->getEditorProperty("id").empty()) {
//Give it it's own id.
char s[64];
sprintf(s, "%u", editor->currentId);
editor->currentId++;
block->setEditorProperty("id", s);
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Add the object to the moving blocks.
vector<MovingPosition> positions;
editor->movingBlocks[block] = positions;
//Get the moving position.
const vector<SDL_Rect> &movingPos = block->movingPos;
//Add the object to the movingBlocks vector.
editor->movingBlocks[block].clear();
for (int i = 0, m = movingPos.size(); i < m; i++) {
MovingPosition position(movingPos[i].x, movingPos[i].y, movingPos[i].w);
editor->movingBlocks[block].push_back(position);
}
//If block doesn't have an id.
if (block->getEditorProperty("id").empty()) {
//Give it it's own id.
char s[64];
sprintf(s, "%u", editor->currentId);
editor->currentId++;
block->setEditorProperty("id", s);
}
break;
}
default:
break;
}
}
// now we doesn't own the object anymore.
ownObject = false;
}
void AddRemoveGameObjectCommand::removeGameObject(){
// some sanity check
assert(!ownObject);
//Make sure we don't access the removed object through
//moving/linking.
editor->movingBlock = nullptr;
editor->linkingTrigger = nullptr;
editor->moving = false;
editor->linking = false;
// Remove objects
for (int index = 0; index < (int)objects.size(); index++) {
GameObject *obj = objects[index];
std::vector<GameObject*>::iterator it;
std::map<Block*, vector<GameObject*> >::iterator mapIt;
//Decrease totalCollectables everytime we remove a collectable
if (obj->type == TYPE_COLLECTABLE){
editor->totalCollectables--;
}
//Check if the object is in the selection.
it = find(editor->selection.begin(), editor->selection.end(), obj);
if (it != editor->selection.end()){
//It is so we delete it.
editor->selection.erase(it);
}
Block *theBlock = dynamic_cast<Block*>(obj);
Scenery *theScenery = dynamic_cast<Scenery*>(obj);
//Check if the object is in the triggers.
if (theBlock) {
mapIt = editor->triggers.find(theBlock);
if (mapIt != editor->triggers.end()){
//Back up triggers if it's remove mode.
if (!isAdd) backupTriggers();
//Remove the object from triggers.
editor->triggers.erase(mapIt);
}
}
//Boolean if it could be a target.
if (obj->type == TYPE_MOVING_BLOCK || obj->type == TYPE_MOVING_SHADOW_BLOCK || obj->type == TYPE_MOVING_SPIKES
|| obj->type == TYPE_CONVEYOR_BELT || obj->type == TYPE_SHADOW_CONVEYOR_BELT || obj->type == TYPE_PORTAL)
{
for (mapIt = editor->triggers.begin(); mapIt != editor->triggers.end(); ++mapIt){
//Now loop the target vector.
for (int o = mapIt->second.size() - 1; o >= 0; o--){
//Check if the obj is in the target vector.
if (mapIt->second[o] == obj){
//Back up triggers if it's remove mode.
if (!isAdd) backupTriggers();
//Remove the object from triggers.
mapIt->second.erase(mapIt->second.begin() + o);
}
}
}
}
//Check if the object is in the movingObjects.
if (theBlock) {
std::map<Block*, vector<MovingPosition> >::iterator movIt;
movIt = editor->movingBlocks.find(theBlock);
if (movIt != editor->movingBlocks.end()){
//It is so we remove it.
editor->movingBlocks.erase(movIt);
}
}
//Check if the block isn't being configured with a window one way or another.
for (;;) {
std::map<GUIObject*, GameObject*>::iterator confIt;
for (confIt = editor->objectWindows.begin(); confIt != editor->objectWindows.end(); ++confIt){
if (confIt->second == obj) break;
}
if (confIt == editor->objectWindows.end()) break;
editor->destroyWindow(confIt->first);
}
//Now we remove the object from the levelObjects and/or scenery.
if (theBlock){
std::vector<Block*>::iterator it;
it = find(editor->levelObjects.begin(), editor->levelObjects.end(), theBlock);
if (it != editor->levelObjects.end()){
editor->levelObjects.erase(it);
theLayer.clear(); // record the layer of this object
}
} else if (theScenery) {
for (auto it = editor->sceneryLayers.begin(); it != editor->sceneryLayers.end(); ++it){
auto it2 = find(it->second->objects.begin(), it->second->objects.end(), theScenery);
if (it2 != it->second->objects.end()) {
it->second->objects.erase(it2);
theLayer = it->first; // record the layer of this object
break;
}
}
}
}
// Resize the level if necessary.
if (resizeCommand) resizeCommand->unexecute();
// Restore old start position if necessary.
if (removeStartCommand) removeStartCommand->unexecute();
// now we own this object
ownObject = true;
//Set dirty of selection popup
editor->selectionDirty();
}
void AddRemoveGameObjectCommand::execute(){
if (isAdd) {
addGameObject();
} else {
removeGameObject();
}
}
void AddRemoveGameObjectCommand::unexecute(){
if (isAdd) {
removeGameObject();
} else {
addGameObject();
}
// Restore old triggers if necessary
if (oldTriggers) editor->triggers = *oldTriggers;
}
//////////////////////////////AddRemovePathCommand///////////////////////////////////
AddRemovePathCommand::AddRemovePathCommand(LevelEditor* levelEditor, Block* movingBlock, MovingPosition movingPosition, bool isAdd_)
:editor(levelEditor), target(movingBlock), movePos(movingPosition), isAdd(isAdd_)
{
if (!isAdd) {
if (target->movingPos.empty()) {
assert(!"movingBlock->movingPos is empty!");
} else {
const SDL_Rect& r = target->movingPos.back();
movePos.x = r.x;
movePos.y = r.y;
movePos.time = r.w;
}
}
}
AddRemovePathCommand::~AddRemovePathCommand(){
}
void AddRemovePathCommand::execute(){
if (isAdd) addPath();
else removePath();
}
void AddRemovePathCommand::unexecute(){
if (isAdd) removePath();
else addPath();
}
void AddRemovePathCommand::addPath() {
//Add movePos to the path.
editor->movingBlocks[target].push_back(movePos);
//Write the path to the moving block.
setEditorData();
}
void AddRemovePathCommand::removePath() {
//Remove the last point in the path.
editor->movingBlocks[target].pop_back();
//Write the path to the moving block.
setEditorData();
}
void AddRemovePathCommand::setEditorData() {
target->movingPos.clear();
for (const MovingPosition& p : editor->movingBlocks[target]) {
SDL_Rect r = { p.x, p.y, p.time };
target->movingPos.push_back(r);
}
}
//////////////////////////////RemovePathCommand///////////////////////////////////
RemovePathCommand::RemovePathCommand(LevelEditor* levelEditor, Block* targetBlock)
:editor(levelEditor), target(targetBlock){
}
RemovePathCommand::~RemovePathCommand(){
}
void RemovePathCommand::execute(){
//Set the number of movingPositions to 0.
target->setEditorProperty("MovingPosCount","0");
std::map<Block*,vector<MovingPosition> >::iterator it;
//Check if target is in movingBlocks.
it = editor->movingBlocks.find(target);
if(it!=editor->movingBlocks.end()){
//Store the movingPositions for unexecute.
movePositions = it->second;
//Clear all its movingPositions
it->second.clear();
}
}
void RemovePathCommand::unexecute(){
std::map<Block*,vector<MovingPosition> >::iterator it;
//Find target in movingBlocks
it = editor->movingBlocks.find(target);
if(it!=editor->movingBlocks.end()){
//Restore its movingPositions
it->second = movePositions;
//Write the path to the moving block.
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(editor->movingBlocks[target].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0; o<editor->movingBlocks[target].size(); o++) {
sprintf(s0+1,"%u",o);
sprintf(s,"%d",editor->movingBlocks[target][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",editor->movingBlocks[target][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",editor->movingBlocks[target][o].time);
s0[0]='t';
editorData[s0]=s;
}
target->setEditorData(editorData);
}
}
//////////////////////////////AddLinkCommand///////////////////////////////////
AddLinkCommand::AddLinkCommand(LevelEditor* levelEditor, Block* linkingTrigger, GameObject* clickedObject)
- :editor(levelEditor), target(linkingTrigger), clickedObj(clickedObject),oldPortalLink(NULL), destination(""), id(""){
+ :editor(levelEditor), target(linkingTrigger), clickedObj(clickedObject),oldPortalLink(NULL), destination(""), id(""), oldTrigger(NULL){
}
AddLinkCommand::~AddLinkCommand(){
}
void AddLinkCommand::execute(){
//Check if the target can handle multiple or only one link.
switch(target->type) {
case TYPE_PORTAL: {
//Store the last portal link.
if(!editor->triggers[target].empty()){
oldPortalLink = editor->triggers[target].back();
}
//Portals can only link to one so remove all existing links.
editor->triggers[target].clear();
editor->triggers[target].push_back(clickedObj);
break;
}
default: {
//The others can handle multiple links.
editor->triggers[target].push_back(clickedObj);
break;
}
}
//Check if it's a portal.
if(target->type==TYPE_PORTAL) {
//Store the previous destination.
destination = target->getEditorProperty("destination");
//Portals need to get the id of the other instead of give it's own id.
char s[64];
sprintf(s,"%d",atoi(clickedObj->getEditorProperty("id").c_str()));
target->setEditorProperty("destination",s);
} else{
//Store the previous id.
id = clickedObj->getEditorProperty("id");
+ if(!id.empty()) {
+ //Another block might already be set as trigger for the clickedObj.
+ for(auto block : editor->levelObjects) {
+ std::string otherId = block->getEditorProperty("id");
+ if(id.compare(otherId) == 0) {
+ //IDs match, so either one of the trigger itself or one of its (other) targets.
+ std::vector<GameObject*>::iterator it;
+ //Find the clickedObj in the block's triggers.
+ it = std::find(editor->triggers[block].begin(), editor->triggers[block].end(), clickedObj);
+ if(it != editor->triggers[block].end()){
+ oldTrigger = block;
+ editor->triggers[block].erase(it);
+ break;
+ }
+ }
+ }
+ }
//Give the clickedObject the same id as the trigger.
char s[64];
sprintf(s,"%d",atoi(target->getEditorProperty("id").c_str()));
clickedObj->setEditorProperty("id",s);
}
}
void AddLinkCommand::unexecute(){
//Check if the target can handle multiple or only one link.
switch(target->type) {
case TYPE_PORTAL: {
//Portals can only link to one so remove all existing links.
editor->triggers[target].clear();
//Put the previous portal link back.
if(oldPortalLink != NULL)
editor->triggers[target].push_back(oldPortalLink);
break;
}
default:{
std::vector<GameObject*>::iterator it;
//Find the clickedObj in the target's triggers.
it = std::find(editor->triggers[target].begin(), editor->triggers[target].end(), clickedObj);
if(it != editor->triggers[target].end()){
//Remove it.
editor->triggers[target].erase(it);
}
+
+ //Restore old trigger if applicable
+ if(oldTrigger != NULL){
+ editor->triggers[oldTrigger].push_back(clickedObj);
+ }
break;
}
}
//Check if it's a portal.
if(target->type==TYPE_PORTAL) {
//Restore previous destination.
target->setEditorProperty("destination",destination);
} else{
//Restore clickedObj's previous id.
clickedObj->setEditorProperty("id",id);
}
}
//////////////////////////////RemoveLinkCommand///////////////////////////////////
RemoveLinkCommand::RemoveLinkCommand(LevelEditor* levelEditor, Block* targetBlock)
: editor(levelEditor), target(targetBlock), destination(""), id(""){
}
RemoveLinkCommand::~RemoveLinkCommand(){
}
void RemoveLinkCommand::execute(){
std::map<Block*,vector<GameObject*> >::iterator it;
//Find target in triggers.
it = editor->triggers.find(target);
if(it!=editor->triggers.end()) {
//Copy the objects the target was linked to.
links = it->second;
//Remove the links.
it->second.clear();
}
//In case of a portal remove its destination field.
if(target->type==TYPE_PORTAL) {
//Copy its previous destination.
destination = target->getEditorProperty("destination");
//Erase its destination.
target->setEditorProperty("destination","");
} else{
//Copy its previous id.
id = target->getEditorProperty("id");
//Give the trigger a new id to prevent activating unlinked targets.
char s[64];
sprintf(s,"%u",editor->currentId);
editor->currentId++;
target->setEditorProperty("id",s);
}
}
void RemoveLinkCommand::unexecute(){
std::map<Block*,vector<GameObject*> >::iterator it;
//Find target in triggers.
it = editor->triggers.find(target);
if(it!=editor->triggers.end()) {
//Restore objects it was linked to.
it->second = links;
}
if(target->type== TYPE_PORTAL){
//Restore old destination.
target->setEditorProperty("destination", destination);
} else{
//Restore old id.
target->setEditorProperty("id", id);
}
}
SetEditorPropertyCommand::SetEditorPropertyCommand(LevelEditor* levelEditor, ImageManager& imageManager, SDL_Renderer& renderer, GameObject* targetBlock,
const std::string& propertyName, const std::string& propertyValue, const std::string& propertyDescription)
: editor(levelEditor), imageManager(imageManager), renderer(renderer)
, target(targetBlock), prop(propertyName), newValue(propertyValue), desc(propertyDescription)
{
oldValue = target->getEditorProperty(prop);
}
SetEditorPropertyCommand::~SetEditorPropertyCommand() {
}
void SetEditorPropertyCommand::execute() {
target->setEditorProperty(prop, newValue);
updateCustomScenery();
}
void SetEditorPropertyCommand::unexecute() {
target->setEditorProperty(prop, oldValue);
updateCustomScenery();
}
void SetEditorPropertyCommand::updateCustomScenery() {
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (target && prop == "customScenery") {
scenery->updateCustomScenery(imageManager, renderer);
}
}
SetLevelPropertyCommand::~SetLevelPropertyCommand() {
}
void SetLevelPropertyCommand::execute() {
setLevelProperty(newProperty);
}
void SetLevelPropertyCommand::unexecute() {
setLevelProperty(oldProperty);
}
std::string SetLevelPropertyCommand::describe() {
return _("Modify level property");
}
SetScriptCommand::SetScriptCommand(LevelEditor* levelEditor, Block* targetBlock, const std::map<int, std::string>& script, const std::string& id)
: editor(levelEditor), target(targetBlock), newScript(script), id(id)
{
if (target) {
oldScript = target->scripts;
oldId = target->id;
} else {
oldScript = editor->scripts;
}
}
SetScriptCommand::~SetScriptCommand() {
}
void SetScriptCommand::execute() {
setScript(newScript, id);
}
void SetScriptCommand::unexecute() {
setScript(oldScript, oldId);
}
void SetScriptCommand::setScript(const std::map<int, std::string>& script, const std::string& id) {
if (target) {
target->scripts = script;
//Set the new id for the target block.
//TODO: Check for trigger links etc...
target->id = id;
} else {
editor->scripts = script;
}
}
AddRemoveLayerCommand::AddRemoveLayerCommand(LevelEditor* levelEditor, const std::string& layerName, bool isAdd_)
: editor(levelEditor), layer(NULL), theLayer(layerName), isAdd(isAdd_), ownObject(isAdd_)
{
if (isAdd) {
layer = new SceneryLayer;
} else {
auto it = editor->sceneryLayers.find(theLayer);
if (it != editor->sceneryLayers.end()) {
layer = it->second;
}
}
}
void AddRemoveLayerCommand::execute() {
if (isAdd) {
addLayer();
} else {
removeLayer();
}
}
void AddRemoveLayerCommand::unexecute() {
if (isAdd) {
removeLayer();
} else {
addLayer();
}
}
void AddRemoveLayerCommand::addLayer() {
assert(ownObject && layer);
assert(editor->sceneryLayers.find(theLayer) == editor->sceneryLayers.end());
// add this layer
editor->sceneryLayers[theLayer] = layer;
// show and select the newly created layer
editor->layerVisibility[theLayer] = true;
editor->selectedLayer = theLayer;
// deselect all
editor->deselectAll();
// change the ownership
ownObject = false;
}
void AddRemoveLayerCommand::removeLayer() {
assert(!ownObject);
assert(editor->sceneryLayers.find(theLayer) != editor->sceneryLayers.end());
assert(editor->sceneryLayers.find(theLayer)->second == layer);
// deselect all
editor->deselectAll();
// select the Blocks layer
editor->selectedLayer.clear();
// remove this layer
editor->layerVisibility.erase(theLayer);
editor->sceneryLayers.erase(theLayer);
// change the ownership
ownObject = true;
}
AddRemoveLayerCommand::~AddRemoveLayerCommand() {
if (ownObject) {
delete layer;
}
}
std::string AddRemoveLayerCommand::describe() {
return tfm::format(isAdd ? _("Add scenery layer %s") : _("Delete scenery layer %s"), theLayer);
}
SetLayerPropertyCommand::SetLayerPropertyCommand(LevelEditor* levelEditor, const std::string& oldName, const LayerProperty& newProperty)
: editor(levelEditor), newProperty(newProperty)
{
auto it = editor->sceneryLayers.find(oldName);
assert(it != editor->sceneryLayers.end() && it->second);
oldProperty.name = oldName;
oldProperty.speedX = it->second->speedX;
oldProperty.speedY = it->second->speedY;
oldProperty.cameraX = it->second->cameraX;
oldProperty.cameraY = it->second->cameraY;
}
void SetLayerPropertyCommand::execute() {
setLayerProperty(oldProperty.name, newProperty);
}
void SetLayerPropertyCommand::unexecute() {
setLayerProperty(newProperty.name, oldProperty);
}
SetLayerPropertyCommand::~SetLayerPropertyCommand() {
}
std::string SetLayerPropertyCommand::describe() {
return tfm::format(_("Modify the property of scenery layer %s"), oldProperty.name);
}
void SetLayerPropertyCommand::setLayerProperty(const std::string& oldName, const LayerProperty& newProperty) {
SceneryLayer *tmp = NULL;
// Find the existing layer
{
auto it = editor->sceneryLayers.find(oldName);
assert(it != editor->sceneryLayers.end() && it->second);
tmp = it->second;
}
// Check if we need to rename scenery layer
if (oldName != newProperty.name) {
assert(editor->sceneryLayers.find(newProperty.name) == editor->sceneryLayers.end());
// remove the old layer
editor->sceneryLayers.erase(oldName);
// then save the temp variable to the new layer
editor->sceneryLayers[newProperty.name] = tmp;
// show and select the newly created layer
editor->layerVisibility[newProperty.name] = editor->layerVisibility[oldName];
editor->layerVisibility.erase(oldName);
editor->selectedLayer = newProperty.name;
}
// Now we update the properties of the layer
tmp->speedX = newProperty.speedX;
tmp->speedY = newProperty.speedY;
tmp->cameraX = newProperty.cameraX;
tmp->cameraY = newProperty.cameraY;
}
MoveToLayerCommand::MoveToLayerCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, const std::string& oldName, const std::string& newName)
: editor(levelEditor), oldName(oldName), newName(newName), createNewLayer(NULL)
{
if (editor->sceneryLayers.find(newName) == editor->sceneryLayers.end()) {
createNewLayer = new AddRemoveLayerCommand(levelEditor, newName, true);
}
for (auto obj : gameObjects) {
Scenery *scenery = dynamic_cast<Scenery*>(obj);
if (scenery) objects.push_back(scenery);
}
}
void MoveToLayerCommand::execute() {
if (createNewLayer) {
createNewLayer->execute();
}
removeGameObject();
addGameObject(newName);
// show and select the new layer
editor->layerVisibility[newName] = true;
editor->selectedLayer = newName;
// deselect all
editor->deselectAll();
}
void MoveToLayerCommand::unexecute() {
removeGameObject();
addGameObject(oldName);
if (createNewLayer) {
createNewLayer->unexecute();
}
// show and select the old layer
editor->layerVisibility[oldName] = true;
editor->selectedLayer = oldName;
// deselect all
editor->deselectAll();
}
MoveToLayerCommand::~MoveToLayerCommand() {
if (createNewLayer) {
delete createNewLayer;
}
}
std::string MoveToLayerCommand::describe() {
return tfm::format(_("Move %d object(s) from layer %s to layer %s"), objects.size(), oldName, newName);
}
void MoveToLayerCommand::removeGameObject() {
// Remove objects
for (int index = 0; index < (int)objects.size(); index++) {
Scenery *scenery = objects[index];
//Now we remove the object from scenery.
for (auto it = editor->sceneryLayers.begin(); it != editor->sceneryLayers.end(); ++it){
auto it2 = find(it->second->objects.begin(), it->second->objects.end(), scenery);
if (it2 != it->second->objects.end()) {
it->second->objects.erase(it2);
break;
}
}
}
}
void MoveToLayerCommand::addGameObject(const std::string& layer) {
// Add objects
const auto& target = editor->sceneryLayers[layer];
for (int index = 0; index < (int)objects.size(); index++) {
target->objects.push_back(objects[index]);
}
}
diff --git a/src/Commands.h b/src/Commands.h
index 67ccfbf..77e8027 100644
--- a/src/Commands.h
+++ b/src/Commands.h
@@ -1,493 +1,496 @@
/*
* Copyright (C) 2018 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
// An undo/redo system based on <http://www.codeproject.com/Articles/2500/A-Basic-Undo-Redo-Framework-For-C>.
// Originally written by squarecross <https://forum.freegamedev.net/viewtopic.php?f=48&t=5432>.
#ifndef COMMANDS_H
#define COMMANDS_H
#include "LevelEditor.h"
#include <vector>
#include <string>
#include <map>
class Block;
class GameObject;
class LevelEditor;
class MovingPosition;
// Abstract class for commands.
class Command {
public:
// Do the command.
virtual void execute() = 0;
// Undo the command.
virtual void unexecute() = 0;
// The short description to the command.
virtual std::string describe() = 0;
// Deconstructor for derived classes.
virtual ~Command();
};
//Class for resize level.
class ResizeLevelCommand : public Command {
private:
LevelEditor* editor;
//New level size.
int newLevelWidth, newLevelHeight;
//Differences used for map dimension changes to shift position of other objects.
int diffx, diffy;
//Old level size.
int oldLevelWidth, oldLevelHeight;
public:
ResizeLevelCommand(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy);
virtual ~ResizeLevelCommand();
virtual void execute();
virtual void unexecute();
static void resizeLevel(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy);
// create a ResizeLevelCommand and shift the input position if any of them is outside the map area
static ResizeLevelCommand* createAndShiftIfNecessary(LevelEditor* levelEditor, std::vector<SDL_Rect>& position);
virtual std::string describe();
};
//Class for moving game objects.
class MoveGameObjectCommand: public Command{
private:
LevelEditor* editor;
//Object being moved.
std::vector<GameObject*> objects;
//New position.
std::vector<SDL_Rect> newPosition;
//Old position.
std::vector<SDL_Rect> oldPosition;
//Internal resize command. Can be NULL.
ResizeLevelCommand* resizeCommand;
public:
// Move or resize one object.
MoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, int x, int y, int w, int h);
// Move a bunch of objects
MoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, int dx, int dy);
virtual ~MoveGameObjectCommand();
//Moves the object to the specified position.
virtual void execute();
//Moves object back to its previous position.
virtual void unexecute();
virtual std::string describe();
private:
// initialize new/old positions, etc. of objects
void init();
};
//Class for add/remove game objects.
class AddRemoveGameObjectCommand: public Command {
private:
LevelEditor* editor;
//Object being added or removed.
std::vector<GameObject*> objects;
//The layer of the object.
std::string theLayer;
//Internal resize command for object placement. Can be NULL.
ResizeLevelCommand* resizeCommand;
//Internal command used to remove player/shadow start if we are adding new start point. Can be NULL.
AddRemoveGameObjectCommand* removeStartCommand;
//The working mode.
bool isAdd;
//Boolean indicates if we own this object.
bool ownObject;
//A copy of the level editor's triggers. Can be NULL.
LevelEditor::Triggers *oldTriggers;
private:
//Adds the gameObject.
void addGameObject();
//Removes the gameObject.
void removeGameObject();
public:
AddRemoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, bool isAdd_);
AddRemoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, bool isAdd_);
virtual void execute();
virtual void unexecute();
virtual ~AddRemoveGameObjectCommand();
virtual std::string describe();
private:
// initialize new positions, etc. of objects
void init();
// Back up old triggers. It can be only called once.
void backupTriggers();
};
//Class that adds/removes a moving position to the target moving block.
class AddRemovePathCommand : public Command{
private:
LevelEditor* editor;
//Point on the path that is being added.
MovingPosition movePos;
//Block that the path is being added to.
Block* target;
//The working mode.
bool isAdd;
public:
AddRemovePathCommand(LevelEditor* levelEditor, Block* movingBlock, MovingPosition movingPosition, bool isAdd_);
virtual ~AddRemovePathCommand();
//Add the MovingPosition.
virtual void execute();
//Remove the MovingPosition.
virtual void unexecute();
virtual std::string describe();
private:
void addPath();
void removePath();
void setEditorData();
};
//Class that clears the moving position of the target moving block.
class RemovePathCommand : public Command{
private:
LevelEditor* editor;
//Block that the path is being removed from.
Block* target;
//The MovingPositions of the target to be restored when unexecuting.
std::vector<MovingPosition> movePositions;
public:
RemovePathCommand(LevelEditor* levelEditor, Block* targetBlock);
virtual ~RemovePathCommand();
//Remove the path.
virtual void execute();
//Re-add the path.
virtual void unexecute();
virtual std::string describe();
};
//Class that adds a link from the target object ot the selected object.
class AddLinkCommand : public Command{
private:
LevelEditor* editor;
//Object that was clicked upon when adding the link.
GameObject* clickedObj;
//Object that is linking to another.
Block* target;
//Previous link of the target block if it is a portal (portal has only 1 link).
GameObject* oldPortalLink;
//Previous destination of portal.
std::string destination;
//Previous id of target.
std::string id;
+
+ //Previous switch/button that was linked to target (can be NULL)
+ Block* oldTrigger;
public:
AddLinkCommand(LevelEditor* levelEditor, Block* linkingTrigger, GameObject* clickedObject);
virtual ~AddLinkCommand();
//Add the link, clicked object will get the same id as the target block.
virtual void execute();
//Remove the link.
virtual void unexecute();
virtual std::string describe();
};
//Class that clears the links from the target object.
class RemoveLinkCommand : public Command{
private:
LevelEditor* editor;
//Block whose links are being removed.
Block* target;
//Copy of the links being removed.
std::vector<GameObject*> links;
//Previous destination of portal.
std::string destination;
//Previous target id.
std::string id;
public:
RemoveLinkCommand(LevelEditor* levelEditor, Block* targetBlock);
virtual ~RemoveLinkCommand();
//Remove all the links, changes the id of the object.
virtual void execute();
//Add all the links, restores the object's previous id.
virtual void unexecute();
virtual std::string describe();
};
//Class that modifies an editor property of an object.
class SetEditorPropertyCommand : public Command {
private:
LevelEditor* editor;
ImageManager& imageManager;
SDL_Renderer& renderer;
//Object being modified.
GameObject* target;
//The property being modified.
std::string prop;
//The new value of the property.
std::string newValue;
//The old value of the property.
std::string oldValue;
//The description of the property.
std::string desc;
public:
SetEditorPropertyCommand(LevelEditor* levelEditor, ImageManager& imageManager, SDL_Renderer& renderer, GameObject* targetBlock,
const std::string& propertyName, const std::string& propertyValue, const std::string& propertyDescription);
virtual ~SetEditorPropertyCommand();
virtual void execute();
virtual void unexecute();
virtual std::string describe();
private:
void updateCustomScenery();
};
//Class that modifies the level settings.
class SetLevelPropertyCommand : public Command {
public:
struct LevelProperty {
std::string levelName;
std::string levelTheme;
std::string levelMusic;
int levelTime;
int levelRecordings;
};
private:
LevelEditor* editor;
LevelProperty oldProperty;
LevelProperty newProperty;
public:
SetLevelPropertyCommand(LevelEditor* levelEditor, const LevelProperty& levelProperty);
virtual ~SetLevelPropertyCommand();
virtual void execute();
virtual void unexecute();
virtual std::string describe();
private:
void setLevelProperty(const LevelProperty& levelProperty);
};
//Class that modifies the scripting for block or level
// NOTE: currently the scripting for scenery block is unsupported.
class SetScriptCommand : public Command {
private:
LevelEditor* editor;
//Object being modified. Can be NULL.
Block* target;
std::map<int, std::string> newScript;
std::map<int, std::string> oldScript;
//the new id for the target block.
std::string id;
std::string oldId;
public:
SetScriptCommand(LevelEditor* levelEditor, Block* targetBlock, const std::map<int, std::string>& script, const std::string& id = std::string());
virtual ~SetScriptCommand();
virtual void execute();
virtual void unexecute();
virtual std::string describe();
private:
void setScript(const std::map<int, std::string>& script, const std::string& id);
};
//Class for add/remove scenery layer.
class AddRemoveLayerCommand : public Command {
private:
LevelEditor* editor;
//Layer added or removed.
SceneryLayer* layer;
//The layer of the object.
std::string theLayer;
//The working mode.
bool isAdd;
//Boolean indicates if we own this object.
bool ownObject;
private:
//Adds the layer.
void addLayer();
//Removes the layer.
void removeLayer();
public:
AddRemoveLayerCommand(LevelEditor* levelEditor, const std::string& layerName, bool isAdd_);
virtual void execute();
virtual void unexecute();
virtual ~AddRemoveLayerCommand();
virtual std::string describe();
};
//Class for editing scenery layer properties (including rename scenery layer).
class SetLayerPropertyCommand : public Command {
public:
struct LayerProperty {
std::string name;
float speedX, speedY;
float cameraX, cameraY;
};
private:
LevelEditor* editor;
LayerProperty oldProperty;
LayerProperty newProperty;
public:
SetLayerPropertyCommand(LevelEditor* levelEditor, const std::string& oldName, const LayerProperty& newProperty);
virtual void execute();
virtual void unexecute();
virtual ~SetLayerPropertyCommand();
virtual std::string describe();
private:
void setLayerProperty(const std::string& oldName, const LayerProperty& newProperty);
};
//Class for move object between layers.
class MoveToLayerCommand : public Command {
private:
LevelEditor* editor;
std::vector<Scenery*> objects;
std::string oldName;
std::string newName;
AddRemoveLayerCommand* createNewLayer;
public:
MoveToLayerCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, const std::string& oldName, const std::string& newName);
virtual void execute();
virtual void unexecute();
virtual ~MoveToLayerCommand();
virtual std::string describe();
private:
void removeGameObject();
void addGameObject(const std::string& layer);
};
#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 7:16 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63385
Default Alt Text
(42 KB)

Event Timeline