Browse Source

battle only basic implementation

Laserlicht 1 month ago
parent
commit
4d37fe631b

BIN
Mods/vcmi/Content/Sprites/lobby/battle-normal.png


BIN
Mods/vcmi/Content/Sprites/lobby/battle-pressed.png


+ 8 - 0
Mods/vcmi/Content/Sprites/lobby/battleButton.json

@@ -0,0 +1,8 @@
+{
+	"basepath" : "lobby/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "battle-normal.png"},
+		{ "frame" : 1, "file" : "battle-pressed.png"}
+	]
+}

+ 1 - 0
Mods/vcmi/Content/config/english.json

@@ -138,6 +138,7 @@
 	"vcmi.lobby.deleteFile" : "Do you want to delete following file?",
 	"vcmi.lobby.deleteFolder" : "Do you want to delete following folder?",
 	"vcmi.lobby.deleteMode" : "Switch to delete mode and back",
+	"vcmi.lobby.battleOnlyMode" : "Battle only mode",
 
 	"vcmi.broadcast.failedLoadGame" : "Failed to load game",
 	"vcmi.broadcast.command" : "Use '!help' to list available commands",

+ 1 - 0
Mods/vcmi/Content/config/german.json

@@ -138,6 +138,7 @@
 	"vcmi.lobby.deleteFile" : "Möchtet Ihr folgende Datei löschen?",
 	"vcmi.lobby.deleteFolder" : "Möchtet Ihr folgenden Ordner löschen?",
 	"vcmi.lobby.deleteMode" : "In den Löschmodus wechseln und zurück",
+	"vcmi.lobby.battleOnlyMode" : "Nur Kämpfen Modus",
 
 	"vcmi.broadcast.failedLoadGame" : "Spiel konnte nicht geladen werden",
 	"vcmi.broadcast.command" : "Benutze '!help' um alle verfügbaren Befehle aufzulisten",

+ 2 - 0
client/CMakeLists.txt

@@ -52,6 +52,7 @@ set(vcmiclientcommon_SRCS
 	gui/ShortcutHandler.cpp
 	gui/WindowHandler.cpp
 
+	lobby/BattleOnlyMode.cpp
 	lobby/CBonusSelection.cpp
 	lobby/CCampaignInfoScreen.cpp
 	lobby/CLobbyScreen.cpp
@@ -262,6 +263,7 @@ set(vcmiclientcommon_HEADERS
 	gui/TextAlignment.h
 	gui/WindowHandler.h
 
+	lobby/BattleOnlyMode.h
 	lobby/CBonusSelection.h
 	lobby/CCampaignInfoScreen.h
 	lobby/CLobbyScreen.h

+ 2 - 1
client/CPlayerInterface.cpp

@@ -97,6 +97,7 @@
 #include "../lib/mapObjects/MiscObjects.h"
 #include "../lib/mapObjects/ObjectTemplate.h"
 
+#include "../lib/mapping/CMap.h"
 #include "../lib/mapping/CMapHeader.h"
 
 #include "../lib/networkPacks/PacksForClient.h"
@@ -658,7 +659,7 @@ void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 
-	bool useQuickCombat = settings["adventure"]["quickCombat"].Bool();
+	bool useQuickCombat = settings["adventure"]["quickCombat"].Bool() || GAME->map().getMap()->battleOnly;
 	bool forceQuickCombat = settings["adventure"]["forceQuickCombat"].Bool();
 
 	if ((replayAllowed && useQuickCombat) || forceQuickCombat)

+ 8 - 1
client/NetPacksClient.cpp

@@ -15,6 +15,7 @@
 #include "windows/GUIClasses.h"
 #include "windows/CCastleInterface.h"
 #include "mapView/mapHandler.h"
+#include "mainmenu/CMainMenu.h"
 #include "adventureMap/AdventureMapInterface.h"
 #include "adventureMap/CInGameConsole.h"
 #include "battle/BattleInterface.h"
@@ -409,7 +410,13 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
 			adventureInt.reset();
 		}
 
-		GAME->server().showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory(), pack.statistic);
+		if(!pack.silentEnd)
+			GAME->server().showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory(), pack.statistic);
+		else
+		{
+			GAME->server().endGameplay();
+			GAME->mainmenu()->menu->switchToTab("main");
+		}
 	}
 
 	// In auto testing pack.mode we always close client if red pack.player won or lose

