浏览代码

Merge pull request #5030 from vcmi/timed_events_objects_removal

Timed events objects removal
Ivan Savenko 11 月之前
父节点
当前提交
3115894307

+ 18 - 0
client/CPlayerInterface.cpp

@@ -403,6 +403,18 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 	localState->erasePath(hero);
 }
 
+void CPlayerInterface::townRemoved(const CGTownInstance* town)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+
+	if(town->tempOwner == playerID)
+	{
+		localState->removeOwnedTown(town);
+		adventureInt->onTownChanged(town);
+	}
+}
+
+
 void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -1424,6 +1436,12 @@ void CPlayerInterface::objectRemoved(const CGObjectInstance * obj, const PlayerC
 		const CGHeroInstance * h = static_cast<const CGHeroInstance *>(obj);
 		heroKilled(h);
 	}
+
+	if(obj->ID == Obj::TOWN && obj->tempOwner == playerID)
+	{
+		const CGTownInstance * t = static_cast<const CGTownInstance *>(obj);
+		townRemoved(t);
+	}
 	GH.fakeMouseMove();
 }
 

+ 1 - 0
client/CPlayerInterface.h

@@ -220,6 +220,7 @@ private:
 	};
 
 	void heroKilled(const CGHeroInstance* hero);
+	void townRemoved(const CGTownInstance* town);
 	void garrisonsChanged(std::vector<const CArmedInstance *> objs);
 	void requestReturningToMainMenu(bool won);
 	void acceptTurn(QueryID queryID, bool hotseatWait); //used during hot seat after your turn message is close

+ 3 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -378,6 +378,9 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
 
 std::string CGTownInstance::getObjectName() const
 {
+	if(ID == Obj::RANDOM_TOWN )
+		return CGObjectInstance::getObjectName();
+
 	return getNameTranslated() + ", " + getTown()->faction->getNameTranslated();
 }
 

+ 6 - 0
lib/mapObjects/FlaggableMapObject.cpp

@@ -51,6 +51,12 @@ void FlaggableMapObject::onHeroVisit( const CGHeroInstance * h ) const
 	giveBonusTo(h->getOwner());
 }
 
+void FlaggableMapObject::markAsDeleted() const
+{
+	if(getOwner().isValidPlayer())
+		takeBonusFrom(getOwner());
+}
+
 void FlaggableMapObject::initObj(vstd::RNG & rand)
 {
 	if(getOwner().isValidPlayer())

+ 1 - 0
lib/mapObjects/FlaggableMapObject.h

@@ -28,6 +28,7 @@ public:
 	using CGObjectInstance::CGObjectInstance;
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
+	void markAsDeleted() const;
 	void initObj(vstd::RNG & rand) override;
 
 	const IOwnableObject * asOwnable() const final;

+ 3 - 0
lib/mapping/CMap.cpp

@@ -103,6 +103,9 @@ void CMapEvent::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeInt("firstOccurrence", firstOccurrence);
 	handler.serializeInt("nextOccurrence", nextOccurrence);
 	resources.serializeJson(handler, "resources");
+
+	auto deletedObjects = handler.enterArray("deletedObjectsInstances");
+	deletedObjects.serializeArray(deletedObjectsInstances);
 }
 
 void CCastleEvent::serializeJson(JsonSerializeFormat & handler)

+ 7 - 0
lib/mapping/CMapDefines.h

@@ -12,6 +12,7 @@
 
 #include "../ResourceSet.h"
 #include "../texts/MetaString.h"
+#include "../int3.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -42,6 +43,8 @@ public:
 	ui32 firstOccurrence;
 	ui32 nextOccurrence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time
 
+	std::vector<ObjectInstanceID> deletedObjectsInstances;
+
 	template <typename Handler>
 	void serialize(Handler & h)
 	{
@@ -64,6 +67,10 @@ public:
 		h & computerAffected;
 		h & firstOccurrence;
 		h & nextOccurrence;
+		if(h.version >= Handler::Version::EVENT_OBJECTS_DELETION)
+		{
+			h & deletedObjectsInstances;
+		}
 	}
 	
 	virtual void serializeJson(JsonSerializeFormat & handler);

