1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Merge remote-tracking branch 'vcmi/develop' into battleint_refactor

This commit is contained in:
Ivan Savenko 2022-12-01 23:58:01 +02:00
commit 495e0b3657
106 changed files with 2330 additions and 315 deletions

View File

@ -1140,7 +1140,7 @@ HeroPtr AIGateway::getHeroWithGrail() const
{
for(const CGHeroInstance * h : cb->getHeroesInfo())
{
if(h->hasArt(2)) //grail
if(h->hasArt(ArtifactID::GRAIL))
return h;
}
return nullptr;

View File

@ -1755,7 +1755,7 @@ HeroPtr VCAI::getHeroWithGrail() const
{
for(const CGHeroInstance * h : cb->getHeroesInfo())
{
if(h->hasArt(2)) //grail
if(h->hasArt(ArtifactID::GRAIL))
return h;
}
return nullptr;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

View File

@ -2,7 +2,7 @@
"name" : "VCMI essential files",
"description" : "Essential files required for VCMI to run correctly",
"version" : "0.99",
"version" : "1.0",
"author" : "VCMI Team",
"contact" : "http://forum.vcmi.eu/index.php",
"modType" : "Graphical",

View File

@ -201,7 +201,14 @@ int main(int argc, char * argv[])
("donotstartserver,d","do not attempt to start server and just connect to it instead server")
("serverport", po::value<si64>(), "override port specified in config file")
("saveprefix", po::value<std::string>(), "prefix for auto save files")
("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
("savefrequency", po::value<si64>(), "limit auto save creation to each N days")
("lobby", "parameters address, port, uuid to connect ro remote lobby session")
("lobby-address", po::value<std::string>(), "address to remote lobby")
("lobby-port", po::value<ui16>(), "port to remote lobby")
("lobby-host", "if this client hosts session")
("lobby-uuid", po::value<std::string>(), "uuid to the server")
("lobby-connections", po::value<ui16>(), "connections of server")
("uuid", po::value<std::string>(), "uuid for the client");
if(argc > 1)
{
@ -483,6 +490,28 @@ int main(int argc, char * argv[])
session["autoSkip"].Bool() = vm.count("autoSkip");
session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
session["aiSolo"].Bool() = false;
session["lobby"].Bool() = false;
if(vm.count("lobby"))
{
session["lobby"].Bool() = true;
session["host"].Bool() = false;
session["address"].String() = vm["lobby-address"].as<std::string>();
CSH->uuid = vm["uuid"].as<std::string>();
session["port"].Integer() = vm["lobby-port"].as<ui16>();
logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid);
if(vm.count("lobby-host"))
{
session["host"].Bool() = true;
session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as<ui16>());
session["hostUuid"].String() = vm["lobby-uuid"].as<std::string>();
logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String());
}
//we should not reconnect to previous game in online mode
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = false;
}
if(vm.count("testmap"))
{
@ -638,7 +667,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted";
VCMIDirs::get().userExtractedPath();
bfs::create_directories(outPath);
@ -671,7 +700,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted" / "configuration";
VCMIDirs::get().userExtractedPath() / "configuration";
bfs::create_directories(outPath);
@ -712,7 +741,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted" / "scripts";
VCMIDirs::get().userExtractedPath() / "scripts";
bfs::create_directories(outPath);
@ -735,7 +764,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted";
VCMIDirs::get().userExtractedPath();
auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
{
@ -851,7 +880,7 @@ void processCommand(const std::string &message)
readed >> URI;
std::unique_ptr<CAnimation> anim = make_unique<CAnimation>(URI);
anim->preload();
anim->exportBitmaps(VCMIDirs::get().userCachePath() / "extracted");
anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
}
else if(cn == "extract")
{
@ -860,7 +889,7 @@ void processCommand(const std::string &message)
if (CResourceHandler::get()->existsResource(ResourceID(URI)))
{
const bfs::path outPath = VCMIDirs::get().userCachePath() / "extracted" / URI;
const bfs::path outPath = VCMIDirs::get().userExtractedPath() / URI;
auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();

View File

@ -118,6 +118,9 @@ CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)
{
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
//read from file to restore last session
if(!settings["server"]["uuid"].isNull() && !settings["server"]["uuid"].String().empty())
uuid = settings["server"]["uuid"].String();
applier = std::make_shared<CApplier<CBaseForLobbyApply>>();
registerTypesLobbyPacks(*applier);
}
@ -190,9 +193,17 @@ void CServerHandler::startLocalServerAndConnect()
}
#elif defined(SINGLE_PROCESS_APP)
boost::condition_variable cond;
threadRunLocalServer = std::make_shared<boost::thread>([&cond, this] {
std::vector<std::string> args{"--uuid=" + uuid, "--port=" + boost::lexical_cast<std::string>(getHostPort())};
if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool())
{
args.push_back("--lobby=" + settings["session"]["address"].String());
args.push_back("--connections=" + settings["session"]["hostConnections"].String());
args.push_back("--lobby-port=" + boost::lexical_cast<std::string>(settings["session"]["port"].Integer()));
args.push_back("--lobby-uuid=" + settings["session"]["hostUuid"].String());
}
threadRunLocalServer = std::make_shared<boost::thread>([&cond, args, this] {
setThreadName("CVCMIServer");
CVCMIServer::create(&cond, uuid);
CVCMIServer::create(&cond, args);
onServerFinished();
});
threadRunLocalServer->detach();
@ -259,8 +270,8 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po
{
logNetwork->info("Establishing connection...");
c = std::make_shared<CConnection>(
addr.size() ? addr : settings["server"]["server"].String(),
port ? port : getDefaultPort(),
addr.size() ? addr : getHostAddress(),
port ? port : getHostPort(),
NAME, uuid);
}
catch(...)
@ -278,12 +289,12 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po
c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
if(!addr.empty() && addr != localhostAddress)
if(!addr.empty() && addr != getHostAddress())
{
Settings serverAddress = settings.write["server"]["server"];
serverAddress->String() = addr;
}
if(port && port != getDefaultPort())
if(port && port != getHostPort())
{
Settings serverPort = settings.write["server"]["port"];
serverPort->Integer() = port;
@ -358,10 +369,7 @@ bool CServerHandler::isGuest() const
ui16 CServerHandler::getDefaultPort()
{
if(settings["session"]["serverport"].Integer())
return static_cast<ui16>(settings["session"]["serverport"].Integer());
else
return static_cast<ui16>(settings["server"]["port"].Integer());
return static_cast<ui16>(settings["server"]["port"].Integer());
}
std::string CServerHandler::getDefaultPortStr()
@ -369,6 +377,28 @@ std::string CServerHandler::getDefaultPortStr()
return boost::lexical_cast<std::string>(getDefaultPort());
}
std::string CServerHandler::getHostAddress() const
{
if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool())
return settings["server"]["server"].String();
if(settings["session"]["host"].Bool())
return localhostAddress;
return settings["session"]["address"].String();
}
ui16 CServerHandler::getHostPort() const
{
if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool())
return getDefaultPort();
if(settings["session"]["host"].Bool())
return getDefaultPort();
return settings["session"]["port"].Integer();
}
void CServerHandler::sendClientConnecting() const
{
LobbyClientConnected lcc;
@ -813,9 +843,17 @@ void CServerHandler::threadRunServer()
setThreadName("CServerHandler::threadRunServer");
const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string();
std::string comm = VCMIDirs::get().serverPath().string()
+ " --port=" + getDefaultPortStr()
+ " --port=" + boost::lexical_cast<std::string>(getHostPort())
+ " --run-by-client"
+ " --uuid=" + uuid;
if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool())
{
comm += " --lobby=" + settings["session"]["address"].String();
comm += " --connections=" + settings["session"]["hostConnections"].String();
comm += " --lobby-port=" + boost::lexical_cast<std::string>(settings["session"]["port"].Integer());
comm += " --lobby-uuid=" + settings["session"]["hostUuid"].String();
}
if(shm)
{
comm += " --enable-shm";
@ -823,6 +861,7 @@ void CServerHandler::threadRunServer()
comm += " --enable-shm-uuid";
}
comm += " > \"" + logName + '\"';
logGlobal->info("Server command line: %s", comm);
#ifdef VCMI_WINDOWS
int result = -1;

View File

@ -111,6 +111,9 @@ public:
static const std::string localhostAddress;
CServerHandler();
std::string getHostAddress() const;
ui16 getHostPort() const;
void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
void startLocalServerAndConnect();

View File

@ -428,11 +428,13 @@ void Graphics::loadErmuToPicture()
assert (etp_idx == 44);
}
void Graphics::addImageListEntry(size_t index, const std::string & listName, const std::string & imageName)
void Graphics::addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName)
{
if (!imageName.empty())
{
JsonNode entry;
if (group != 0)
entry["group"].Integer() = group;
entry["frame"].Integer() = index;
entry["file"].String() = imageName;
@ -442,7 +444,7 @@ void Graphics::addImageListEntry(size_t index, const std::string & listName, con
void Graphics::addImageListEntries(const EntityService * service)
{
auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3);
auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3, _4);
auto loopCb = [&](const Entity * entity, bool & stop)
{

View File

@ -38,7 +38,7 @@ enum EFonts : int
/// Handles fonts, hero images, town images, various graphics
class Graphics
{
void addImageListEntry(size_t index, const std::string & listName, const std::string & imageName);
void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
void addImageListEntries(const EntityService * service);

View File

@ -231,7 +231,7 @@ void CBattleControlPanel::bSpellf()
if (blockingBonus->source == Bonus::ARTIFACT)
{
const int32_t artID = blockingBonus->sid;
const auto artID = ArtifactID(blockingBonus->sid);
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
//TODO check who *really* is source of bonus
std::string heroName = myHero->hasArt(artID) ? myHero->name : owner->enemyHero().name;

View File

@ -97,7 +97,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
}
queue->update();
//preparing siege info
const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
@ -188,6 +187,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
addUsedEvents(RCLICK | MOVE | KEYBOARD);
controlPanel->blockUI(true);
queue->update();
blockUI(true);
}
CBattleInterface::~CBattleInterface()

View File

@ -40,6 +40,8 @@ class CPlayerInterface;
class CClickableHex;
class CAnimation;
struct BattleEffect;
class IImage;
class CStackQueue;
class CBattleProjectileController;
class CBattleSiegeController;
@ -182,7 +184,7 @@ public:
friend class CPlayerInterface;
friend class CInGameConsole;
friend class CStackQueue;
friend class CBattleResultWindow;
friend class CBattleHero;
friend class CBattleStackAnimation;

View File

@ -694,7 +694,13 @@ void CStackQueue::update()
stackBoxes[boxIndex]->setUnit(nullptr);
}
CStackQueue::StackBox::StackBox(CStackQueue * owner)
int32_t CStackQueue::getSiegeShooterIconID()
{
return owner->siegeH->town->town->faction->index;
}
CStackQueue::StackBox::StackBox(CStackQueue * owner):
owner(owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
background = std::make_shared<CPicture>(owner->embedded ? "StackQueueSmall" : "StackQueueLarge");
@ -726,7 +732,16 @@ void CStackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
{
background->colorize(unit->unitOwner());
icon->visible = true;
icon->setFrame(unit->creatureIconIndex());
// temporary code for mod compatibility:
// first, set icon that should definitely exist (arrow tower icon in base vcmi mod)
// second, try to switch to icon that should be provided by mod
// if mod is not up to date and does have arrow tower icon yet - second setFrame call will fail and retain previously set image
// for 1.2 release & later next line should be moved into 'else' block
icon->setFrame(unit->creatureIconIndex(), 0);
if (unit->unitType()->idNumber == CreatureID::ARROW_TOWERS)
icon->setFrame(owner->getSiegeShooterIconID(), 1);
amount->setText(makeNumberShort(unit->getCount()));
if(stateIcon)

View File

@ -158,6 +158,7 @@ class CStackQueue : public CIntObject
{
class StackBox : public CIntObject
{
CStackQueue * owner;
public:
std::shared_ptr<CPicture> background;
std::shared_ptr<CAnimImage> icon;
@ -176,6 +177,7 @@ class CStackQueue : public CIntObject
std::shared_ptr<CAnimation> icons;
std::shared_ptr<CAnimation> stateIcons;
int32_t getSiegeShooterIconID();
public:
const bool embedded;

View File

@ -31,6 +31,8 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@ -50,7 +50,13 @@ int client_main(int argc, char * argv[])
id __block startGameObserver = [NSNotificationCenter.defaultCenter addObserverForName:@"StartGame" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[NSNotificationCenter.defaultCenter removeObserver:startGameObserver];
startGameObserver = nil;
startSDLManually(argc, argv);
NSArray<NSString *> * args = note.userInfo[@"args"];
const char * newArgv[args.count];
NSUInteger i = 0;
for (NSString * obj in args)
newArgv[i++] = obj.UTF8String;
startSDLManually(args.count, (char **)(newArgv));
}];
return qt_main_wrapper(argc, argv);
}

View File

@ -480,8 +480,8 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
inputAddress->giveFocus();
}
inputAddress->setText(host ? CServerHandler::localhostAddress : settings["server"]["server"].String(), true);
inputPort->setText(CServerHandler::getDefaultPortStr(), true);
inputAddress->setText(host ? CServerHandler::localhostAddress : CSH->getHostAddress(), true);
inputPort->setText(boost::lexical_cast<std::string>(CSH->getHostPort()), true);
buttonCancel = std::make_shared<CButton>(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), SDLK_ESCAPE);
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));