+ 124 - 0
client/lobby/BattleOnlyMode.cpp

@@ -0,0 +1,124 @@
+/*
+ * BattleOnlyMode.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "BattleOnlyMode.h"
+
+#include "../CServerHandler.h"
+#include "../GameEngine.h"
+#include "../GameInstance.h"
+
+#include "../gui/Shortcut.h"
+#include "../gui/WindowHandler.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/Images.h"
+
+#include "../../lib/gameState/CGameState.h"
+#include "../../lib/StartInfo.h"
+#include "../../lib/VCMIDirs.h"
+#include "../../lib/CRandomGenerator.h"
+#include "../../lib/callback/EditorCallback.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
+#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
+#include "../../lib/mapping/CMap.h"
+#include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/mapping/CMapEditManager.h"
+#include "../../lib/mapping/CMapService.h"
+#include "../../lib/mapping/MapFormat.h"
+#include "../../lib/filesystem/Filesystem.h"
+
+void BattleOnlyMode::openBattleWindow()
+{
+	ENGINE->windows().createAndPushWindow<BattleOnlyModeWindow>();
+}
+
+BattleOnlyModeWindow::BattleOnlyModeWindow()
+	: CWindowObject(BORDERED)
+{
+	OBJECT_CONSTRUCTION;
+
+	pos.w = 640;
+	pos.h = 480;
+
+	updateShadow();
+	center();
+
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
+	backgroundTexture->setPlayerColor(PlayerColor(1));
+	buttonOk = std::make_shared<CButton>(Point(183, 388), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ startBattle(); }, EShortcut::GLOBAL_ACCEPT);
+	buttonAbort = std::make_shared<CButton>(Point(250, 388), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ close(); }, EShortcut::GLOBAL_CANCEL);
+}
+
+void BattleOnlyModeWindow::startBattle()
+{
+	auto map = std::make_unique<CMap>(nullptr);
+	map->version = EMapFormat::VCMI;
+	map->creationDateTime = std::time(nullptr);
+	map->width = 4;
+	map->height = 3;
+	map->mapLevels = 1;
+	map->battleOnly = true;
+	
+	map->initTerrain();
+	map->getEditManager()->clearTerrain(&CRandomGenerator::getDefault());
+
+	//auto terrain = LIBRARY->terrainTypeHandler->objects;
+	map->getEditManager()->getTerrainSelection().selectAll();
+	map->getEditManager()->drawTerrain(TerrainId::GRASS, 0, &CRandomGenerator::getDefault());
+
+	map->players[0].canComputerPlay = true;
+	map->players[0].canHumanPlay = true;
+	map->players[1] = map->players[0];
+
+	auto cb = std::make_unique<EditorCallback>(map.get());
+
+	auto knownHeroes = LIBRARY->objtypeh->knownSubObjects(Obj::HERO);
+
+	auto addHero = [&](MapObjectSubID heroType, PlayerColor color, const int3 & position)
+	{
+		auto it = knownHeroes.find(heroType);
+		if (it == knownHeroes.end())
+			return; // unknown hero type, nothing to add
+
+		auto firstHeroType = *it;
+		auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::HERO, firstHeroType);
+		auto templates = factory->getTemplates();
+
+		auto obj = std::dynamic_pointer_cast<CGHeroInstance>(factory->create(cb.get(), templates.front()));
+		obj->setOwner(color);
+		obj->pos = position;
+		/*if(heroType == MapObjectSubID(1))
+		{*/
+			obj->pushPrimSkill(PrimarySkill::ATTACK, 100);
+			obj->getArmy()->setCreature(SlotID(0), CreatureID(1), 10000);
+		//}
+		map->getEditManager()->insertObject(obj);
+	};
+
+	addHero(MapObjectSubID(1), PlayerColor(0), int3(2, 1, 0));
+	addHero(MapObjectSubID(2), PlayerColor(1), int3(3, 1, 0));
+
+	auto path = VCMIDirs::get().userDataPath() / "Maps";
+	boost::filesystem::create_directories(path);
+	const std::string fileName = "BattleOnlyMode.vmap";
+	const auto fullPath = path / fileName;
+	CMapService mapService;
+	mapService.saveMap(map, fullPath);
+	CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount) { return true; });
+
+	auto mapInfo = std::make_shared<CMapInfo>();
+	mapInfo->mapInit("Maps/BattleOnlyMode");
+	GAME->server().setMapInfo(mapInfo);
+	ExtraOptionsInfo extraOptions;
+	extraOptions.unlimitedReplay = true;
+	GAME->server().setExtraOptionsInfo(extraOptions);
+	GAME->server().sendStartGame();
+}

