forked from mirror/openmw-tes3mp
Merge pull request #366 from TES3MP/0.6.2 while resolving conflicts, 2nd try
Conflicts: apps/openmw-mp/Networking.cpp apps/openmw-mp/Script/Functions/Miscellaneous.cpp apps/openmw-mp/Script/Functions/Miscellaneous.hpp apps/openmw/mwmp/GUI/GUIChat.cpp
This commit is contained in:
commit
b6a7377692
13 changed files with 91 additions and 50 deletions
|
@ -34,6 +34,7 @@ using namespace std;
|
||||||
Networking *Networking::sThis = nullptr;
|
Networking *Networking::sThis = nullptr;
|
||||||
|
|
||||||
static int currentMpNum = 0;
|
static int currentMpNum = 0;
|
||||||
|
static bool pluginEnforcementState = true;
|
||||||
|
|
||||||
Networking::Networking(RakNet::RakPeerInterface *peer) : mclient(nullptr)
|
Networking::Networking(RakNet::RakPeerInterface *peer) : mclient(nullptr)
|
||||||
{
|
{
|
||||||
|
@ -226,14 +227,16 @@ bool Networking::update(RakNet::Packet *packet)
|
||||||
packetPreInit.SetSendStream(&bs);
|
packetPreInit.SetSendStream(&bs);
|
||||||
|
|
||||||
// If the loop above was broken, then the client's plugins do not match the server's
|
// If the loop above was broken, then the client's plugins do not match the server's
|
||||||
if (plugin != plugins.end())
|
if (pluginEnforcementState && plugin != plugins.end())
|
||||||
{
|
{
|
||||||
|
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "%s was not allowed to connect due to incompatible plugins", packet->systemAddress.ToString());
|
||||||
packetPreInit.setChecksums(&samples);
|
packetPreInit.setChecksums(&samples);
|
||||||
packetPreInit.Send(packet->systemAddress);
|
packetPreInit.Send(packet->systemAddress);
|
||||||
peer->CloseConnection(packet->systemAddress, true);
|
peer->CloseConnection(packet->systemAddress, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "%s was allowed to connect", packet->systemAddress.ToString());
|
||||||
PacketPreInit::PluginContainer tmp;
|
PacketPreInit::PluginContainer tmp;
|
||||||
packetPreInit.setChecksums(&tmp);
|
packetPreInit.setChecksums(&tmp);
|
||||||
packetPreInit.Send(packet->systemAddress);
|
packetPreInit.Send(packet->systemAddress);
|
||||||
|
@ -371,12 +374,21 @@ int Networking::incrementMpNum()
|
||||||
return currentMpNum;
|
return currentMpNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Networking::getPluginEnforcementState()
|
||||||
|
{
|
||||||
|
return pluginEnforcementState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Networking::setPluginEnforcementState(bool state)
|
||||||
|
{
|
||||||
|
pluginEnforcementState = state;
|
||||||
|
}
|
||||||
|
|
||||||
Networking &Networking::get()
|
Networking &Networking::get()
|
||||||
{
|
{
|
||||||
return *sThis;
|
return *sThis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Networking *Networking::getPtr()
|
Networking *Networking::getPtr()
|
||||||
{
|
{
|
||||||
return sThis;
|
return sThis;
|
||||||
|
|
|
@ -62,6 +62,9 @@ namespace mwmp
|
||||||
void setCurrentMpNum(int value);
|
void setCurrentMpNum(int value);
|
||||||
int incrementMpNum();
|
int incrementMpNum();
|
||||||
|
|
||||||
|
bool getPluginEnforcementState();
|
||||||
|
void setPluginEnforcementState(bool state);
|
||||||
|
|
||||||
MasterClient *getMasterClient();
|
MasterClient *getMasterClient();
|
||||||
void InitQuery(const std::string &queryAddr, unsigned short queryPort);
|
void InitQuery(const std::string &queryAddr, unsigned short queryPort);
|
||||||
void setServerPassword(std::string passw) noexcept;
|
void setServerPassword(std::string passw) noexcept;
|
||||||
|
|
|
@ -170,6 +170,14 @@ LuaState::LuaState()
|
||||||
mwmp::Networking::getPtr()->setCurrentMpNum(num);
|
mwmp::Networking::getPtr()->setCurrentMpNum(num);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lua->set_function("getPluginEnforcementState", []() {
|
||||||
|
return mwmp::Networking::getPtr()->getPluginEnforcementState();
|
||||||
|
});
|
||||||
|
|
||||||
|
lua->set_function("setPluginEnforcementState", [](bool state) {
|
||||||
|
mwmp::Networking::getPtr()->setPluginEnforcementState(state);
|
||||||
|
});
|
||||||
|
|
||||||
lua->set_function("getCaseInsensitiveFilename", [](const char *folderPath, const char *filename) {
|
lua->set_function("getCaseInsensitiveFilename", [](const char *folderPath, const char *filename) {
|
||||||
if (!boost::filesystem::exists(folderPath)) return "invalid";
|
if (!boost::filesystem::exists(folderPath)) return "invalid";
|
||||||
|
|
||||||
|
|
|
@ -390,21 +390,29 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mJumpState == JumpState_InAir)
|
if (!mCurrentJump.empty())
|
||||||
{
|
|
||||||
mAnimation->disable(mCurrentJump);
|
|
||||||
mCurrentJump = jumpAnimName;
|
|
||||||
if (mAnimation->hasAnimation("jump"))
|
|
||||||
mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false,
|
|
||||||
1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
mAnimation->disable(mCurrentJump);
|
mAnimation->disable(mCurrentJump);
|
||||||
mCurrentJump.clear();
|
mCurrentJump.clear();
|
||||||
if (mAnimation->hasAnimation("jump"))
|
}
|
||||||
|
|
||||||
|
if(mJumpState == JumpState_InAir)
|
||||||
|
{
|
||||||
|
if (mAnimation->hasAnimation(jumpAnimName))
|
||||||
|
{
|
||||||
|
mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false,
|
||||||
|
1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul);
|
||||||
|
mCurrentJump = jumpAnimName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mJumpState == JumpState_Landing)
|
||||||
|
{
|
||||||
|
if (mAnimation->hasAnimation(jumpAnimName))
|
||||||
|
{
|
||||||
mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
|
mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
|
||||||
1.0f, "loop stop", "stop", 0.0f, 0);
|
1.0f, "loop stop", "stop", 0.0f, 0);
|
||||||
|
mCurrentJump = jumpAnimName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1825,7 +1833,6 @@ void CharacterController::update(float duration)
|
||||||
mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
|
mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
|
||||||
isrunning = isrunning && mHasMovedInXY;
|
isrunning = isrunning && mHasMovedInXY;
|
||||||
|
|
||||||
|
|
||||||
// advance athletics
|
// advance athletics
|
||||||
if(mHasMovedInXY && mPtr == getPlayer())
|
if(mHasMovedInXY && mPtr == getPlayer())
|
||||||
{
|
{
|
||||||
|
@ -1908,7 +1915,7 @@ void CharacterController::update(float duration)
|
||||||
vec.y() *= factor;
|
vec.y() *= factor;
|
||||||
vec.z() = 0.0f;
|
vec.z() = 0.0f;
|
||||||
}
|
}
|
||||||
else if(vec.z() > 0.0f && mJumpState == JumpState_None)
|
else if(vec.z() > 0.0f && mJumpState != JumpState_InAir)
|
||||||
{
|
{
|
||||||
// Started a jump.
|
// Started a jump.
|
||||||
float z = cls.getJump(mPtr);
|
float z = cls.getJump(mPtr);
|
||||||
|
@ -1980,7 +1987,8 @@ void CharacterController::update(float duration)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
jumpstate = JumpState_None;
|
jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None;
|
||||||
|
|
||||||
vec.z() = 0.0f;
|
vec.z() = 0.0f;
|
||||||
|
|
||||||
inJump = false;
|
inJump = false;
|
||||||
|
@ -2010,9 +2018,15 @@ void CharacterController::update(float duration)
|
||||||
else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()))
|
else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()))
|
||||||
{
|
{
|
||||||
if(rot.z() > 0.0f)
|
if(rot.z() > 0.0f)
|
||||||
|
{
|
||||||
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
|
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
|
||||||
|
mAnimation->disable(mCurrentJump);
|
||||||
|
}
|
||||||
else if(rot.z() < 0.0f)
|
else if(rot.z() < 0.0f)
|
||||||
|
{
|
||||||
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
|
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
|
||||||
|
mAnimation->disable(mCurrentJump);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ namespace mwmp
|
||||||
{
|
{
|
||||||
// Give keyboard focus to the combo box whenever the console is
|
// Give keyboard focus to the combo box whenever the console is
|
||||||
// turned on
|
// turned on
|
||||||
SetEditState(0);
|
setEditState(0);
|
||||||
windowState = CHAT_ENABLED;
|
windowState = CHAT_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ namespace mwmp
|
||||||
// Apparently, hidden widgets can retain key focus
|
// Apparently, hidden widgets can retain key focus
|
||||||
// Remove for MyGUI 3.2.2
|
// Remove for MyGUI 3.2.2
|
||||||
windowState = CHAT_DISABLED;
|
windowState = CHAT_DISABLED;
|
||||||
SetEditState(0);
|
setEditState(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GUIChat::exit()
|
bool GUIChat::exit()
|
||||||
|
@ -102,7 +102,7 @@ namespace mwmp
|
||||||
if (cm.empty())
|
if (cm.empty())
|
||||||
{
|
{
|
||||||
mCommandLine->setCaption("");
|
mCommandLine->setCaption("");
|
||||||
SetEditState(false);
|
setEditState(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +140,7 @@ namespace mwmp
|
||||||
mEditString.clear();
|
mEditString.clear();
|
||||||
|
|
||||||
mCommandLine->setCaption("");
|
mCommandLine->setCaption("");
|
||||||
SetEditState(false);
|
setEditState(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIChat::onResChange(int width, int height)
|
void GUIChat::onResChange(int width, int height)
|
||||||
|
@ -226,7 +225,7 @@ namespace mwmp
|
||||||
{
|
{
|
||||||
case CHAT_DISABLED:
|
case CHAT_DISABLED:
|
||||||
this->mMainWidget->setVisible(false);
|
this->mMainWidget->setVisible(false);
|
||||||
SetEditState(false);
|
setEditState(false);
|
||||||
break;
|
break;
|
||||||
case CHAT_ENABLED:
|
case CHAT_ENABLED:
|
||||||
this->mMainWidget->setVisible(true);
|
this->mMainWidget->setVisible(true);
|
||||||
|
@ -237,7 +236,7 @@ namespace mwmp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIChat::SetEditState(bool state)
|
void GUIChat::setEditState(bool state)
|
||||||
{
|
{
|
||||||
editState = state;
|
editState = state;
|
||||||
mCommandLine->setVisible(editState);
|
mCommandLine->setVisible(editState);
|
||||||
|
@ -249,17 +248,17 @@ namespace mwmp
|
||||||
{
|
{
|
||||||
if (windowState == CHAT_DISABLED)
|
if (windowState == CHAT_DISABLED)
|
||||||
return;
|
return;
|
||||||
else if (windowState == CHAT_HIDDENMODE)
|
|
||||||
|
if (!mCommandLine->getVisible())
|
||||||
|
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Opening chat.");
|
||||||
|
|
||||||
|
if (windowState == CHAT_HIDDENMODE)
|
||||||
{
|
{
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
curTime = 0;
|
curTime = 0;
|
||||||
editState = true;
|
|
||||||
}
|
}
|
||||||
else // CHAT_ENABLED
|
|
||||||
editState = true;
|
|
||||||
|
|
||||||
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Opening chat.");
|
setEditState(true);
|
||||||
SetEditState(editState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIChat::keyPress(MyGUI::Widget *_sender, MyGUI::KeyCode key, MyGUI::Char _char)
|
void GUIChat::keyPress(MyGUI::Widget *_sender, MyGUI::KeyCode key, MyGUI::Char _char)
|
||||||
|
@ -294,14 +293,14 @@ namespace mwmp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIChat::Update(float dt)
|
void GUIChat::update(float dt)
|
||||||
{
|
{
|
||||||
if (windowState == CHAT_HIDDENMODE && !editState && isVisible())
|
if (windowState == CHAT_HIDDENMODE && !editState && isVisible())
|
||||||
{
|
{
|
||||||
curTime += dt;
|
curTime += dt;
|
||||||
if (curTime >= delay)
|
if (curTime >= delay)
|
||||||
{
|
{
|
||||||
SetEditState(false);
|
setEditState(false);
|
||||||
this->mMainWidget->setVisible(false);
|
this->mMainWidget->setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace mwmp
|
||||||
void pressedSay(); // switch chat focus (if chat mode != CHAT_DISABLED)
|
void pressedSay(); // switch chat focus (if chat mode != CHAT_DISABLED)
|
||||||
void setDelay(float delay);
|
void setDelay(float delay);
|
||||||
|
|
||||||
void Update(float dt);
|
void update(float dt);
|
||||||
|
|
||||||
void setCaption(const std::string &str);
|
void setCaption(const std::string &str);
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ namespace mwmp
|
||||||
|
|
||||||
void acceptCommand(MyGUI::EditBox* _sender);
|
void acceptCommand(MyGUI::EditBox* _sender);
|
||||||
|
|
||||||
void SetEditState(bool state);
|
void setEditState(bool state);
|
||||||
|
|
||||||
int windowState;
|
int windowState;
|
||||||
bool editState;
|
bool editState;
|
||||||
|
|
|
@ -247,7 +247,7 @@ bool mwmp::GUIController:: hasFocusedElement()
|
||||||
void mwmp::GUIController::update(float dt)
|
void mwmp::GUIController::update(float dt)
|
||||||
{
|
{
|
||||||
if (mChat != nullptr)
|
if (mChat != nullptr)
|
||||||
mChat->Update(dt);
|
mChat->update(dt);
|
||||||
|
|
||||||
// Make sure we read the pressed button without resetting it, because it may also get
|
// Make sure we read the pressed button without resetting it, because it may also get
|
||||||
// checked somewhere else
|
// checked somewhere else
|
||||||
|
|
|
@ -462,15 +462,18 @@ void WorldEvent::runConsoleCommands(MWWorld::CellStore* cellStore)
|
||||||
windowManager->setConsolePtr(static_cast<LocalPlayer*>(player)->getPlayerPtr());
|
windowManager->setConsolePtr(static_cast<LocalPlayer*>(player)->getPlayerPtr());
|
||||||
windowManager->executeCommandInConsole(consoleCommand);
|
windowManager->executeCommandInConsole(consoleCommand);
|
||||||
}
|
}
|
||||||
else if (player != 0)
|
else
|
||||||
{
|
{
|
||||||
player = PlayerList::getPlayer(guid);
|
player = PlayerList::getPlayer(worldObject.guid);
|
||||||
|
|
||||||
|
if (player != 0)
|
||||||
|
{
|
||||||
LOG_APPEND(Log::LOG_VERBOSE, "-- running on player %s", player->npc.mName.c_str());
|
LOG_APPEND(Log::LOG_VERBOSE, "-- running on player %s", player->npc.mName.c_str());
|
||||||
windowManager->setConsolePtr(static_cast<DedicatedPlayer*>(player)->getPtr());
|
windowManager->setConsolePtr(static_cast<DedicatedPlayer*>(player)->getPtr());
|
||||||
windowManager->executeCommandInConsole(consoleCommand);
|
windowManager->executeCommandInConsole(consoleCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_APPEND(Log::LOG_VERBOSE, "-- running on cellRef: %s, %i, %i", worldObject.refId.c_str(), worldObject.refNumIndex, worldObject.mpNum);
|
LOG_APPEND(Log::LOG_VERBOSE, "-- running on cellRef: %s, %i, %i", worldObject.refId.c_str(), worldObject.refNumIndex, worldObject.mpNum);
|
||||||
|
|
|
@ -705,7 +705,7 @@ namespace MWPhysics
|
||||||
if (physFramerate > 0)
|
if (physFramerate > 0)
|
||||||
{
|
{
|
||||||
mPhysicsDt = 1.f / physFramerate;
|
mPhysicsDt = 1.f / physFramerate;
|
||||||
std::cerr << "Warning: physics framerate was overriden (a new value is " << physFramerate << ")." << std::endl;
|
std::cerr << "Warning: physics framerate was overridden (a new value is " << physFramerate << ")." << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,12 +153,12 @@ void NiPixelData::read(NIFStream *nif)
|
||||||
// Unknown
|
// Unknown
|
||||||
nif->skip(12);
|
nif->skip(12);
|
||||||
|
|
||||||
mips = nif->getInt();
|
numberOfMipmaps = nif->getInt();
|
||||||
|
|
||||||
// Bytes per pixel, should be bpp * 8
|
// Bytes per pixel, should be bpp * 8
|
||||||
/* int bytes = */ nif->getInt();
|
/* int bytes = */ nif->getInt();
|
||||||
|
|
||||||
for(int i=0; i<mips; i++)
|
for(int i=0; i<numberOfMipmaps; i++)
|
||||||
{
|
{
|
||||||
// Image size and offset in the following data field
|
// Image size and offset in the following data field
|
||||||
Mipmap m;
|
Mipmap m;
|
||||||
|
|
|
@ -115,7 +115,7 @@ public:
|
||||||
Format fmt;
|
Format fmt;
|
||||||
|
|
||||||
unsigned int rmask, gmask, bmask, amask;
|
unsigned int rmask, gmask, bmask, amask;
|
||||||
int bpp, mips;
|
int bpp, numberOfMipmaps;
|
||||||
|
|
||||||
struct Mipmap
|
struct Mipmap
|
||||||
{
|
{
|
||||||
|
|
|
@ -268,7 +268,7 @@ namespace NifOsg
|
||||||
osg::ref_ptr<SceneUtil::Skeleton> skel = new SceneUtil::Skeleton;
|
osg::ref_ptr<SceneUtil::Skeleton> skel = new SceneUtil::Skeleton;
|
||||||
|
|
||||||
osg::Group* root = created->asGroup();
|
osg::Group* root = created->asGroup();
|
||||||
if (root && root->getDataVariance() == osg::Object::STATIC)
|
if (root && root->getDataVariance() == osg::Object::STATIC && !root->asTransform())
|
||||||
{
|
{
|
||||||
skel->setStateSet(root->getStateSet());
|
skel->setStateSet(root->getStateSet());
|
||||||
skel->setName(root->getName());
|
skel->setName(root->getName());
|
||||||
|
@ -440,7 +440,7 @@ namespace NifOsg
|
||||||
// The Root node can be created as a Group if no transformation is required.
|
// The Root node can be created as a Group if no transformation is required.
|
||||||
// This takes advantage of the fact root nodes can't have additional controllers
|
// This takes advantage of the fact root nodes can't have additional controllers
|
||||||
// loaded from an external .kf file (original engine just throws "can't find node" errors if you try).
|
// loaded from an external .kf file (original engine just throws "can't find node" errors if you try).
|
||||||
if (!nifNode->parent && nifNode->controller.empty())
|
if (!nifNode->parent && nifNode->controller.empty() && nifNode->trafo.isIdentity())
|
||||||
{
|
{
|
||||||
node = new osg::Group;
|
node = new osg::Group;
|
||||||
dataVariance = osg::Object::STATIC;
|
dataVariance = osg::Object::STATIC;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Copyright 2015 Alexandre Moine <nobrakal@gmail.com> -->
|
<!--
|
||||||
<application>
|
Copyright 2015 Alexandre Moine <nobrakal@gmail.com>
|
||||||
<id type="desktop">openmw.desktop</id>
|
Copyright 2017 Bret Curtis <psi29a@gmail.com>
|
||||||
|
-->
|
||||||
|
<component>
|
||||||
|
<id>org.openmw.desktop</id>
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license> GPL-3.0 and MIT and zlib</project_license>
|
<project_license> GPL-3.0 and MIT</project_license>
|
||||||
<name>OpenMW</name>
|
<name>OpenMW</name>
|
||||||
<summary>Unofficial open source engine re-implementation of the game Morrowind</summary>
|
<summary>Unofficial open source engine re-implementation of the game Morrowind</summary>
|
||||||
<description>
|
<description>
|
||||||
|
@ -32,8 +35,7 @@
|
||||||
<caption>Vivec seen from Ebonheart on OpenMW</caption>
|
<caption>Vivec seen from Ebonheart on OpenMW</caption>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<updatecontact>nobrakal@gmail.com</updatecontact>
|
|
||||||
<url type="homepage">https://openmw.org</url>
|
<url type="homepage">https://openmw.org</url>
|
||||||
<url type="bugtracker">https://bugs.openmw.org/</url>
|
<url type="bugtracker">https://bugs.openmw.org/</url>
|
||||||
<url type="faq">https://openmw.org/faq/</url>
|
<url type="faq">https://openmw.org/faq/</url>
|
||||||
</application>
|
</component>
|
||||||
|
|
Loading…
Reference in a new issue