View File

@ -1038,12 +1038,10 @@ void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)
{
if(captureAllKeys)
{
captureAllKeys = false;
endEnteringText(false);
}
else if(SDLK_TAB == key.keysym.sym)
{
captureAllKeys = true;
startEnteringText();
}
break;
@ -1052,7 +1050,6 @@ void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)
{
if(enteredText.size() > 0 && captureAllKeys)
{
captureAllKeys = false;
endEnteringText(true);
CCS->soundh->playSound("CHAT");
}
@ -1133,6 +1130,8 @@ void CInGameConsole::startEnteringText()
if (statusBar)
{
captureAllKeys = true;
CSDL_Ext::startTextInput(&statusBar->pos);
enteredText = "_";
@ -1145,6 +1144,8 @@ void CInGameConsole::startEnteringText()
void CInGameConsole::endEnteringText(bool printEnteredText)
{
captureAllKeys = false;
CSDL_Ext::stopTextInput();
prevEntDisp = -1;

View File

@ -155,10 +155,11 @@ std::string CComponent::getDescription()
case creature: return "";
case artifact:
{
auto artID = ArtifactID(subtype);
std::unique_ptr<CArtifactInstance> art;
if (subtype != ArtifactID::SPELL_SCROLL)
if (artID != ArtifactID::SPELL_SCROLL)
{
art.reset(CArtifactInstance::createNewArtifactInstance(subtype));
art.reset(CArtifactInstance::createNewArtifactInstance(artID));
}
else
{

View File

@ -394,6 +394,20 @@ void CGStatusBar::init()
GH.statusbar = shared_from_this();
}
void CGStatusBar::clickLeft(tribool down, bool previousState)
{
if(!down && onClick)
{
onClick();
}
}
void CGStatusBar::setOnClick(std::function<void()> handler)
{
onClick = handler;
addUsedEvents(LCLICK);
}
Point CGStatusBar::getBorderSize()
{
//Width of borders where text should not be printed

View File

@ -124,6 +124,11 @@ class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusB
protected:
Point getBorderSize() override;
void clickLeft(tribool down, bool previousState) override;
private:
std::function<void()> onClick;
public:
template<typename ...Args>
static std::shared_ptr<CGStatusBar> create(Args... args)
@ -140,6 +145,7 @@ public:
void show(SDL_Surface * to) override; //shows statusbar (with current text)
void lock(bool shouldLock) override; //If true, current text cannot be changed until lock(false) is called
void setOnClick(std::function<void()> handler);
};
class CFocusable;

View File

@ -716,6 +716,11 @@ CAdvMapInt::CAdvMapInt():
worldViewUnderground->block(!CGI->mh->map->twoLevel);
addUsedEvents(MOVE);
statusbar->setOnClick([&]
{
if(LOCPLINT) LOCPLINT->cingconsole->startEnteringText();
});
}
CAdvMapInt::~CAdvMapInt()
@ -949,6 +954,7 @@ void CAdvMapInt::activate()
}
minimap.activate();
terrain.activate();
statusbar->activate();
GH.fakeMouseMove(); //to restore the cursor
}

View File

@ -935,7 +935,7 @@ void CCastleBuildings::enterMagesGuild()
void CCastleBuildings::enterTownHall()
{
if(town->visitingHero && town->visitingHero->hasArt(2) &&
if(town->visitingHero && town->visitingHero->hasArt(ArtifactID::GRAIL) &&
!vstd::contains(town->builtBuildings, BuildingID::GRAIL)) //hero has grail, but town does not have it
{
if(!vstd::contains(town->forbiddenBuildings, BuildingID::GRAIL))

View File

@ -123,6 +123,8 @@
},
"graphics" :
{
"iconSmall" : "vcmi/creatureIcons/towerSmall",
"iconLarge" : "vcmi/creatureIcons/towerLarge",
"animation": "CLCBOW.DEF" // needed to pass validation, never used
},
"sound": {}

View File

@ -203,6 +203,8 @@
"siege" :
{
"shooter" : "archer",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGCS",
"gate" :
{

View File

@ -210,6 +210,8 @@
"siege" :
{
"shooter" : "stormElemental",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGEL",
"gate" :
{

View File

@ -204,6 +204,8 @@
"siege" :
{
"shooter" : "medusa",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGDN",
"gate" :
{

View File

@ -209,6 +209,8 @@
"siege" :
{
"shooter" : "lizardman",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGFR",
"gate" :
{

View File

@ -204,6 +204,8 @@
"siege" :
{
"shooter" : "gog",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGIN",
"gate" :
{

View File

@ -214,6 +214,8 @@
"siege" :
{
"shooter" : "lich",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGNC",
"gate" :
{

View File

@ -211,6 +211,8 @@
"siege" :
{
"shooter" : "woodElf",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGRM",
"gate" :
{

View File

@ -203,6 +203,8 @@
{
"shooter" : "orc",
"imagePrefix" : "SGST",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"gate" :
{
"arch" : { "x" : 478, "y" : 235 },

View File

@ -202,6 +202,8 @@
"siege" :
{
"shooter" : "mage",
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
"imagePrefix" : "SGTW",
"gate" :
{

View File

@ -293,7 +293,7 @@
},
"names" : {
"type" : "array",
"default" : {},
"default" : [],
"items":
{
"type" : "string",
@ -380,7 +380,7 @@
"type" : "object",
"default": {},
"additionalProperties" : false,
"required" : [ "repositoryURL", "enableInstalledMods", "extraResolutionsModPath", "autoCheckRepositories", "updateOnStartup", "updateConfigUrl" ],
"required" : [ "repositoryURL", "enableInstalledMods", "extraResolutionsModPath", "autoCheckRepositories", "updateOnStartup", "updateConfigUrl", "lobbyUrl", "lobbyPort", "lobbyUsername", "connectionTimeout" ],
"properties" : {
"repositoryURL" : {
"type" : "array",
@ -410,6 +410,26 @@
"updateConfigUrl" : {
"type" : "string",
"default" : "https://raw.githubusercontent.com/vcmi/vcmi-updates/master/vcmi-updates.json"
},
"lobbyUrl" : {
"type" : "string",
"description" : "ip address or web link to remote proxy server",
"default" : "beholder.vcmi.eu"
},
"lobbyPort" : {
"type" : "number",
"description" : "connection port for remote proxy server",
"default" : 5002
},
"lobbyUsername" : {
"type" : "string",
"description" : "username for the client on the remote proxy server",
"default" : ""
},
"connectionTimeout" : {
"type" : "number",
"description" : "maximum time in ms, should be enough to establish socket connection to remote proxy server.",
"default" : 2000
}
}
}

View File

@ -6,6 +6,7 @@
"description" : "Format used to define town siege screen in VCMI",
"required" : [
"gate", "imagePrefix", "moat", "shooter",
"towerIconLarge", "towerIconSmall",
"static", "towers", "walls"
],
@ -80,6 +81,16 @@
"type":"string",
"description" : "Identifier of creature that will be used as tower shooter"
},
"towerIconSmall": {
"type":"string",
"description": "Small icon for tower, used in battle queue",
"format" : "imageFile"
},
"towerIconLarge": {
"type":"string",
"description": "Large icon for tower, used in battle queue",
"format" : "imageFile"
},
"static": {
"type":"object",
"additionalProperties" : false,

View File

@ -17,7 +17,7 @@ class IBonusBearer;
class DLL_LINKAGE Entity
{
public:
using IconRegistar = std::function<void(int32_t index, const std::string & listName, const std::string & imageName)>;
using IconRegistar = std::function<void(int32_t index, int32_t group, const std::string & listName, const std::string & imageName)>;
virtual ~Entity() = default;

View File

@ -33,6 +33,9 @@ set(launcher_SRCS
launcherdirs.cpp
jsonutils.cpp
updatedialog_moc.cpp
lobby/lobby.cpp
lobby/lobby_moc.cpp
lobby/lobbyroomrequest_moc.cpp
)
set(launcher_HEADERS
@ -43,6 +46,9 @@ set(launcher_HEADERS
launcherdirs.h
jsonutils.h
updatedialog_moc.h
lobby/lobby.h
lobby/lobby_moc.h
lobby/lobbyroomrequest_moc.h
main.h
)
@ -52,6 +58,8 @@ set(launcher_FORMS
settingsView/csettingsview_moc.ui
mainwindow_moc.ui
updatedialog_moc.ui
lobby/lobby_moc.ui
lobby/lobbyroomrequest_moc.ui
)
if(APPLE_IOS)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

View File

@ -26,5 +26,9 @@ void launchGame(int argc, char * argv[]) {
qtNativeWindow.windowScene = nil;
#endif
}
[NSNotificationCenter.defaultCenter postNotificationName:@"StartGame" object:nil];
}
__auto_type args = [NSMutableArray arrayWithCapacity:argc];
for (int i = 0; i < argc; ++i)
[args addObject:@(argv[i])];
[NSNotificationCenter.defaultCenter postNotificationName:@"StartGame" object:nil userInfo:@{@"args": args}];
}

123
launcher/lobby/lobby.cpp Normal file
View File

@ -0,0 +1,123 @@
/*
* lobby.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 "lobby.h"
#include "../lib/GameConstants.h"
SocketLobby::SocketLobby(QObject *parent) :
QObject(parent)
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(connected()), this, SLOT(connected()));
connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWritten(qint64)));
}
void SocketLobby::connectServer(const QString & host, int port, const QString & usr, int timeout)
{
username = usr;
socket->connectToHost(host, port);
if(!socket->waitForDisconnected(timeout) && !isConnected)
{
emit text("Error: " + socket->errorString());
emit disconnect();
}
}
void SocketLobby::disconnectServer()
{
socket->disconnectFromHost();
}
void SocketLobby::requestNewSession(const QString & session, int totalPlayers, const QString & pswd, const QMap<QString, QString> & mods)
{
const QString sessionMessage = ProtocolStrings[CREATE].arg(session, pswd, QString::number(totalPlayers), prepareModsClientString(mods));
send(sessionMessage);
}
void SocketLobby::requestJoinSession(const QString & session, const QString & pswd, const QMap<QString, QString> & mods)
{
const QString sessionMessage = ProtocolStrings[JOIN].arg(session, pswd, prepareModsClientString(mods));
send(sessionMessage);
}
void SocketLobby::requestLeaveSession(const QString & session)
{
const QString sessionMessage = ProtocolStrings[LEAVE].arg(session);
send(sessionMessage);
}
void SocketLobby::requestReadySession(const QString & session)
{
const QString sessionMessage = ProtocolStrings[READY].arg(session);
send(sessionMessage);
}
void SocketLobby::send(const QString & msg)
{
QByteArray str = msg.toUtf8();
int sz = str.size();
QByteArray pack((const char *)&sz, sizeof(sz));
pack.append(str);
socket->write(pack);
}
void SocketLobby::connected()
{
isConnected = true;
emit text("Connected!");
QByteArray greetingBytes;
greetingBytes.append(ProtocolVersion);
greetingBytes.append(ProtocolEncoding.size());
const QString greetingConst = QString(greetingBytes)
+ ProtocolStrings[GREETING].arg(QString::fromStdString(ProtocolEncoding),
username,
QString::fromStdString(GameConstants::VCMI_VERSION));
send(greetingConst);
}
void SocketLobby::disconnected()
{
isConnected = false;
emit disconnect();
emit text("Disconnected!");
}
void SocketLobby::bytesWritten(qint64 bytes)
{
qDebug() << "We wrote: " << bytes;
}
void SocketLobby::readyRead()
{
qDebug() << "Reading...";
emit receive(socket->readAll());
}
ServerCommand::ServerCommand(ProtocolConsts cmd, const QStringList & args):
command(cmd),
arguments(args)
{
}
QString prepareModsClientString(const QMap<QString, QString> & mods)
{
QStringList result;
for(auto & mod : mods.keys())
{
result << mod + "&" + mods[mod];
}
return result.join(";");
}

188
launcher/lobby/lobby.h Normal file
View File

@ -0,0 +1,188 @@
/*
* lobby.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 <QTcpSocket>
#include <QAbstractSocket>
const unsigned int ProtocolVersion = 3;
const std::string ProtocolEncoding = "utf8";
class ProtocolError: public std::runtime_error
{
public:
ProtocolError(const char * w): std::runtime_error(w) {}
};
enum ProtocolConsts
{
//client consts
GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, KICK, READY, FORCESTART,
//server consts
SESSIONS, CREATED, JOINED, KICKED, SRVERROR, CHAT, START, STATUS, HOST, MODS, CLIENTMODS
};
const QMap<ProtocolConsts, QString> ProtocolStrings
{
//=== client commands ===
//handshaking with server
//%1: first byte is protocol_version, then size of encoding string in bytes, then encoding string
//%2: client name
//%3: VCMI version
{GREETING, "%1<GREETINGS>%2<VER>%3"},
//[unsupported] autorization with username
//%1: username
{USERNAME, "<USER>%1"},
//sending message to the chat
//%1: message text
{MESSAGE, "<MSG>%1"},
//create new room
//%1: room name
//%2: password for the room
//%3: max number of players
//%4: mods used by host
// each mod has a format modname&modversion, mods should be separated by ; symbol
{CREATE, "<NEW>%1<PSWD>%2<COUNT>%3<MODS>%4"},
//join to the room
//%1: room name
//%2: password for the room
//%3: list of mods used by player
// each mod has a format modname&modversion, mods should be separated by ; symbol
{JOIN, "<JOIN>%1<PSWD>%2<MODS>%3"},
//leave the room
//%1: room name
{LEAVE, "<LEAVE>%1"},
//kick user from the current room
//%1: player username
{KICK, "<KICK>%1"},
//signal that player is ready for game
//%1: room name
{READY, "<READY>%1"},
//[unsupported] start session immediately
//%1: room name
{FORCESTART, "<FORCESTART>%1"},
//=== server commands ===
//server commands are started from :>>, arguments are enumerated by : symbol
//new session was created
//arg[0]: room name
{CREATED, "CREATED"},
//list of existing sessions
//arg[0]: amount of sessions, following arguments depending on it
//arg[x]: session name
//arg[x+1]: amount of players in the session
//arg[x+2]: total amount of players allowed
//arg[x+3]: True if session is protected by password
{SESSIONS, "SESSIONS"},
//user has joined to the session
//arg[0]: session name
//arg[1]: username (who was joined)
{JOINED, "JOIN"},
//user has left the session
//arg[0]: session name
//arg[1]: username (who has left)
{KICKED, "KICK"},
//session has been started
//arg[0]: session name
//arg[1]: uuid to be used for connection
{START, "START"},
//host ownership for the game session
//arg[0]: uuid to be used by vcmiserver
//arg[1]: amount of players (clients) to be connected
{HOST, "HOST"},
//room status
//arg[0]: amount of players, following arguments depending on it
//arg[x]: player username
//arg[x+1]: True if player is ready
{STATUS, "STATUS"}, //joined_players:player_name:is_ready
//server error
//arg[0]: error message
{SRVERROR, "ERROR"},
//mods used in the session by host player
//arg[0]: amount of mods, following arguments depending on it
//arg[x]: mod name
//arg[x+1]: mod version
{MODS, "MODS"},
//mods used by user
//arg[0]: username
//arg[1]: amount of mods, following arguments depending on it
//arg[x]: mod name
//arg[x+1]: mod version
{CLIENTMODS, "MODSOTHER"},
//received chat message
//arg[0]: sender username
//arg[1]: message text
{CHAT, "MSG"}
};
class ServerCommand
{
public:
ServerCommand(ProtocolConsts, const QStringList & arguments);
const ProtocolConsts command;
const QStringList arguments;
};
class SocketLobby : public QObject
{
Q_OBJECT
public:
explicit SocketLobby(QObject *parent = 0);
void connectServer(const QString & host, int port, const QString & username, int timeout);
void disconnectServer();
void requestNewSession(const QString & session, int totalPlayers, const QString & pswd, const QMap<QString, QString> & mods);
void requestJoinSession(const QString & session, const QString & pswd, const QMap<QString, QString> & mods);
void requestLeaveSession(const QString & session);
void requestReadySession(const QString & session);
void send(const QString &);
signals:
void text(QString);
void receive(QString);
void disconnect();
public slots:
void connected();
void disconnected();
void bytesWritten(qint64 bytes);
void readyRead();
private:
QTcpSocket *socket;
bool isConnected = false;
QString username;
};
QString prepareModsClientString(const QMap<QString, QString> & mods);

View File

@ -0,0 +1,417 @@
/*
* lobby_moc.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 "main.h"
#include "lobby_moc.h"
#include "ui_lobby_moc.h"
#include "lobbyroomrequest_moc.h"
#include "../mainwindow_moc.h"
#include "../modManager/cmodlist.h"
#include "../../lib/CConfigHandler.h"
Lobby::Lobby(QWidget *parent) :
QWidget(parent),
ui(new Ui::Lobby)
{
ui->setupUi(this);
connect(&socketLobby, SIGNAL(text(QString)), this, SLOT(sysMessage(QString)));
connect(&socketLobby, SIGNAL(receive(QString)), this, SLOT(dispatchMessage(QString)));
connect(&socketLobby, SIGNAL(disconnect()), this, SLOT(onDisconnected()));
QString hostString("%1:%2");
hostString = hostString.arg(QString::fromStdString(settings["launcher"]["lobbyUrl"].String()));
hostString = hostString.arg(settings["launcher"]["lobbyPort"].Integer());
ui->serverEdit->setText(hostString);
ui->userEdit->setText(QString::fromStdString(settings["launcher"]["lobbyUsername"].String()));
ui->kickButton->setVisible(false);
}
Lobby::~Lobby()
{
delete ui;
}
QMap<QString, QString> Lobby::buildModsMap() const
{
QMap<QString, QString> result;
QObject * mainWindow = qApp->activeWindow();
if(!mainWindow)
mainWindow = parent();
if(!mainWindow)
return result; //probably something is really wrong here
while(mainWindow->parent())
mainWindow = mainWindow->parent();
const auto & modlist = qobject_cast<MainWindow*>(mainWindow)->getModList();
for(auto & modname : modlist.getModList())
{
auto mod = modlist.getMod(modname);
if(mod.isEnabled())
{
result[modname] = mod.getValue("version").toString();
}
}
return result;
}
bool Lobby::isModAvailable(const QString & modName, const QString & modVersion) const
{
QObject * mainWindow = qApp->activeWindow();
while(mainWindow->parent())
mainWindow = mainWindow->parent();
const auto & modlist = qobject_cast<MainWindow*>(mainWindow)->getModList();
if(!modlist.hasMod(modName))
return false;
auto mod = modlist.getMod(modName);
return (mod.isInstalled () || mod.isAvailable()) && (mod.getValue("version") == modVersion);
}
void Lobby::serverCommand(const ServerCommand & command) try
{
//initialize variables outside of switch block
const QString statusPlaceholder("%1 %2\n");
const auto & args = command.arguments;
int amount, tagPoint;
QString joinStr;
switch(command.command)
{
case SRVERROR:
protocolAssert(args.size());
chatMessage("System error", args[0], true);
if(authentificationStatus == AuthStatus::AUTH_NONE)
authentificationStatus = AuthStatus::AUTH_ERROR;
break;
case CREATED:
protocolAssert(args.size());
hostSession = args[0];
session = args[0];
sysMessage("new session started");
break;
case SESSIONS:
protocolAssert(args.size());
amount = args[0].toInt();
protocolAssert(amount * 4 == (args.size() - 1));
ui->sessionsTable->setRowCount(amount);
tagPoint = 1;
for(int i = 0; i < amount; ++i)
{
QTableWidgetItem * sessionNameItem = new QTableWidgetItem(args[tagPoint++]);
ui->sessionsTable->setItem(i, 0, sessionNameItem);
int playersJoined = args[tagPoint++].toInt();
int playersTotal = args[tagPoint++].toInt();
QTableWidgetItem * sessionPlayerItem = new QTableWidgetItem(QString("%1/%2").arg(playersJoined).arg(playersTotal));
ui->sessionsTable->setItem(i, 1, sessionPlayerItem);
QTableWidgetItem * sessionProtectedItem = new QTableWidgetItem();
bool isPrivate = (args[tagPoint++] == "True");
sessionProtectedItem->setData(Qt::UserRole, isPrivate);
if(isPrivate)
sessionProtectedItem->setIcon(QIcon("icons:room-private.png"));
ui->sessionsTable->setItem(i, 2, sessionProtectedItem);
}
break;
case JOINED:
case KICKED:
protocolAssert(args.size() == 2);
joinStr = (command.command == JOINED ? "%1 joined to the session %2" : "%1 left session %2");
if(args[1] == username)
{
ui->buttonReady->setText("Ready");
ui->chat->clear(); //cleanup the chat
sysMessage(joinStr.arg("you", args[0]));
session = args[0];
ui->stackedWidget->setCurrentWidget(command.command == JOINED ? ui->roomPage : ui->sessionsPage);
}
else
{
sysMessage(joinStr.arg(args[1], args[0]));
}
break;
case MODS: {
protocolAssert(args.size() > 0);
amount = args[0].toInt();
protocolAssert(amount * 2 == (args.size() - 1));
tagPoint = 1;
ui->modsList->clear();
auto enabledMods = buildModsMap();
for(int i = 0; i < amount; ++i, tagPoint += 2)
{
if(enabledMods.contains(args[tagPoint]))
{
if(enabledMods[args[tagPoint]] == args[tagPoint + 1])
enabledMods.remove(args[tagPoint]);
else
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-update.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
}
else if(isModAvailable(args[tagPoint], args[tagPoint + 1]))
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
else
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-delete.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
}
for(auto & remainMod : enabledMods.keys())
{
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod])));
}
if(!ui->modsList->count())
ui->modsList->addItem("No issues detected");
break;
}
case CLIENTMODS: {
protocolAssert(args.size() > 1);
amount = args[1].toInt();
protocolAssert(amount * 2 == (args.size() - 2));
tagPoint = 2;
break;
}
case STATUS:
protocolAssert(args.size() > 0);
amount = args[0].toInt();
protocolAssert(amount * 2 == (args.size() - 1));
tagPoint = 1;
ui->playersList->clear();
for(int i = 0; i < amount; ++i, tagPoint += 2)
{
if(args[tagPoint + 1] == "True")
ui->playersList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), args[tagPoint]));
else
ui->playersList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), args[tagPoint]));
if(args[tagPoint] == username)
if(args[tagPoint + 1] == "True")
ui->buttonReady->setText("Not ready");
else
ui->buttonReady->setText("Ready");
}
break;
case START: {
protocolAssert(args.size() == 1);
//actually start game
gameArgs << "--lobby";
gameArgs << "--lobby-address" << serverUrl;
gameArgs << "--lobby-port" << QString::number(serverPort);
gameArgs << "--uuid" << args[0];
startGame(gameArgs);
break;
}
case HOST: {
protocolAssert(args.size() == 2);
gameArgs << "--lobby-host";
gameArgs << "--lobby-uuid" << args[0];
gameArgs << "--lobby-connections" << args[1];
break;
}
case CHAT: {
protocolAssert(args.size() > 1);
QString msg;
for(int i = 1; i < args.size(); ++i)
msg += args[i];
chatMessage(args[0], msg);
break;
}
default:
sysMessage("Unknown server command");
}
if(authentificationStatus == AuthStatus::AUTH_ERROR)
{
socketLobby.disconnectServer();
}
else
{
authentificationStatus = AuthStatus::AUTH_OK;
ui->newButton->setEnabled(true);
}
}
catch(const ProtocolError & e)
{
chatMessage("System error", e.what(), true);
}
void Lobby::dispatchMessage(QString txt) try
{
if(txt.isEmpty())
return;
QStringList parseTags = txt.split(":>>");
protocolAssert(parseTags.size() > 1 && parseTags[0].isEmpty() && !parseTags[1].isEmpty());
for(int c = 1; c < parseTags.size(); ++c)
{
QStringList parseArgs = parseTags[c].split(":");
protocolAssert(parseArgs.size() > 1);
auto ctype = ProtocolStrings.key(parseArgs[0]);
parseArgs.pop_front();
ServerCommand cmd(ctype, parseArgs);
serverCommand(cmd);
}
}
catch(const ProtocolError & e)
{
chatMessage("System error", e.what(), true);
}
void Lobby::onDisconnected()
{
authentificationStatus = AuthStatus::AUTH_NONE;
ui->stackedWidget->setCurrentWidget(ui->sessionsPage);
ui->connectButton->setChecked(false);
ui->serverEdit->setEnabled(true);
ui->userEdit->setEnabled(true);
ui->newButton->setEnabled(false);
ui->joinButton->setEnabled(false);
ui->sessionsTable->clear();
}
void Lobby::chatMessage(QString title, QString body, bool isSystem)
{
QTextCharFormat fmtBody, fmtTitle;
fmtTitle.setFontWeight(QFont::Bold);
if(isSystem)
fmtBody.setFontWeight(QFont::DemiBold);
QTextCursor curs(ui->chat->document());
curs.movePosition(QTextCursor::End);
curs.insertText(title + ": ", fmtTitle);
curs.insertText(body + "\n", fmtBody);
ui->chat->ensureCursorVisible();
}
void Lobby::sysMessage(QString body)
{
chatMessage("System", body, true);
}
void Lobby::protocolAssert(bool expr)
{
if(!expr)
throw ProtocolError("Protocol error");
}
void Lobby::on_messageEdit_returnPressed()
{
socketLobby.send(ProtocolStrings[MESSAGE].arg(ui->messageEdit->text()));
ui->messageEdit->clear();
}
void Lobby::on_connectButton_toggled(bool checked)
{
if(checked)
{
authentificationStatus = AuthStatus::AUTH_NONE;
username = ui->userEdit->text();
const int connectionTimeout = settings["launcher"]["connectionTimeout"].Integer();
auto serverStrings = ui->serverEdit->text().split(":");
if(serverStrings.size() != 2)
{
QMessageBox::critical(this, "Connection error", "Server address must have the format URL:port");
return;
}
serverUrl = serverStrings[0];
serverPort = serverStrings[1].toInt();
Settings node = settings.write["launcher"];
node["lobbyUrl"].String() = serverUrl.toStdString();
node["lobbyPort"].Integer() = serverPort;
node["lobbyUsername"].String() = username.toStdString();
ui->serverEdit->setEnabled(false);
ui->userEdit->setEnabled(false);
sysMessage("Connecting to " + serverUrl + ":" + QString::number(serverPort));
//show text immediately
ui->chat->repaint();
qApp->processEvents();
socketLobby.connectServer(serverUrl, serverPort, username, connectionTimeout);
}
else
{
ui->serverEdit->setEnabled(true);
ui->userEdit->setEnabled(true);
socketLobby.disconnectServer();
}
}
void Lobby::on_newButton_clicked()
{
new LobbyRoomRequest(socketLobby, "", buildModsMap(), this);
}
void Lobby::on_joinButton_clicked()
{
auto * item = ui->sessionsTable->item(ui->sessionsTable->currentRow(), 0);
if(item)
{
auto isPrivate = ui->sessionsTable->item(ui->sessionsTable->currentRow(), 2)->data(Qt::UserRole).toBool();
if(isPrivate)
new LobbyRoomRequest(socketLobby, item->text(), buildModsMap(), this);
else
socketLobby.requestJoinSession(item->text(), "", buildModsMap());
}
}
void Lobby::on_buttonLeave_clicked()
{
socketLobby.requestLeaveSession(session);
}
void Lobby::on_buttonReady_clicked()
{
if(ui->buttonReady->text() == "Ready")
ui->buttonReady->setText("Not ready");
else
ui->buttonReady->setText("Ready");
socketLobby.requestReadySession(session);
}
void Lobby::on_sessionsTable_itemSelectionChanged()
{
auto selection = ui->sessionsTable->selectedItems();
ui->joinButton->setEnabled(!selection.empty());
}
void Lobby::on_playersList_currentRowChanged(int currentRow)
{
ui->kickButton->setVisible(ui->playersList->currentItem()
&& currentRow > 0
&& ui->playersList->currentItem()->text() != username);
}
void Lobby::on_kickButton_clicked()
{
if(ui->playersList->currentItem() && ui->playersList->currentItem()->text() != username)
socketLobby.send(ProtocolStrings[KICK].arg(ui->playersList->currentItem()->text()));
}

View File

@ -0,0 +1,76 @@
/*
* lobby_moc.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 <QWidget>
#include "lobby.h"
namespace Ui {
class Lobby;
}
class Lobby : public QWidget
{
Q_OBJECT
public:
explicit Lobby(QWidget *parent = nullptr);
~Lobby();
private slots:
void on_messageEdit_returnPressed();
void chatMessage(QString title, QString body, bool isSystem = false);
void sysMessage(QString body);
void dispatchMessage(QString);
void serverCommand(const ServerCommand &);
void on_connectButton_toggled(bool checked);
void on_newButton_clicked();
void on_joinButton_clicked();
void on_buttonLeave_clicked();
void on_buttonReady_clicked();
void onDisconnected();
void on_sessionsTable_itemSelectionChanged();
void on_playersList_currentRowChanged(int currentRow);
void on_kickButton_clicked();
private:
QString serverUrl;
int serverPort;
Ui::Lobby *ui;
SocketLobby socketLobby;
QString hostSession;
QString session;
QString username;
QStringList gameArgs;
enum AuthStatus
{
AUTH_NONE, AUTH_OK, AUTH_ERROR
};
AuthStatus authentificationStatus = AUTH_NONE;
private:
QMap<QString, QString> buildModsMap() const;
bool isModAvailable(const QString & modName, const QString & modVersion) const;
void protocolAssert(bool);
};

216
launcher/lobby/lobby_moc.ui Normal file
View File

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Lobby</class>
<widget class="QWidget" name="Lobby">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>652</width>
<height>329</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="5">
<widget class="QPushButton" name="connectButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Connect</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLineEdit" name="messageEdit"/>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLineEdit" name="userEdit"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="serverEdit">
<property name="text">
<string>127.0.0.1:5002</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QPlainTextEdit" name="chat">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Server</string>
</property>
</widget>
</item>
<item row="2" column="3" rowspan="2" colspan="3">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="sessionsPage">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QTableWidget" name="sessionsTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>20</number>
</attribute>
<column>
<property name="text">
<string>Session</string>
</property>
</column>
<column>
<property name="text">
<string>Players</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="newButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>New room</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="joinButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Join room</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="roomPage">
<layout class="QGridLayout" name="gridLayout_3">
<item row="5" column="1">
<widget class="QPushButton" name="buttonReady">
<property name="text">
<string>Ready</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Mods mismatch</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="buttonLeave">
<property name="text">
<string>Leave</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QListWidget" name="modsList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QListWidget" name="playersList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="kickButton">
<property name="text">
<string>Kick player</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Players in the room</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,50 @@
/*
* lobbyroomrequest_moc.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 "lobbyroomrequest_moc.h"
#include "ui_lobbyroomrequest_moc.h"
LobbyRoomRequest::LobbyRoomRequest(SocketLobby & socket, const QString & room, const QMap<QString, QString> & mods, QWidget *parent) :
QDialog(parent),
ui(new Ui::LobbyRoomRequest),
socketLobby(socket),
mods(mods)
{
ui->setupUi(this);
ui->nameEdit->setText(room);
if(!room.isEmpty())
{
ui->nameEdit->setReadOnly(true);
ui->totalPlayers->setEnabled(false);
}
show();
}
LobbyRoomRequest::~LobbyRoomRequest()
{
delete ui;
}
void LobbyRoomRequest::on_buttonBox_accepted()
{
if(ui->nameEdit->isReadOnly())
{
socketLobby.requestJoinSession(ui->nameEdit->text(), ui->passwordEdit->text(), mods);
}
else
{
if(!ui->nameEdit->text().isEmpty())
{
int totalPlayers = ui->totalPlayers->currentIndex() + 2; //where 2 is a minimum amount of players
socketLobby.requestNewSession(ui->nameEdit->text(), totalPlayers, ui->passwordEdit->text(), mods);
}
}
}

View File

@ -0,0 +1,37 @@
/*
* lobbyroomrequest_moc.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
*
*/
#ifndef LOBBYROOMREQUEST_MOC_H
#define LOBBYROOMREQUEST_MOC_H
#include <QDialog>
#include "lobby.h"
namespace Ui {
class LobbyRoomRequest;
}
class LobbyRoomRequest : public QDialog
{
Q_OBJECT
public:
explicit LobbyRoomRequest(SocketLobby & socket, const QString & room, const QMap<QString, QString> & mods, QWidget *parent = nullptr);
~LobbyRoomRequest();
private slots:
void on_buttonBox_accepted();
private:
Ui::LobbyRoomRequest *ui;
SocketLobby & socketLobby;
QMap<QString, QString> mods;
};
#endif // LOBBYROOMREQUEST_MOC_H

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LobbyRoomRequest</class>
<widget class="QDialog" name="LobbyRoomRequest">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>193</width>
<height>188</height>
</rect>
</property>
<property name="windowTitle">
<string>Room settings</string>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Room name</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="nameEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Maximum players</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="totalPlayers">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Password (optional)</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLineEdit" name="passwordEdit"/>
</item>
<item row="5" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LobbyRoomRequest</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LobbyRoomRequest</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,38 +1,86 @@
/*
* main.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 "main.h"
#include "mainwindow_moc.h"
#include <QApplication>
// Conan workaround https://github.com/conan-io/conan-center-index/issues/13332
#ifdef VCMI_IOS
#if __has_include("QIOSIntegrationPlugin.h")
#include "QIOSIntegrationPlugin.h"
#endif
#endif
int main(int argc, char * argv[])
{
int result;
#ifdef VCMI_IOS
{
#endif
QApplication vcmilauncher(argc, argv);
MainWindow mainWindow;
mainWindow.show();
result = vcmilauncher.exec();
#ifdef VCMI_IOS
}
if (result == 0)
launchGame(argc, argv);
#endif
return result;
}
/*
* main.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 "main.h"
#include "mainwindow_moc.h"
#include <QApplication>
#include <QProcess>
#include <QMessageBox>
#include "../lib/VCMIDirs.h"
// Conan workaround https://github.com/conan-io/conan-center-index/issues/13332
#ifdef VCMI_IOS
#if __has_include("QIOSIntegrationPlugin.h")
#include "QIOSIntegrationPlugin.h"
#endif
int argcForClient;
char ** argvForClient;
#endif
int main(int argc, char * argv[])
{
int result;
#ifdef VCMI_IOS
{
#endif
QApplication vcmilauncher(argc, argv);
MainWindow mainWindow;
mainWindow.show();
result = vcmilauncher.exec();
#ifdef VCMI_IOS
}
if (result == 0)
launchGame(argcForClient, argvForClient);
#endif
return result;
}
void startGame(const QStringList & args)
{
logGlobal->warn("Starting game with the arguments: %s", args.join(" ").toStdString());
#ifdef Q_OS_IOS
argcForClient = args.size() + 1; //first argument is omitted
argvForClient = new char*[argcForClient];
argvForClient[0] = "vcmiclient";
for(int i = 1; i < argcForClient; ++i)
{
std::string s = args.at(i - 1).toStdString();
argvForClient[i] = new char[s.size() + 1];
strcpy(argvForClient[i], s.c_str());
}
qApp->quit();
#else
startExecutable(pathToQString(VCMIDirs::get().clientPath()), args);
#endif
}
#ifndef Q_OS_IOS
void startExecutable(QString name, const QStringList & args)
{
QProcess process;
// Start the executable
if(process.startDetached(name, args))
{
qApp->quit();
}
else
{
QMessageBox::critical(qApp->activeWindow(),
"Error starting executable",
"Failed to start " + name + "\n"
"Reason: " + process.errorString(),
QMessageBox::Ok,
QMessageBox::Ok);
}
}
#endif

View File

@ -1,14 +1,18 @@
/*
* main.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
#ifdef VCMI_IOS
extern "C" void launchGame(int argc, char * argv[]);
#endif
/*
* main.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
void startGame(const QStringList & args);
#ifdef VCMI_IOS
extern "C" void launchGame(int argc, char * argv[]);
#else
void startExecutable(QString name, const QStringList & args);
#endif

View File

@ -11,7 +11,6 @@
#include "mainwindow_moc.h"
#include "ui_mainwindow_moc.h"
#include <QProcess>
#include <QDir>
#include "../lib/CConfigHandler.h"
@ -20,6 +19,7 @@
#include "../lib/logging/CBasicLogConfigurator.h"
#include "updatedialog_moc.h"
#include "main.h"
void MainWindow::load()
{
@ -85,9 +85,9 @@ MainWindow::MainWindow(QWidget * parent)
}
ui->tabListWidget->setCurrentIndex(0);
ui->settingsView->isExtraResolutionsModEnabled = ui->stackedWidgetPage2->isExtraResolutionsModEnabled();
ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled();
ui->settingsView->setDisplayList();
connect(ui->stackedWidgetPage2, &CModListView::extraResolutionsEnabledChanged,
connect(ui->modlistView, &CModListView::extraResolutionsEnabledChanged,
ui->settingsView, &CSettingsView::fillValidResolutions);
connect(ui->tabSelectList, &QListWidget::currentRowChanged, [this](int i) {
@ -97,6 +97,11 @@ MainWindow::MainWindow(QWidget * parent)
#endif
ui->tabListWidget->setCurrentIndex(i);
});
#ifdef Q_OS_IOS
QScroller::grabGesture(ui->tabSelectList, QScroller::LeftMouseButtonGesture);
ui->tabSelectList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
#endif
if(settings["launcher"]["updateOnStartup"].Bool())
UpdateDialog::showUpdateDialog(false);
@ -114,31 +119,15 @@ MainWindow::~MainWindow()
void MainWindow::on_startGameButton_clicked()
{
#ifdef Q_OS_IOS
qApp->quit();
#else
startExecutable(pathToQString(VCMIDirs::get().clientPath()));
#endif
startGame({});
}
#ifndef Q_OS_IOS
void MainWindow::startExecutable(QString name)
void MainWindow::on_tabSelectList_currentRowChanged(int currentRow)
{
QProcess process;
// Start the executable
if(process.startDetached(name, {}))
{
close(); // exit launcher
}
else
{
QMessageBox::critical(this,
"Error starting executable",
"Failed to start " + name + "\n"
"Reason: " + process.errorString(),
QMessageBox::Ok,
QMessageBox::Ok);
}
ui->startGameButton->setEnabled(currentRow != TabRows::LOBBY);
}
const CModList & MainWindow::getModList() const
{
return ui->modlistView->getModList();
}
#endif

View File

@ -19,6 +19,8 @@ const QString appName = "VCMI Launcher";
}
class QTableWidgetItem;
class CModList;
class MainWindow : public QMainWindow
{
@ -27,14 +29,22 @@ class MainWindow : public QMainWindow
private:
Ui::MainWindow * ui;
void load();
#ifndef Q_OS_IOS
void startExecutable(QString name);
#endif
enum TabRows
{
MODS = 0, SETTINGS = 1, LOBBY = 2
};
public:
explicit MainWindow(QWidget * parent = 0);
~MainWindow();
private slots:
const CModList & getModList() const;
public slots:
void on_startGameButton_clicked();
private slots:
void on_tabSelectList_currentRowChanged(int currentRow);
};

View File

@ -51,6 +51,9 @@
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
@ -75,6 +78,9 @@
<property name="flow">
<enum>QListView::TopToBottom</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
@ -91,7 +97,7 @@
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
<bool>false</bool>
</property>
<item>
<property name="text">
@ -111,13 +117,21 @@
<normaloff>icons:menu-settings.png</normaloff>icons:menu-settings.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Lobby</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-lobby.png</normaloff>icons:menu-lobby.png</iconset>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="startGameTitle">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@ -143,8 +157,9 @@
<property name="currentIndex">
<number>0</number>
</property>
<widget class="CModListView" name="stackedWidgetPage2"/>
<widget class="CModListView" name="modlistView"/>
<widget class="CSettingsView" name="settingsView"/>
<widget class="Lobby" name="lobbyView"/>
</widget>
</item>
<item row="1" column="0">
@ -202,6 +217,12 @@
<header>settingsView/csettingsview_moc.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>Lobby</class>
<extends>QWidget</extends>
<header>lobby/lobby_moc.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabSelectList</tabstop>

View File

@ -102,6 +102,17 @@ CModListView::CModListView(QWidget * parent)
{
manager->resetRepositories();
}
#ifdef Q_OS_IOS
for(auto * scrollWidget : {
(QAbstractItemView*)ui->allModsView,
(QAbstractItemView*)ui->screenshotsList})
{
QScroller::grabGesture(scrollWidget, QScroller::LeftMouseButtonGesture);
scrollWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
scrollWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
#endif
}
void CModListView::loadRepositories()
@ -825,3 +836,9 @@ void CModListView::on_showInfoButton_clicked()
{
showModInfo();
}
const CModList & CModListView::getModList() const
{
assert(modModel);
return *modModel;
}

View File

@ -18,6 +18,7 @@ class CModListView;
}
class CModManager;
class CModList;
class CModListModel;
class CModFilterModel;
class CDownloadManager;
@ -80,6 +81,8 @@ public:
void selectMod(const QModelIndex & index);
bool isExtraResolutionsModEnabled() const;
const CModList & getModList() const;
private slots:
void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);
void modSelected(const QModelIndex & current, const QModelIndex & previous);

View File

@ -72,8 +72,8 @@ const std::string & CArtifact::getJsonKey() const
void CArtifact::registerIcons(const IconRegistar & cb) const
{
cb(getIconIndex(), "ARTIFACT", image);
cb(getIconIndex(), "ARTIFACTLARGE", large);
cb(getIconIndex(), 0, "ARTIFACT", image);
cb(getIconIndex(), 0, "ARTIFACTLARGE", large);
}
ArtifactID CArtifact::getId() const
@ -851,7 +851,7 @@ void CArtifactInstance::putAt(ArtifactLocation al)
assert(canBePutAt(al));
al.getHolderArtSet()->setNewArtSlot(al.slot, this, false);
if(al.slot < GameConstants::BACKPACK_START)
if(!ArtifactUtils::isSlotBackpack(al.slot))
al.getHolderNode()->attachTo(*this);
}
@ -859,7 +859,7 @@ void CArtifactInstance::removeFrom(ArtifactLocation al)
{
assert(al.getHolderArtSet()->getArt(al.slot) == this);
al.getHolderArtSet()->eraseArtSlot(al.slot);
if(al.slot < GameConstants::BACKPACK_START)
if(!ArtifactUtils::isSlotBackpack(al.slot))
al.getHolderNode()->detachFrom(*this);
//TODO delete me?
@ -938,12 +938,12 @@ CArtifactInstance * CArtifactInstance::createNewArtifactInstance(CArtifact *Art)
}
}
CArtifactInstance * CArtifactInstance::createNewArtifactInstance(int aid)
CArtifactInstance * CArtifactInstance::createNewArtifactInstance(ArtifactID aid)
{
return createNewArtifactInstance(VLC->arth->objects[aid]);
}
CArtifactInstance * CArtifactInstance::createArtifact(CMap * map, int aid, int spellID)
CArtifactInstance * CArtifactInstance::createArtifact(CMap * map, ArtifactID aid, int spellID)
{
CArtifactInstance * a = nullptr;
if(aid >= 0)
@ -1003,7 +1003,7 @@ bool CCombinedArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactP
bool canMainArtifactBePlaced = CArtifactInstance::canBePutAt(artSet, slot, assumeDestRemoved);
if(!canMainArtifactBePlaced)
return false; //no is no...
if(slot >= GameConstants::BACKPACK_START)
if(ArtifactUtils::isSlotBackpack(slot))
return true; //we can always remove combined art to the backapck
@ -1019,11 +1019,12 @@ bool CCombinedArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactP
}
//we iterate over all active slots and check if constituents fits them
for (int i = 0; i < GameConstants::BACKPACK_START; i++)
for(const auto pos : ArtifactUtils::constituentWornSlots())
{
for(auto art = constituentsToBePlaced.begin(); art != constituentsToBePlaced.end(); art++)
{
if(art->art->canBePutAt(artSet, ArtifactPosition(i), i == slot)) // i == al.slot because we can remove already worn artifact only from that slot that is our main destination
// pos == slot because we can remove already worn artifact only from that slot. That is our main destination
if(art->art->canBePutAt(artSet, pos, pos == slot))
{
constituentsToBePlaced.erase(art);
break;
@ -1069,7 +1070,7 @@ void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, Artifac
void CCombinedArtifactInstance::putAt(ArtifactLocation al)
{
if(al.slot >= GameConstants::BACKPACK_START)
if(ArtifactUtils::isSlotBackpack(al.slot))
{
CArtifactInstance::putAt(al);
for(ConstituentInfo &ci : constituentsInfo)
@ -1094,7 +1095,7 @@ void CCombinedArtifactInstance::putAt(ArtifactLocation al)
else
ci.slot = pos = ci.art->firstAvailableSlot(al.getHolderArtSet());
assert(pos < GameConstants::BACKPACK_START);
assert(!ArtifactUtils::isSlotBackpack(pos));
al.getHolderArtSet()->setNewArtSlot(pos, ci.art, true); //sets as lock
}
else
@ -1107,7 +1108,7 @@ void CCombinedArtifactInstance::putAt(ArtifactLocation al)
void CCombinedArtifactInstance::removeFrom(ArtifactLocation al)
{
if(al.slot >= GameConstants::BACKPACK_START)
if(ArtifactUtils::isSlotBackpack(al.slot))
{
CArtifactInstance::removeFrom(al);
}
@ -1199,19 +1200,19 @@ CArtifactInstance* CArtifactSet::getArt(ArtifactPosition pos, bool excludeLocked
return const_cast<CArtifactInstance*>((const_cast<const CArtifactSet*>(this))->getArt(pos, excludeLocked));
}
ArtifactPosition CArtifactSet::getArtPos(int aid, bool onlyWorn, bool allowLocked) const
ArtifactPosition CArtifactSet::getArtPos(ArtifactID aid, bool onlyWorn, bool allowLocked) const
{
const auto result = getAllArtPositions(aid, onlyWorn, allowLocked, false);
return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0];
}
ArtifactPosition CArtifactSet::getArtBackpackPos(int aid) const
ArtifactPosition CArtifactSet::getArtBackpackPos(ArtifactID aid) const
{
const auto result = getBackpackArtPositions(aid);
return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0];
}
std::vector<ArtifactPosition> CArtifactSet::getAllArtPositions(int aid, bool onlyWorn, bool allowLocked, bool getAll) const
std::vector<ArtifactPosition> CArtifactSet::getAllArtPositions(ArtifactID aid, bool onlyWorn, bool allowLocked, bool getAll) const
{
std::vector<ArtifactPosition> result;
for(auto & slotInfo : artifactsWorn)
@ -1228,7 +1229,7 @@ std::vector<ArtifactPosition> CArtifactSet::getAllArtPositions(int aid, bool onl
return result;
}
std::vector<ArtifactPosition> CArtifactSet::getBackpackArtPositions(int aid) const
std::vector<ArtifactPosition> CArtifactSet::getBackpackArtPositions(ArtifactID aid) const
{
std::vector<ArtifactPosition> result;
@ -1270,7 +1271,7 @@ const CArtifactInstance * CArtifactSet::getArtByInstanceId( ArtifactInstanceID a
}
bool CArtifactSet::hasArt(
ui32 aid,
ArtifactID aid,
bool onlyWorn,
bool searchBackpackAssemblies,
bool allowLocked) const
@ -1278,12 +1279,12 @@ bool CArtifactSet::hasArt(
return getArtPosCount(aid, onlyWorn, searchBackpackAssemblies, allowLocked) > 0;
}
bool CArtifactSet::hasArtBackpack(ui32 aid) const
bool CArtifactSet::hasArtBackpack(ArtifactID aid) const
{
return getBackpackArtPositions(aid).size() > 0;
}
unsigned CArtifactSet::getArtPosCount(int aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const
unsigned CArtifactSet::getArtPosCount(ArtifactID aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const
{
const auto allPositions = getAllArtPositions(aid, onlyWorn, allowLocked, true);
if(!allPositions.empty())
@ -1296,7 +1297,7 @@ unsigned CArtifactSet::getArtPosCount(int aid, bool onlyWorn, bool searchBackpac
}
std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *>
CArtifactSet::searchForConstituent(int aid) const
CArtifactSet::searchForConstituent(ArtifactID aid) const
{
for(auto & slot : artifactsInBackpack)
{
@ -1316,12 +1317,12 @@ CArtifactSet::searchForConstituent(int aid) const
return {nullptr, nullptr};
}
const CArtifactInstance *CArtifactSet::getHiddenArt(int aid) const
const CArtifactInstance *CArtifactSet::getHiddenArt(ArtifactID aid) const
{
return searchForConstituent(aid).second;
}
const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(int aid) const
const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(ArtifactID aid) const
{
return searchForConstituent(aid).first;
}
@ -1353,7 +1354,7 @@ bool CArtifactSet::isPositionFree(ArtifactPosition pos, bool onlyLockCheck) cons
ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot)
{
assert(!vstd::contains(artifactsWorn, slot));
ArtSlotInfo &ret = slot < GameConstants::BACKPACK_START
ArtSlotInfo &ret = !ArtifactUtils::isSlotBackpack(slot)
? artifactsWorn[slot]
: *artifactsInBackpack.insert(artifactsInBackpack.begin() + (slot - GameConstants::BACKPACK_START), ArtSlotInfo());
@ -1369,14 +1370,15 @@ void CArtifactSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art,
void CArtifactSet::eraseArtSlot(ArtifactPosition slot)
{
if(slot < GameConstants::BACKPACK_START)
if(ArtifactUtils::isSlotBackpack(slot))
{
artifactsWorn.erase(slot);
assert(artifactsInBackpack.begin() + slot < artifactsInBackpack.end());
slot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
artifactsInBackpack.erase(artifactsInBackpack.begin() + slot);
}
else
{
slot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
artifactsInBackpack.erase(artifactsInBackpack.begin() + slot);
artifactsWorn.erase(slot);
}
}
@ -1543,16 +1545,41 @@ DLL_LINKAGE ArtifactPosition ArtifactUtils::getArtifactDstPosition( const CArtif
return ArtifactPosition(GameConstants::BACKPACK_START);
}
DLL_LINKAGE std::vector<ArtifactPosition> ArtifactUtils::unmovablePositions()
DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUtils::unmovableSlots()
{
return { ArtifactPosition::SPELLBOOK, ArtifactPosition::MACH4 };
return
{
ArtifactPosition::SPELLBOOK,
ArtifactPosition::MACH4
};
}
DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUtils::constituentWornSlots()
{
return
{
ArtifactPosition::HEAD,
ArtifactPosition::SHOULDERS,
ArtifactPosition::NECK,
ArtifactPosition::RIGHT_HAND,
ArtifactPosition::LEFT_HAND,
ArtifactPosition::TORSO,
ArtifactPosition::RIGHT_RING,
ArtifactPosition::LEFT_RING,
ArtifactPosition::FEET,
ArtifactPosition::MISC1,
ArtifactPosition::MISC2,
ArtifactPosition::MISC3,
ArtifactPosition::MISC4,
ArtifactPosition::MISC5,
};
}
DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot)
{
return slot.second.artifact
&& !slot.second.locked
&& !vstd::contains(unmovablePositions(), slot.first);
&& !vstd::contains(unmovableSlots(), slot.first);
}
DLL_LINKAGE bool ArtifactUtils::checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot)

View File

@ -176,7 +176,7 @@ public:
static CArtifactInstance *createScroll(SpellID sid);
static CArtifactInstance *createNewArtifactInstance(CArtifact *Art);
static CArtifactInstance *createNewArtifactInstance(int aid);
static CArtifactInstance *createNewArtifactInstance(ArtifactID aid);
/**
* Creates an artifact instance.
@ -185,7 +185,7 @@ public:
* @param spellID optional. the id of a spell if a spell scroll object should be created
* @return the created artifact instance
*/
static CArtifactInstance * createArtifact(CMap * map, int aid, int spellID = -1);
static CArtifactInstance * createArtifact(CMap * map, ArtifactID aid, int spellID = -1);
};
class DLL_LINKAGE CCombinedArtifactInstance : public CArtifactInstance
@ -327,20 +327,20 @@ public:
CArtifactInstance* getArt(ArtifactPosition pos, bool excludeLocked = true); //nullptr - no artifact
/// Looks for equipped artifact with given ID and returns its slot ID or -1 if none
/// (if more than one such artifact lower ID is returned)
ArtifactPosition getArtPos(int aid, bool onlyWorn = true, bool allowLocked = true) const;
ArtifactPosition getArtPos(ArtifactID aid, bool onlyWorn = true, bool allowLocked = true) const;
ArtifactPosition getArtPos(const CArtifactInstance *art) const;
ArtifactPosition getArtBackpackPos(int aid) const;
std::vector<ArtifactPosition> getAllArtPositions(int aid, bool onlyWorn, bool allowLocked, bool getAll) const;
std::vector<ArtifactPosition> getBackpackArtPositions(int aid) const;
ArtifactPosition getArtBackpackPos(ArtifactID aid) const;
std::vector<ArtifactPosition> getAllArtPositions(ArtifactID aid, bool onlyWorn, bool allowLocked, bool getAll) const;
std::vector<ArtifactPosition> getBackpackArtPositions(ArtifactID aid) const;
const CArtifactInstance *getArtByInstanceId(ArtifactInstanceID artInstId) const;
/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
const CArtifactInstance *getHiddenArt(int aid) const;
const CCombinedArtifactInstance *getAssemblyByConstituent(int aid) const;
const CArtifactInstance *getHiddenArt(ArtifactID aid) const;
const CCombinedArtifactInstance *getAssemblyByConstituent(ArtifactID aid) const;
/// Checks if hero possess artifact of given id (either in backack or worn)
bool hasArt(ui32 aid, bool onlyWorn = false, bool searchBackpackAssemblies = false, bool allowLocked = true) const;
bool hasArtBackpack(ui32 aid) const;
bool hasArt(ArtifactID aid, bool onlyWorn = false, bool searchBackpackAssemblies = false, bool allowLocked = true) const;
bool hasArtBackpack(ArtifactID aid) const;
bool isPositionFree(ArtifactPosition pos, bool onlyLockCheck = false) const;
unsigned getArtPosCount(int aid, bool onlyWorn = true, bool searchBackpackAssemblies = true, bool allowLocked = true) const;
unsigned getArtPosCount(ArtifactID aid, bool onlyWorn = true, bool searchBackpackAssemblies = true, bool allowLocked = true) const;
virtual ArtBearer::ArtBearer bearerType() const = 0;
virtual void putArtifact(ArtifactPosition pos, CArtifactInstance * art) = 0;
@ -358,7 +358,7 @@ public:
protected:
std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> searchForConstituent(int aid) const;
std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> searchForConstituent(ArtifactID aid) const;
private:
void serializeJsonHero(JsonSerializeFormat & handler, CMap * map);
void serializeJsonCreature(JsonSerializeFormat & handler, CMap * map);
@ -386,7 +386,9 @@ namespace ArtifactUtils
DLL_LINKAGE ArtifactPosition getArtifactDstPosition( const CArtifactInstance * artifact,
const CArtifactSet * target,
ArtBearer::ArtBearer bearer);
DLL_LINKAGE std::vector<ArtifactPosition> unmovablePositions(); // TODO: Make this constexpr when the toolset is upgraded
// TODO: Make this constexpr when the toolset is upgraded
DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & unmovableSlots();
DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & constituentWornSlots();
DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot);
DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot);
DLL_LINKAGE bool isSlotBackpack(ArtifactPosition slot);

View File

@ -46,8 +46,8 @@ const std::string & CCreature::getJsonKey() const
void CCreature::registerIcons(const IconRegistar & cb) const
{
cb(getIconIndex(), "CPRSMALL", smallIconName);
cb(getIconIndex(), "TWCRPORT", largeIconName);
cb(getIconIndex(), 0, "CPRSMALL", smallIconName);
cb(getIconIndex(), 0, "TWCRPORT", largeIconName);
}
CreatureID CCreature::getId() const

View File

@ -2256,7 +2256,7 @@ bool CGameState::checkForVictory(PlayerColor player, const EventCondition & cond
case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact
{
for(auto & elem : p->heroes)
if(elem->hasArt(condition.objectType))
if(elem->hasArt(ArtifactID(condition.objectType)))
return true;
return false;
}
@ -2342,8 +2342,8 @@ bool CGameState::checkForVictory(PlayerColor player, const EventCondition & cond
case EventCondition::TRANSPORT:
{
const CGTownInstance *t = static_cast<const CGTownInstance *>(condition.object);
if((t->visitingHero && t->visitingHero->hasArt(condition.objectType))
|| (t->garrisonHero && t->garrisonHero->hasArt(condition.objectType)))
if((t->visitingHero && t->visitingHero->hasArt(ArtifactID(condition.objectType)))
|| (t->garrisonHero && t->garrisonHero->hasArt(ArtifactID(condition.objectType))))
{
return true;
}

View File

@ -59,10 +59,10 @@ HeroTypeID CHero::getId() const
void CHero::registerIcons(const IconRegistar & cb) const
{
cb(getIconIndex(), "UN32", iconSpecSmall);
cb(getIconIndex(), "UN44", iconSpecLarge);
cb(getIconIndex(), "PORTRAITSLARGE", portraitLarge);
cb(getIconIndex(), "PORTRAITSSMALL", portraitSmall);
cb(getIconIndex(), 0, "UN32", iconSpecSmall);
cb(getIconIndex(), 0, "UN44", iconSpecLarge);
cb(getIconIndex(), 0, "PORTRAITSLARGE", portraitLarge);
cb(getIconIndex(), 0, "PORTRAITSSMALL", portraitSmall);
}
void CHero::updateFrom(const JsonNode & data)

View File

@ -70,9 +70,9 @@ void CSkill::registerIcons(const IconRegistar & cb) const
{
int frame = 2 + level + 3 * id;
const LevelInfo & skillAtLevel = at(level);
cb(frame, "SECSK32", skillAtLevel.iconSmall);
cb(frame, "SECSKILL", skillAtLevel.iconMedium);
cb(frame, "SECSK82", skillAtLevel.iconLarge);
cb(frame, 0, "SECSK32", skillAtLevel.iconSmall);
cb(frame, 0, "SECSKILL", skillAtLevel.iconMedium);
cb(frame, 0, "SECSK82", skillAtLevel.iconLarge);
}
}

View File

@ -126,15 +126,19 @@ void CFaction::registerIcons(const IconRegistar & cb) const
if(town)
{
auto & info = town->clientInfo;
cb(info.icons[0][0], "ITPT", info.iconLarge[0][0]);
cb(info.icons[0][1], "ITPT", info.iconLarge[0][1]);
cb(info.icons[1][0], "ITPT", info.iconLarge[1][0]);
cb(info.icons[1][1], "ITPT", info.iconLarge[1][1]);
cb(info.icons[0][0], 0, "ITPT", info.iconLarge[0][0]);
cb(info.icons[0][1], 0, "ITPT", info.iconLarge[0][1]);
cb(info.icons[1][0], 0, "ITPT", info.iconLarge[1][0]);
cb(info.icons[1][1], 0, "ITPT", info.iconLarge[1][1]);
cb(info.icons[0][0] + 2, 0, "ITPA", info.iconSmall[0][0]);
cb(info.icons[0][1] + 2, 0, "ITPA", info.iconSmall[0][1]);
cb(info.icons[1][0] + 2, 0, "ITPA", info.iconSmall[1][0]);
cb(info.icons[1][1] + 2, 0, "ITPA", info.iconSmall[1][1]);
cb(index, 1, "CPRSMALL", info.towerIconSmall);
cb(index, 1, "TWCRPORT", info.towerIconLarge);
cb(info.icons[0][0] + 2, "ITPA", info.iconSmall[0][0]);
cb(info.icons[0][1] + 2, "ITPA", info.iconSmall[0][1]);
cb(info.icons[1][0] + 2, "ITPA", info.iconSmall[1][0]);
cb(info.icons[1][1] + 2, "ITPA", info.iconSmall[1][1]);
}
}
@ -755,6 +759,9 @@ CTown::ClientInfo::Point JsonToPoint(const JsonNode & node)
void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source)
{
town.clientInfo.siegePrefix = source["imagePrefix"].String();
town.clientInfo.towerIconSmall = source["towerIconSmall"].String();
town.clientInfo.towerIconLarge = source["towerIconLarge"].String();
VLC->modh->identifiers.requestIdentifier("creature", source["shooter"], [&town](si32 creature)
{
auto crId = CreatureID(creature);

View File

@ -303,6 +303,8 @@ public:
std::string siegePrefix;
std::vector<Point> siegePositions;
CreatureID siegeShooter; // shooter creature ID
std::string towerIconSmall;
std::string towerIconLarge;
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -321,6 +323,8 @@ public:
h & siegePrefix;
h & siegePositions;
h & siegeShooter;
h & towerIconSmall;
h & towerIconLarge;
}
} clientInfo;

View File

@ -1054,6 +1054,14 @@ public:
ID_LIKE_CLASS_COMMON(ArtifactID, EArtifactID)
EArtifactID num;
struct hash
{
size_t operator()(const ArtifactID & aid) const
{
return std::hash<int>()(aid.num);
}
};
};
ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID)

View File

@ -23,6 +23,8 @@ bfs::path IVCMIDirs::userLogsPath() const { return userCachePath(); }
bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; }
bfs::path IVCMIDirs::userExtractedPath() const { return userCachePath() / "extracted"; }
bfs::path IVCMIDirs::fullLibraryPath(const std::string &desiredFolder, const std::string &baseLibName) const
{
return libraryPath() / desiredFolder / libraryName(baseLibName);
@ -36,15 +38,16 @@ std::string IVCMIDirs::genHelpString() const
const auto gdStringA = boost::algorithm::join(tempVec, ":");
return
" game data: " + gdStringA + "\n"
" libraries: " + libraryPath().string() + "\n"
" server: " + serverPath().string() + "\n"
" game data: " + gdStringA + "\n"
" libraries: " + libraryPath().string() + "\n"
" server: " + serverPath().string() + "\n"
"\n"
" user data: " + userDataPath().string() + "\n"
" user cache: " + userCachePath().string() + "\n"
" user config: " + userConfigPath().string() + "\n"
" user logs: " + userLogsPath().string() + "\n"
" user saves: " + userSavePath().string() + "\n"; // Should end without new-line?
" user data: " + userDataPath().string() + "\n"
" user cache: " + userCachePath().string() + "\n"
" user config: " + userConfigPath().string() + "\n"
" user logs: " + userLogsPath().string() + "\n"
" user saves: " + userSavePath().string() + "\n";
" user extracted: " + userExtractedPath().string() + "\n"; // Should end without new-line?
}
void IVCMIDirs::init()
@ -148,24 +151,24 @@ bool StartBatchCopyDataProgram(
class VCMIDirsWIN32 final : public IVCMIDirs
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
std::vector<bfs::path> dataPaths() const override;
boost::filesystem::path clientPath() const override;
boost::filesystem::path serverPath() const override;
bfs::path clientPath() const override;
bfs::path serverPath() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
bfs::path libraryPath() const override;
bfs::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
void init() override;
protected:
boost::filesystem::path oldUserDataPath() const;
boost::filesystem::path oldUserSavePath() const;
bfs::path oldUserDataPath() const;
bfs::path oldUserSavePath() const;
};
void VCMIDirsWIN32::init()
@ -355,8 +358,8 @@ std::string VCMIDirsWIN32::libraryName(const std::string& basename) const { retu
class IVCMIDirsUNIX : public IVCMIDirs
{
public:
boost::filesystem::path clientPath() const override;
boost::filesystem::path serverPath() const override;
bfs::path clientPath() const override;
bfs::path serverPath() const override;
virtual bool developmentMode() const;
};
@ -427,14 +430,14 @@ bfs::path VCMIDirsIOS::binaryPath() const { return {iOS_utils::bundlePath()}; }
class VCMIDirsOSX final : public VCMIDirsApple
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userLogsPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userLogsPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
std::vector<bfs::path> dataPaths() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
bfs::path libraryPath() const override;
bfs::path binaryPath() const override;
void init() override;
};
@ -464,12 +467,12 @@ void VCMIDirsOSX::init()
for (bfs::directory_iterator file(from); file != bfs::directory_iterator(); ++file)
{
const boost::filesystem::path& srcFilePath = file->path();
const boost::filesystem::path dstFilePath = to / srcFilePath.filename();
const bfs::path& srcFilePath = file->path();
const bfs::path dstFilePath = to / srcFilePath.filename();
// TODO: Aplication should ask user what to do when file exists:
// replace/ignore/stop process/replace all/ignore all
if (!boost::filesystem::exists(dstFilePath))
if (!bfs::exists(dstFilePath))
bfs::rename(srcFilePath, dstFilePath);
}
@ -526,14 +529,14 @@ bfs::path VCMIDirsOSX::binaryPath() const { return "."; }
class VCMIDirsXDG : public IVCMIDirsUNIX
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
std::vector<bfs::path> dataPaths() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
bfs::path libraryPath() const override;
bfs::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
};

View File

@ -29,6 +29,9 @@ public:
// Path to saved games
virtual boost::filesystem::path userSavePath() const;
// Path to "extracted" directory, used to temporarily hold extracted Original H3 files
virtual boost::filesystem::path userExtractedPath() const;
// Paths to global system-wide data directories. First items have higher priority
virtual std::vector<boost::filesystem::path> dataPaths() const = 0;

View File

@ -39,13 +39,13 @@ VCMI_LIB_NAMESPACE_BEGIN
LibClasses * VLC = nullptr;
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential)
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential, bool extractArchives)
{
console = Console;
VLC = new LibClasses();
try
{
VLC->loadFilesystem(onlyEssential);
VLC->loadFilesystem(onlyEssential, extractArchives);
}
catch(...)
{
@ -157,7 +157,7 @@ void LibClasses::updateEntity(Metatype metatype, int32_t index, const JsonNode &
}
}
void LibClasses::loadFilesystem(bool onlyEssential)
void LibClasses::loadFilesystem(bool onlyEssential, bool extractArchives)
{
CStopWatch totalTime;
CStopWatch loadTime;
@ -165,7 +165,7 @@ void LibClasses::loadFilesystem(bool onlyEssential)
CResourceHandler::initialize();
logGlobal->info("\tInitialization: %d ms", loadTime.getDiff());
CResourceHandler::load("config/filesystem.json");
CResourceHandler::load("config/filesystem.json", extractArchives);
logGlobal->info("\tData loading: %d ms", loadTime.getDiff());
modh = new CModHandler();

View File

@ -100,8 +100,8 @@ public:
void init(bool onlyEssential); //uses standard config file
void clear(); //deletes all handlers and its data
void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init()
// basic initialization. should be called before init(). Can also extract original H3 archives
void loadFilesystem(bool onlyEssential, bool extractArchives = false);
#if SCRIPTING_ENABLED
void scriptsLoaded();
@ -151,7 +151,7 @@ public:
extern DLL_LINKAGE LibClasses * VLC;
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false);
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false, bool extractArchives = false);
DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false);

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "CArchiveLoader.h"
#include "VCMIDirs.h"
#include "CFileInputStream.h"
#include "CCompressedStream.h"
@ -23,9 +24,10 @@ ArchiveEntry::ArchiveEntry()
}
CArchiveLoader::CArchiveLoader(std::string _mountPoint, boost::filesystem::path _archive) :
CArchiveLoader::CArchiveLoader(std::string _mountPoint, bfs::path _archive, bool extractArchives) :
archive(std::move(_archive)),
mountPoint(std::move(_mountPoint))
mountPoint(std::move(_mountPoint)),
extractArchives(extractArchives)
{
// Open archive file(.snd, .vid, .lod)
CFileInputStream fileStream(archive);
@ -77,6 +79,25 @@ void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStr
// Add lod entry to local entries map
entries[ResourceID(mountPoint + entry.name)] = entry;
if(extractArchives)
{
si64 currentPosition = fileStream.tell(); // save filestream position
std::string fName = filename;
boost::to_upper(fName);
if(fName.find(".PCX") != std::string::npos)
extractToFolder("IMAGES", mountPoint, entry);
else if ((fName.find(".DEF") != std::string::npos ) || (fName.find(".MSK") != std::string::npos) || (fName.find(".FNT") != std::string::npos) || (fName.find(".PAL") != std::string::npos))
extractToFolder("SPRITES", mountPoint, entry);
else if ((fName.find(".h3c") != std::string::npos))
extractToFolder("SPRITES", mountPoint, entry);
else
extractToFolder("MISC", mountPoint, entry);
fileStream.seek(currentPosition); // restore filestream position
}
}
}
@ -112,6 +133,9 @@ void CArchiveLoader::initVIDArchive(const std::string &mountPoint, CFileInputStr
auto it = offsets.find(entry.second.offset);
it++;
entry.second.fullSize = *it - entry.second.offset;
if(extractArchives)
extractToFolder("VIDEO", fileStream, entry.second);
}
}
@ -139,6 +163,9 @@ void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStr
entry.fullSize = reader.readInt32();
entry.compressedSize = 0;
entries[ResourceID(mountPoint + entry.name)] = entry;
if(extractArchives)
extractToFolder("SOUND", fileStream, entry);
}
}
@ -182,4 +209,39 @@ std::unordered_set<ResourceID> CArchiveLoader::getFilteredFiles(std::function<bo
return foundID;
}
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, ArchiveEntry entry)
{
si64 currentPosition = fileStream.tell(); // save filestream position
std::vector<ui8> data(entry.fullSize);
fileStream.seek(entry.offset);
fileStream.read(data.data(), entry.fullSize);
bfs::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name);
// writeToOutputFile
std::ofstream out(extractedFilePath.string(), std::ofstream::binary);
out.exceptions(std::ifstream::failbit | std::ifstream::badbit);
out.write((char*)data.data(), entry.fullSize);
fileStream.seek(currentPosition); // restore filestream position
}
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry)
{
std::unique_ptr<CInputStream> inputStream = load(ResourceID(mountPoint + entry.name));
extractToFolder(outputSubFolder, *inputStream, entry);
}
bfs::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName)
{
bfs::path extractionFolderPath = VCMIDirs::get().userExtractedPath() / outputSubFolder;
bfs::path extractedFilePath = extractionFolderPath / entryName;
bfs::create_directories(extractionFolderPath);
return extractedFilePath;
}
VCMI_LIB_NAMESPACE_END

View File

@ -12,6 +12,8 @@
#include "ISimpleResourceLoader.h"
#include "ResourceID.h"
namespace bfs = boost::filesystem;
VCMI_LIB_NAMESPACE_BEGIN
class CFileInputStream;
@ -52,10 +54,11 @@ public:
* These are valid extensions: .LOD, .SND, .VID
*
* @param archive Specifies the file path to the archive which should be indexed and loaded.
* @param extractArchives Specifies if the original H3 archives should be extracted to a separate folder.
*
* @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
*/
CArchiveLoader(std::string mountPoint, boost::filesystem::path archive);
CArchiveLoader(std::string mountPoint, bfs::path archive, bool extractArchives = false);
/// Interface implementation
/// @see ISimpleResourceLoader
@ -64,6 +67,10 @@ public:
std::string getMountPoint() const override;
void updateFilteredFiles(std::function<bool(const std::string &)> filter) const override {}
std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
/** Extracts one archive entry to the specified subfolder. Used for Video and Sound */
void extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, ArchiveEntry entry);
/** Extracts one archive entry to the specified subfolder. Used for Images, Sprites, etc */
void extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry);
private:
/**
@ -88,12 +95,18 @@ private:
void initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream);
/** The file path to the archive which is scanned and indexed. */
boost::filesystem::path archive;
bfs::path archive;
std::string mountPoint;
/** Holds all entries of the archive file. An entry can be accessed via the entry name. **/
std::unordered_map<ResourceID, ArchiveEntry> entries;
/** Specifies if Original H3 archives should be extracted to a separate folder **/
bool extractArchives;
};
/** Constructs the file path for the extracted file. Creates the subfolder hierarchy aswell **/
bfs::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName);
VCMI_LIB_NAMESPACE_END