+ 13 - 4
lib/networkPacks/NetPacksLib.cpp

@@ -45,6 +45,7 @@
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "campaign/CampaignState.h"
 #include "IGameSettings.h"
+#include "mapObjects/FlaggableMapObject.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -1184,7 +1185,6 @@ void RemoveBonus::applyGs(CGameState *gs)
 
 void RemoveObject::applyGs(CGameState *gs)
 {
-
 	CGObjectInstance *obj = gs->getObjInstance(objectID);
 	logGlobal->debug("removing object id=%d; address=%x; name=%s", objectID, (intptr_t)obj, obj->getObjectName());
 	//unblock tiles
@@ -1197,10 +1197,7 @@ void RemoveObject::applyGs(CGameState *gs)
 	{
 		auto * beatenHero = dynamic_cast<CGHeroInstance *>(obj);
 		assert(beatenHero);
-		PlayerState * p = gs->getPlayerState(beatenHero->tempOwner);
 		gs->map->heroesOnMap -= beatenHero;
-		p->removeOwnedObject(beatenHero);
-
 
 		auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs);
 
@@ -1254,6 +1251,18 @@ void RemoveObject::applyGs(CGameState *gs)
 		}
 	}
 
+	if(obj->getOwner().isValidPlayer())
+	{
+		gs->getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten
+
+		FlaggableMapObject* flaggableObject = dynamic_cast<FlaggableMapObject*>(obj);
+		if(flaggableObject)
+		{
+			flaggableObject->markAsDeleted();
+		}
+	}
+
+
 	gs->map->instanceNames.erase(obj->instanceName);
 	gs->map->objects[objectID.getNum()].dellNull();
 	gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -69,6 +69,7 @@ enum class ESerializationVersion : int32_t
 	FOLDER_NAME_REWORK, // 870 - rework foldername
 	REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects
 	MARKET_TRANSLATION_FIX, // 872 - remove serialization of markets translateable strings
+	EVENT_OBJECTS_DELETION, //873 - allow events to remove map objects
 	
-	CURRENT = MARKET_TRANSLATION_FIX
+	CURRENT = EVENT_OBJECTS_DELETION
 };

+ 2 - 1
mapeditor/mapeditorroles.h

@@ -17,6 +17,7 @@ enum MapEditorRoles
 	PlayerIDRole,
 	BuildingIDRole,
 	SpellIDRole,
+	ObjectInstanceIDRole,
 	ArtifactIDRole,
-	ArtifactSlotRole
+	ArtifactSlotRole,
 };

+ 2 - 0
mapeditor/mapsettings/abstractsettings.h

@@ -14,6 +14,8 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
+Q_DECLARE_METATYPE(int3)
+
 //parses date for lose condition (1m 1w 1d)
 int expiredDate(const QString & date);
 QString expiredDate(int date);

+ 25 - 1
mapeditor/mapsettings/eventsettings.cpp

@@ -55,6 +55,28 @@ TResources resourcesFromVariant(const QVariant & v)
 	return TResources(vJson);
 }
 
+QVariant toVariant(std::vector<ObjectInstanceID> objects)
+{
+	QVariantList result;
+	for(auto obj : objects)
+	{
+		result.push_back(QVariant::fromValue(obj.num));
+	}
+	return result;
+}
+
+std::vector<ObjectInstanceID> deletedObjectsIdsFromVariant(const QVariant & v)
+{
+	std::vector<ObjectInstanceID> result;
+	for(auto idAsVariant : v.toList())
+	{
+		auto id = idAsVariant.value<int>();
+		result.push_back(ObjectInstanceID(id));
+	}
+
+	return result;
+}
+
 QVariant toVariant(const CMapEvent & event)
 {
 	QVariantMap result;
@@ -66,6 +88,7 @@ QVariant toVariant(const CMapEvent & event)
 	result["firstOccurrence"] = QVariant::fromValue(event.firstOccurrence);
 	result["nextOccurrence"] = QVariant::fromValue(event.nextOccurrence);
 	result["resources"] = toVariant(event.resources);
+	result["deletedObjectsInstances"] = toVariant(event.deletedObjectsInstances);
 	return QVariant(result);
 }
 