+ 32 - 0
client/lobby/BattleOnlyMode.h

@@ -0,0 +1,32 @@
+/*
+ * BattleOnlyMode.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../windows/CWindowObject.h"
+
+class FilledTexturePlayerColored;
+class CButton;
+
+class BattleOnlyMode
+{
+public:
+	static void openBattleWindow();
+};
+class BattleOnlyModeWindow : public CWindowObject
+{
+private:
+	std::shared_ptr<FilledTexturePlayerColored> backgroundTexture;
+	std::shared_ptr<CButton> buttonOk;
+	std::shared_ptr<CButton> buttonAbort;
+
+	void startBattle();
+public:
+	BattleOnlyModeWindow();
+};

+ 11 - 0
client/lobby/SelectionTab.cpp

@@ -12,6 +12,7 @@
 #include "SelectionTab.h"
 #include "CSelectionBase.h"
 #include "CLobbyScreen.h"
+#include "BattleOnlyMode.h"
 
 #include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
@@ -241,6 +242,15 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 		sortByDate->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/selectionTabSortDate")));
 		buttonsSortBy.push_back(sortByDate);
 
+		bool isMultiplayer = GAME->server().loadMode == ELoadMode::MULTI;
+
+		if(tabType == ESelectionScreen::newGame && !isMultiplayer)
+		{
+			buttonBattleOnlyMode = std::make_shared<CButton>(Point(23, 18), AnimationPath::builtin("lobby/battleButton"), CButton::tooltip("", LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode")), [this, tabTitle, tabTitleDelete](){
+				BattleOnlyMode::openBattleWindow();
+			});
+		}
+
 		if(tabType == ESelectionScreen::loadGame || tabType == ESelectionScreen::newGame)
 		{
 			buttonDeleteMode = std::make_shared<CButton>(Point(367, 18), AnimationPath::builtin("lobby/deleteButton"), CButton::tooltip("", LIBRARY->generaltexth->translate("vcmi.lobby.deleteMode")), [this, tabTitle, tabTitleDelete](){
@@ -325,6 +335,7 @@ void SelectionTab::toggleMode()
 				inputName->disable();
 				auto files = getFiles("Maps/", EResType::MAP);
 				files.erase(ResourcePath("Maps/Tutorial.tut", EResType::MAP));
+				files.erase(ResourcePath("Maps/BattleOnlyMode.vmap", EResType::MAP));
 				parseMaps(files);
 				break;
 			}

+ 2 - 0
client/lobby/SelectionTab.h

@@ -128,6 +128,8 @@ private:
 	std::shared_ptr<CButton> buttonDeleteMode;
 	bool deleteMode;
 
+	std::shared_ptr<CButton> buttonBattleOnlyMode;
+
 	bool enableUiEnhancements;
 	std::shared_ptr<CButton> buttonCampaignSet;
 

+ 1 - 0
lib/mapping/CMapHeader.cpp

@@ -129,6 +129,7 @@ CMapHeader::CMapHeader()
 	, defeatIconIndex(0)
 	, howManyTeams(0)
 	, areAnyPlayers(false)
+	, battleOnly(false)
 {
 	setupEvents();
 	allowedHeroes = LIBRARY->heroh->getDefaultAllowed();

+ 4 - 0
lib/mapping/CMapHeader.h

@@ -272,6 +272,8 @@ public:
 
 	bool areAnyPlayers; /// Unused. True if there are any playable players on the map.
 
+	bool battleOnly; /// Battle only mode
+
 	/// "main quests" of the map that describe victory and loss conditions
 	std::vector<TriggeredEvent> triggeredEvents;
 	
@@ -316,6 +318,8 @@ public:
 
 		h & levelLimit;
 		h & areAnyPlayers;
+		if (h.version >= Handler::Version::BATTLE_ONLY)
+			h & battleOnly;
 		h & players;
 		h & howManyTeams;
 		h & allowedHeroes;

+ 4 - 4
lib/mapping/MapEditUtils.cpp

@@ -120,14 +120,14 @@ void CTerrainSelection::setSelection(const std::vector<int3> & vec)
 
 void CTerrainSelection::selectAll()
 {
-	selectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height));
-	selectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height));
+	for(int i = 0; i < getMap()->mapLevels; i++)
+		selectRange(MapRect(int3(0, 0, i), getMap()->width, getMap()->height));
 }
 
 void CTerrainSelection::clearSelection()
 {
-	deselectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height));
-	deselectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height));
+	for(int i = 0; i < getMap()->mapLevels; i++)
+		deselectRange(MapRect(int3(0, 0, i), getMap()->width, getMap()->height));
 }
 
 CObjectSelection::CObjectSelection(CMap * map) : CMapSelection(map)

+ 4 - 0
lib/mapping/MapFormatJson.cpp

@@ -870,6 +870,8 @@ void CMapLoaderJson::readHeader(const bool complete)
 	readTeams(handler);
 	//TODO: check mods
 
+	mapHeader->battleOnly = header["battleOnly"].Bool();
+
 	if(complete)
 		readOptions(handler);
 	
@@ -1223,6 +1225,8 @@ void CMapSaverJson::writeHeader()
 
 	writeTeams(handler);
 
+	header["battleOnly"].Bool() = mapHeader->battleOnly;
+
 	writeOptions(handler);
 
 	writeTranslations();

+ 2 - 2
lib/modding/IdentifierStorage.cpp

@@ -94,7 +94,7 @@ void CIdentifierStorage::requestIdentifier(const ObjectCallback & callback) cons
 	checkIdentifier(callback.type);
 	checkIdentifier(callback.name);
 
-	assert(!callback.localScope.empty());
+	//assert(!callback.localScope.empty());
 
 	if (state != ELoadingState::FINISHED) // enqueue request if loading is still in progress
 		scheduledRequests.push_back(callback);
@@ -126,7 +126,7 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameW
 
 CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function<void(si32)> & callback, bool optional, bool bypassDependenciesCheck, bool caseSensitive)
 {
-	assert(!scope.empty());
+	//assert(!scope.empty());
 
 	auto scopeAndFullName = vstd::splitStringToPair(fullName, ':');
 	auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.');

+ 2 - 0
lib/networkPacks/PacksForClient.h

@@ -524,6 +524,7 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
 	PlayerColor player;
 	EVictoryLossCheckResult victoryLossCheckResult;
 	StatisticDataSet statistic;
+	bool silentEnd = false;
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -532,6 +533,7 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
 		h & player;
 		h & victoryLossCheckResult;
 		h & statistic;
+		h & silentEnd;
 	}
 };
 

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -50,8 +50,9 @@ enum class ESerializationVersion : int32_t
 	BONUS_HIDDEN, // hidden bonus
 	MORE_MAP_LAYERS, // more map layers
 	CONFIGURABLE_RESOURCES, // configurable resources
+	BATTLE_ONLY, // battle only mode
 
-	CURRENT = CONFIGURABLE_RESOURCES,
+	CURRENT = BATTLE_ONLY,
 };
 
 static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

+ 10 - 0
server/CGameHandler.cpp

@@ -3478,6 +3478,16 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 
 	if(!p || p->status != EPlayerStatus::INGAME) return;
 
+	if(gameState().getMap().battleOnly)
+	{
+		PlayerEndsGame peg;
+		peg.player = player;
+		peg.silentEnd = true;
+		sendAndApply(peg);
+		gameServer().setState(EServerState::SHUTDOWN);
+		return;
+	}
+
 	auto victoryLossCheckResult = gameState().checkForVictoryAndLoss(player);
 
 	if (victoryLossCheckResult.victory() || victoryLossCheckResult.loss())

+ 10 - 0
server/processors/TurnOrderProcessor.cpp

@@ -19,6 +19,7 @@
 #include "../../lib/CPlayerState.h"
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/mapObjects/CGObjectInstance.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/pathfinder/CPathfinder.h"
 #include "../../lib/pathfinder/PathfinderOptions.h"
@@ -364,6 +365,15 @@ bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which)
 
 void TurnOrderProcessor::onGameStarted()
 {
+	if(gameHandler->gameInfo().getMapHeader()->battleOnly)
+	{
+		auto heroes = gameHandler->gameState().getMap().getObjects<CGHeroInstance>();
+		auto hero1 = heroes.at(0);
+		auto hero2 = heroes.at(1);
+		gameHandler->startBattle(hero1, hero2);
+		return;
+	}
+
 	if (actingPlayers.empty())
 		blockedContacts = computeContactStatus();