View File

@ -26,9 +26,10 @@ VCMI_LIB_NAMESPACE_BEGIN
std::map<std::string, ISimpleResourceLoader*> CResourceHandler::knownLoaders = std::map<std::string, ISimpleResourceLoader*>();
CResourceHandler CResourceHandler::globalResourceHandler;
CFilesystemGenerator::CFilesystemGenerator(std::string prefix):
CFilesystemGenerator::CFilesystemGenerator(std::string prefix, bool extractArchives):
filesystem(new CFilesystemList()),
prefix(prefix)
prefix(prefix),
extractArchives(extractArchives)
{
}
@ -105,7 +106,7 @@ void CFilesystemGenerator::loadArchive(const std::string &mountPoint, const Json
std::string URI = prefix + config["path"].String();
auto filename = CResourceHandler::get("initial")->getResourceName(ResourceID(URI, archiveType));
if (filename)
filesystem->addLoader(new CArchiveLoader(mountPoint, *filename), false);
filesystem->addLoader(new CArchiveLoader(mountPoint, *filename, extractArchives), false);
}
void CFilesystemGenerator::loadJsonMap(const std::string &mountPoint, const JsonNode & config)
@ -200,13 +201,13 @@ ISimpleResourceLoader * CResourceHandler::get(std::string identifier)
return knownLoaders.at(identifier);
}
void CResourceHandler::load(const std::string &fsConfigURI)
void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives)
{
auto fsConfigData = get("initial")->load(ResourceID(fsConfigURI, EResType::TEXT))->readAll();
const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
addFilesystem("data", "core", createFileSystem("", fsConfig["filesystem"]));
addFilesystem("data", "core", createFileSystem("", fsConfig["filesystem"], extractArchives));
}
void CResourceHandler::addFilesystem(const std::string & parent, const std::string & identifier, ISimpleResourceLoader * loader)
@ -246,9 +247,9 @@ bool CResourceHandler::removeFilesystem(const std::string & parent, const std::s
return true;
}
ISimpleResourceLoader * CResourceHandler::createFileSystem(const std::string & prefix, const JsonNode &fsConfig)
ISimpleResourceLoader * CResourceHandler::createFileSystem(const std::string & prefix, const JsonNode &fsConfig, bool extractArchives)
{
CFilesystemGenerator generator(prefix);
CFilesystemGenerator generator(prefix, extractArchives);
generator.loadConfig(fsConfig);
return generator.getFilesystem();
}