@@ -81,6 +104,7 @@ CMapEvent eventFromVariant(CMapHeader & mapHeader, const QVariant & variant)
 	result.firstOccurrence = v.value("firstOccurrence").toInt();
 	result.nextOccurrence = v.value("nextOccurrence").toInt();
 	result.resources = resourcesFromVariant(v.value("resources"));
+	result.deletedObjectsInstances = deletedObjectsIdsFromVariant(v.value("deletedObjectsInstances"));
 	return result;
 }
 
@@ -137,6 +161,6 @@ void EventSettings::on_timedEventRemove_clicked()
 
 void EventSettings::on_eventsList_itemActivated(QListWidgetItem *item)
 {
-	new TimedEvent(item, parentWidget());
+	new TimedEvent(*controller, item, parentWidget());
 }
 

+ 66 - 4
mapeditor/mapsettings/timedevent.cpp

@@ -11,13 +11,15 @@
 #include "timedevent.h"
 #include "ui_timedevent.h"
 #include "eventsettings.h"
+#include "../mapeditorroles.h"
 #include "../../lib/constants/EntityIdentifiers.h"
 #include "../../lib/constants/StringConstants.h"
 
-TimedEvent::TimedEvent(QListWidgetItem * t, QWidget *parent) :
+TimedEvent::TimedEvent(MapController & c, QListWidgetItem * t, QWidget *parent) : 
+	controller(c),
 	QDialog(parent),
-	target(t),
-	ui(new Ui::TimedEvent)
+	ui(new Ui::TimedEvent),
+	target(t)
 {
 	ui->setupUi(this);
 
@@ -51,7 +53,14 @@ TimedEvent::TimedEvent(QListWidgetItem * t, QWidget *parent) :
 		nval->setFlags(nval->flags() | Qt::ItemIsEditable);
 		ui->resources->setItem(i, 1, nval);
 	}
-
+	auto deletedObjectInstances = params.value("deletedObjectsInstances").toList();
+	for(auto const & idAsVariant : deletedObjectInstances)
+	{
+		auto id = ObjectInstanceID(idAsVariant.toInt());
+		auto obj = controller.map()->objects[id];
+		if(obj)
+			insertObjectToDelete(obj);
+	}
 	show();
 }
 
@@ -89,10 +98,63 @@ void TimedEvent::on_TimedEvent_finished(int result)
 	}
 	descriptor["resources"] = res;
 
+	QVariantList deletedObjects;
+	for(int i = 0; i < ui->deletedObjects->count(); ++i)
+	{
+		auto const & item = ui->deletedObjects->item(i);
+		auto data = item->data(MapEditorRoles::ObjectInstanceIDRole);
+		auto id = ObjectInstanceID(data.value<int>());
+		deletedObjects.push_back(QVariant::fromValue(id.num));
+	}
+	descriptor["deletedObjectsInstances"] = QVariant::fromValue(deletedObjects);
+
 	target->setData(Qt::UserRole, descriptor);
 	target->setText(ui->eventNameText->text());
 }
 