View File

@ -36,7 +36,8 @@ class DLL_LINKAGE CFilesystemGenerator
TLoadFunctorMap genFunctorMap();
public:
/// prefix = prefix that will be given to file entries in all nodes of this filesystem
CFilesystemGenerator(std::string prefix);
/// extractArchives = Specifies if Original H3 archives should be extracted to a separate folder
CFilesystemGenerator(std::string prefix, bool extractArchives = false);
/// loads configuration from json
/// config - configuration to load, using format of "filesystem" entry in config/filesystem.json
@ -44,6 +45,9 @@ public:
/// returns generated filesystem
CFilesystemList * getFilesystem();
/** Specifies if Original H3 archives should be extracted to a separate folder **/
bool extractArchives;
};
/**
@ -81,7 +85,7 @@ public:
* Will load all filesystem data from Json data at this path (normally - config/filesystem.json)
* @param fsConfigURI - URI from which data will be loaded
*/
static void load(const std::string & fsConfigURI);
static void load(const std::string & fsConfigURI, bool extractArchives = false);
/**
* @brief addFilesystem adds filesystem into global resource loader
@ -104,7 +108,7 @@ public:
* @param fsConfig - configuration to load
* @return generated filesystem that contains all config entries
*/
static ISimpleResourceLoader * createFileSystem(const std::string &prefix, const JsonNode & fsConfig);
static ISimpleResourceLoader * createFileSystem(const std::string &prefix, const JsonNode & fsConfig, bool extractArchives = false);
~CResourceHandler() = default;
private:

View File

@ -422,7 +422,7 @@ void CQuest::getCompletionText(MetaString &iwText, std::vector<Component> &compo
}
}
void CQuest::addArtifactID(ui16 id)
void CQuest::addArtifactID(ArtifactID id)
{
m5arts.push_back(id);
++artifactsRequirements[id];
@ -474,7 +474,7 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
break;
case MISSION_ART:
//todo: ban artifacts
handler.serializeIdArray<ui16, ArtifactID>("artifacts", m5arts);
handler.serializeIdArray<ArtifactID>("artifacts", m5arts);
break;
case MISSION_ARMY:
{

View File

@ -21,7 +21,7 @@ class CGCreature;
class DLL_LINKAGE CQuest final
{
mutable std::unordered_map<ui16, unsigned> artifactsRequirements; // artifact ID -> required count
mutable std::unordered_map<ArtifactID, unsigned, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
public:
enum Emission {MISSION_NONE = 0, MISSION_LEVEL = 1, MISSION_PRIMARY_STAT = 2, MISSION_KILL_HERO = 3, MISSION_KILL_CREATURE = 4,
@ -36,7 +36,7 @@ public:
ui32 m13489val;
std::vector<ui32> m2stats;
std::vector<ui16> m5arts; // artifact IDs. Add IDs through addArtifactID(), not directly to the field.
std::vector<ArtifactID> m5arts; // artifact IDs. Add IDs through addArtifactID(), not directly to the field.
std::vector<CStackBasicDescriptor> m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant
std::vector<ui32> m7resources; //TODO: use resourceset?
@ -62,7 +62,7 @@ public:
virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
virtual void completeQuest (const CGHeroInstance * h) const {};
virtual void addReplacements(MetaString &out, const std::string &base) const;
void addArtifactID(ui16 id);
void addArtifactID(ArtifactID id);
bool operator== (const CQuest & quest) const
{

View File

@ -870,15 +870,15 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
{
const int artmask = map->version == EMapFormat::ROE ? 0xff : 0xffff;
int aid;
ArtifactID aid;
if(map->version == EMapFormat::ROE)
{
aid = reader.readUInt8();
aid = ArtifactID(reader.readUInt8());
}
else
{
aid = reader.readUInt16();
aid = ArtifactID(reader.readUInt16());
}
bool isArt = aid != artmask;
@ -1207,7 +1207,7 @@ void CMapLoaderH3M::readObjects()
case Obj::RANDOM_RELIC_ART:
case Obj::SPELL_SCROLL:
{
int artID = ArtifactID::NONE; //random, set later
auto artID = ArtifactID::NONE; //random, set later
int spellID = -1;
auto art = new CGArtifact();
nobj = art;
@ -1222,7 +1222,7 @@ void CMapLoaderH3M::readObjects()
else if(objTempl->id == Obj::ARTIFACT)
{
//specific artifact
artID = objTempl->subid;
artID = ArtifactID(objTempl->subid);
}
art->storedArtifact = CArtifactInstance::createArtifact(map, artID, spellID);
@ -1754,7 +1754,7 @@ CGSeerHut * CMapLoaderH3M::readSeerHut()
else
{
//RoE
int artID = reader.readUInt8();
auto artID = ArtifactID(reader.readUInt8());
if (artID != 255)
{
//not none quest
@ -1886,7 +1886,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard)
int artNumber = reader.readUInt8();
for(int yy = 0; yy < artNumber; ++yy)
{
int artid = reader.readUInt16();
auto artid = ArtifactID(reader.readUInt16());
guard->quest->addArtifactID(artid);
map->allowedArtifact[artid] = false; //these are unavailable for random generation
}

View File

@ -1152,7 +1152,7 @@ void CMapLoaderJson::MapObjectLoader::configure()
if(auto art = dynamic_cast<CGArtifact *>(instance))
{
int artID = ArtifactID::NONE;
auto artID = ArtifactID::NONE;
int spellID = -1;
if(art->ID == Obj::SPELL_SCROLL)
@ -1168,7 +1168,7 @@ void CMapLoaderJson::MapObjectLoader::configure()
else if(art->ID == Obj::ARTIFACT)
{
//specific artifact
artID = art->subID;
artID = ArtifactID(art->subID);
}
art->storedArtifact = CArtifactInstance::createArtifact(owner->map, artID, spellID);

View File

@ -505,10 +505,10 @@ std::unique_ptr<spells::Mechanics> CSpell::battleMechanics(const spells::IBattle
void CSpell::registerIcons(const IconRegistar & cb) const
{
cb(getIndex(), "SPELLS", iconBook);
cb(getIndex()+1, "SPELLINT", iconEffect);
cb(getIndex(), "SPELLBON", iconScenarioBonus);
cb(getIndex(), "SPELLSCR", iconScroll);
cb(getIndex(), 0, "SPELLS", iconBook);
cb(getIndex()+1, 0, "SPELLINT", iconEffect);
cb(getIndex(), 0, "SPELLBON", iconScenarioBonus);
cb(getIndex(), 0, "SPELLSCR", iconScroll);
}
void CSpell::updateFrom(const JsonNode & data)

View File

@ -19,7 +19,7 @@
#include "../lib/filesystem/ISimpleResourceLoader.h"
#include "../lib/JsonNode.h"
#include "../lib/CRandomGenerator.h"
#include "../lib/VCMIDirs.h"
typedef std::map<size_t, std::vector<JsonNode>> source_map;
@ -686,6 +686,39 @@ std::shared_ptr<QImage> Animation::getImage(size_t frame, size_t group, bool ver
return nullptr;
}
void Animation::exportBitmaps(const QDir & path) const
{
if(images.empty())
{
logGlobal->error("Nothing to export, animation is empty");
return;
}
QString actualPath = path.absolutePath() + "/SPRITES/" + QString::fromStdString(name);
QDir().mkdir(actualPath);
size_t counter = 0;
for(const auto& groupPair : images)
{
size_t group = groupPair.first;
for(const auto& imagePair : groupPair.second)
{
size_t frame = imagePair.first;
const auto img = imagePair.second;
QString filename = QString("%1_%2_%3.png").arg(QString::fromStdString(name)).arg(group).arg(frame);
QString filePath = actualPath + "/" + filename;
img->save(filePath, "PNG");
counter++;
}
}
logGlobal->info("Exported %d frames to %s", counter, actualPath.toStdString());
}
void Animation::load()
{
for (auto & elem : source)
@ -774,4 +807,4 @@ void Animation::createFlippedGroup(const size_t sourceGroup, const size_t target
auto image = getImage(frame, targetGroup);
*image = image->transformed(QTransform::fromScale(1, -1));
}
}
}

View File

@ -83,6 +83,8 @@ public:
void load (size_t frame, size_t group = 0);
void unload(size_t frame, size_t group = 0);
void exportBitmaps(const QDir & path) const;
//total count of frames in group (including not loaded)
size_t size(size_t group = 0) const;

View File

@ -24,6 +24,7 @@ set(editor_SRCS
inspector/messagewidget.cpp
inspector/rewardswidget.cpp
inspector/questwidget.cpp
resourceExtractor/ResourceConverter.cpp
)
set(editor_HEADERS
@ -51,6 +52,7 @@ set(editor_HEADERS
inspector/messagewidget.h
inspector/rewardswidget.h
inspector/questwidget.h
resourceExtractor/ResourceConverter.h
)
set(editor_FORMS
@ -110,7 +112,7 @@ if(WIN32)
endif()
if(APPLE)
# This makes Xcode project prettier by moving vcmilauncher_autogen directory into vcmiclient subfolder
# This makes Xcode project prettier by moving mapeditor_autogen directory into vcmiclient subfolder
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor)
endif()

View File

@ -310,11 +310,13 @@ std::shared_ptr<Animation> Graphics::getAnimation(const std::shared_ptr<const Ob
return ret;
}
void Graphics::addImageListEntry(size_t index, const std::string & listName, const std::string & imageName)
void Graphics::addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName)
{
if (!imageName.empty())
{
JsonNode entry;
if(group != 0)
entry["group"].Integer() = group;
entry["frame"].Integer() = index;
entry["file"].String() = imageName;
@ -324,7 +326,7 @@ void Graphics::addImageListEntry(size_t index, const std::string & listName, con
void Graphics::addImageListEntries(const EntityService * service)
{
auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3);
auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3, _4);
auto loopCb = [&](const Entity * entity, bool & stop)
{

View File

@ -27,7 +27,7 @@ class JsonNode;
/// Handles fonts, hero images, town images, various graphics
class Graphics
{
void addImageListEntry(size_t index, const std::string & listName, const std::string & imageName);
void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
void addImageListEntries(const EntityService * service);

BIN
mapeditor/icons/brush-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
mapeditor/icons/brush-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
mapeditor/icons/brush-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
mapeditor/icons/brush-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -134,7 +134,7 @@ QString QuestWidget::commitChanges()
return QString("N/A");
case CQuest::Emission::MISSION_ART:
seerhut.quest->m5arts.clear();
seerhut.quest->m5arts.push_back(ui->targetId->currentIndex());
seerhut.quest->m5arts.push_back(ArtifactID(ui->targetId->currentIndex()));
//TODO: support multiple artifacts
return ui->targetId->currentText();
case CQuest::Emission::MISSION_ARMY:

View File

@ -88,57 +88,94 @@ void MainWindow::saveUserSettings()
s.setValue(mainWindowPositionSetting, pos());
}
MainWindow::MainWindow(QWidget *parent) :
void MainWindow::parseCommandLine(ExtractionOptions & extractionOptions)
{
QCommandLineParser parser;
parser.addHelpOption();
parser.addPositionalArgument("map", QCoreApplication::translate("main", "Filepath of the map to open."));
parser.addOptions({
{"e", QCoreApplication::translate("main", "Extract original H3 archives into a separate folder.")},
{"s", QCoreApplication::translate("main", "From an extracted archive, it Splits TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44 into individual PNG's.")},
{"c", QCoreApplication::translate("main", "From an extracted archive, Converts single Images (found in Images folder) from .pcx to png.")},
{"d", QCoreApplication::translate("main", "Delete original files, for the ones splitted / converted.")},
});
parser.process(qApp->arguments());
const QStringList positionalArgs = parser.positionalArguments();
if(!positionalArgs.isEmpty())
mapFilePath = positionalArgs.at(0);
extractionOptions = {
parser.isSet("e"), {
parser.isSet("s"),
parser.isSet("c"),
parser.isSet("d")}};
}
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
controller(this)
{
for(auto & string : VCMIDirs::get().dataPaths())
QDir::addSearchPath("icons", pathToQString(string / "mapeditor" / "icons"));
QDir::addSearchPath("icons", pathToQString(VCMIDirs::get().userDataPath() / "mapeditor" / "icons"));
ui->setupUi(this);
loadUserSettings(); //For example window size
setTitle();
// Set current working dir to executable folder.
// This is important on Mac for relative paths to work inside DMG.
QDir::setCurrent(QApplication::applicationDirPath());
ExtractionOptions extractionOptions;
parseCommandLine(extractionOptions);
//configure logging
const boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Editor_log.txt";
console = new CConsoleHandler();
logConfig = new CBasicLogConfigurator(logPath, console);
logConfig->configureDefault();
logGlobal->info("The log file will be saved to %s", logPath);
//init
preinitDLL(::console);
preinitDLL(::console, false, extractionOptions.extractArchives);
settings.init();
// Initialize logging based on settings
logConfig->configure();
logGlobal->debug("settings = %s", settings.toJsonNode().toJson());
// Some basic data validation to produce better error messages in cases of incorrect install
auto testFile = [](std::string filename, std::string message) -> bool
{
if (CResourceHandler::get()->existsResource(ResourceID(filename)))
return true;
logGlobal->error("Error: %s was not found!", message);
return false;
};
if(!testFile("DATA/HELP.TXT", "Heroes III data") ||
!testFile("MODS/VCMI/MOD.JSON", "VCMI data"))
if (!testFile("DATA/HELP.TXT", "Heroes III data") ||
!testFile("MODS/VCMI/MOD.JSON", "VCMI data"))
{
QApplication::quit();
}
conf.init();
logGlobal->info("Loading settings");
init();
graphics = new Graphics(); // should be before curh->init()
graphics->load();//must be after Content loading but should be in main thread
if (extractionOptions.extractArchives)
ResourceConverter::convertExtractedResourceFiles(extractionOptions.conversionOptions);
ui->mapView->setScene(controller.scene(0));
ui->mapView->setController(&controller);
@ -167,8 +204,8 @@ MainWindow::MainWindow(QWidget *parent) :
show();
//Load map from command line
if(qApp->arguments().size() >= 2)
openMap(qApp->arguments().at(1));
if(!mapFilePath.isEmpty())
openMap(mapFilePath);
}
MainWindow::~MainWindow()

Some files were not shown because too many files have changed in this diff Show More