+void TimedEvent::on_addObjectToDelete_clicked()
+{
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller.scene(lvl)->objectPickerView;
+		l.highlight<const CGObjectInstance>();
+		l.update();
+		QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &TimedEvent::onObjectPicked);
+	}
+	hide();
+	dynamic_cast<QWidget *>(parent()->parent()->parent()->parent()->parent()->parent()->parent())->hide();
+}
+
+void TimedEvent::on_removeObjectToDelete_clicked()
+{
+	delete ui->deletedObjects->takeItem(ui->deletedObjects->currentRow());
+}
+
+void TimedEvent::onObjectPicked(const CGObjectInstance * obj)
+{
+	show();
+	dynamic_cast<QWidget *>(parent()->parent()->parent()->parent()->parent()->parent()->parent())->show();
+
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller.scene(lvl)->objectPickerView;
+		l.clear();
+		l.update();
+		QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &TimedEvent::onObjectPicked);
+	}
+
+	if(!obj) 
+		return;
+	insertObjectToDelete(obj);
+}
+
+void TimedEvent::insertObjectToDelete(const CGObjectInstance * obj)
+{
+	QString objectLabel = QString("%1, x: %2, y: %3, z: %4").arg(QString::fromStdString(obj->getObjectName())).arg(obj->pos.x).arg(obj->pos.y).arg(obj->pos.z);
+	auto * item = new QListWidgetItem(objectLabel);
+	item->setData(MapEditorRoles::ObjectInstanceIDRole, QVariant::fromValue(obj->id.num));
+	ui->deletedObjects->addItem(item);
+}
 
 void TimedEvent::on_pushButton_clicked()
 {

+ 10 - 3
mapeditor/mapsettings/timedevent.h

@@ -11,6 +11,8 @@
 
 #include <QDialog>
 
+#include "mapcontroller.h"
+
 namespace Ui {
 class TimedEvent;
 }
@@ -20,18 +22,23 @@ class TimedEvent : public QDialog
 	Q_OBJECT
 
 public:
-	explicit TimedEvent(QListWidgetItem *, QWidget *parent = nullptr);
+	explicit TimedEvent(MapController & map, QListWidgetItem *, QWidget * parent = nullptr);
 	~TimedEvent();
 
 private slots:
 
 	void on_TimedEvent_finished(int result);
 
+	void on_addObjectToDelete_clicked();
+	void on_removeObjectToDelete_clicked();
+	void onObjectPicked(const CGObjectInstance * obj);
+	void insertObjectToDelete(const CGObjectInstance * obj);
 	void on_pushButton_clicked();
 
-	void on_resources_itemDoubleClicked(QTableWidgetItem *item);
+	void on_resources_itemDoubleClicked(QTableWidgetItem * item);
 
 private:
-	Ui::TimedEvent *ui;
+	MapController & controller;
+	Ui::TimedEvent * ui;
 	QListWidgetItem * target;
 };

+ 30 - 2
mapeditor/mapsettings/timedevent.ui

@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>620</width>
-    <height>371</height>
+    <width>730</width>
+    <height>422</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -201,6 +201,34 @@
        </column>
       </widget>
      </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QLabel" name="deletedObjectsLabel">
+       <property name="text">
+        <string>Objects to delete</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="addObjectToDelete">
+       <property name="text">
+        <string>Add</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeObjectToDelete">
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QListWidget" name="deletedObjects"/>
+     </item>
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="text">

+ 1 - 1
server/CGameHandler.cpp

@@ -787,7 +787,7 @@ bool CGameHandler::removeObject(const CGObjectInstance * obj, const PlayerColor
 	ro.initiator = initiator;
 	sendAndApply(ro);
 
-	checkVictoryLossConditionsForAll(); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
+	checkVictoryLossConditionsForAll(); //e.g. if monster escaped (removing objs after battle is done directly by endBattle, not this function)
 	return true;
 }
 

+ 8 - 0
server/processors/NewTurnProcessor.cpp

@@ -61,6 +61,14 @@ void NewTurnProcessor::handleTimeEvents(PlayerColor color)
 				if (event.resources[i])
 					iw.components.emplace_back(ComponentType::RESOURCE, i, event.resources[i]);
 		}
+
+		//remove objects specified by event
+		for(const ObjectInstanceID objectIdToRemove : event.deletedObjectsInstances)
+		{
+			auto objectInstance = gameHandler->getObj(objectIdToRemove, false);
+			if(objectInstance != nullptr)
+				gameHandler->removeObject(objectInstance, PlayerColor::NEUTRAL);
+		}
 		gameHandler->sendAndApply(iw); //show dialog
 	}
 }