Compare commits

..

2 Commits

@ -4,7 +4,7 @@ os:
osx_image: xcode9.4
language: cpp
sudo: required
dist: xenial
dist: trusty
branches:
only:
- master
@ -15,18 +15,18 @@ env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: 1QK0yVyoOB+gf2I7XzvhXu9w/5lq4stBXIwJbVCTjz4Q4XVHCosURaW1MAgKzMrPnbFEwjyn5uQ8BwsvvfkuN1AZD0YXITgc7gyI+J1wQ/p/ljxRxglakU6WEgsTs2J5z9UmGac4YTXg+quK7YP3rv+zuGim2I2rhzImejyzp0Ym3kRCnNcy+SGBsiRaevRJMe00Ch8zGAbEhduQGeSoS6W0rcu02DNlQKiq5NktWsXR+TWWWVfIeIlQR/lbPsCd0pdxMaMv2QCY0rVbwrYxWJwr/Qe45dAdWp+8/C3PbXpeMSGxlLa33nJNX4Lf/djxbjm8KWk6edaXPajrjR/0iwcpwq0jg2Jt6XfEdnJt35F1gpXlc04sxStjG45uloOKCFYT0wdhIO1Lq+hDP54wypQl+JInd5qC001O7pwhVxO36EgKWqo8HD+BqGDBwsNj2engy9Qcp3wO6G0rLBPB3CrZsk9wrHVv5cSiQSLMhId3Xviu3ZI2qEDA+kgTvxrKrsnMj4bILVCyG5Ka2Mj22wIDW9e8oIab9oTdujax3DTN1GkD6QuOAGzwDsNwGASsgfoeZ+FUhgM75RlBWGMilgkmnF7EJ0oAXLEpjtABnEr2d4qHv+y08kOuTDBLB9ExzCIj024dYYYNLZrqPKx0ncHuCMG2QNj2aJAJEZtj1rQ=
- secure: NZmvVuA0O9NJXVQ12tXQZHDJC2mbFgYNFcsicw0DgW1It2Nk5hxIkF0pfu4/Z59mhQuOPgRVjl5b0FKy2Axh0gkWc1DJEXGwNaiW5lpTMNWR1LJG5rxa8LrDUpFkycpbzfAFuTUZu5z3iYVv64XzELvBuqNGhPMu1LeBnrlech0jFNjkR9p5qtJGWb8zYcPMCC57rig8a9g1ABoVYS6UXjrKpx0946ZLRsE5ukc9pXsypGwPmOMyfzZkxxzIqFaxoE5JIEdaJTWba/6Za315ozYYIi/N35ROI1YAv5GHRe/Iw9XAa4vQpbDzjM7ZSsZdTvvQsSU598gD2xC6jFUKSrpW6GZKwM2x236fZLGnOk5Uw7DUbG+AwpcEmxBwoy9PjBl9ZF3tJykI0gROewCy8MODhdsVMKr1HGIMVBIJySm/RnNqtoDbYV8mYnSl5b8rwJiCajoiR8Zuv4CIfGneeH1a3DOQDPH/qkDsU6ilzF4ANsBlMUUpgY653KBMBmTlNuVZSH527tnD7Fg6JgHVuSQkTbRa1vSkR7Zcre604RZcAoaEdbX3bhVDasPPghU/I742L0RH3oQNlR09pPBDZ8kG7ydl4aPHwpCWnvXNM1vgxtGvnYLztwrse7IoaRXRYiMFmrso78WhMWUDKgvY4wV9aeUu0DtnMezZVIQwCKg=
addons:
apt:
sources:
- sourceline: 'ppa:openmw/openmw'
- sourceline: 'ppa:rakhimov/boost'
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.8
packages: [
# Dev
cmake, clang-6.0, libunshield-dev, libtinyxml-dev,
g++-8,
cmake, clang-3.8, libunshield-dev, libtinyxml-dev,
g++-6,
# Tests
libgtest-dev, google-mock,
# Boost
@ -45,7 +45,7 @@ addons:
project:
name: "TES3MP/openmw-tes3mp"
description: "<Your project description here>"
notification_email: koncord@tes3mp.com
notification_email: stas5978@gmail.com
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
build_command: "make -j3"
branch_pattern: coverity_scan
@ -53,21 +53,21 @@ matrix:
include:
- os: linux
env:
- ANALYZE="scan-build-6.0 --use-cc clang-6.0 --use-c++ clang++-6.0 "
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
- ANALYZE="scan-build-3.8 --use-cc clang-3.8 --use-c++ clang++-3.8 "
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
compiler: clang
- os: linux
env:
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
- MATRIX_CC="CC=gcc-6 && CXX=g++-6"
- os: linux
env:
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
allow_failures:
- env:
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
- env:
- ANALYZE="scan-build-6.0 --use-cc clang-6.0 --use-c++ clang++-6.0 "
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
- ANALYZE="scan-build-3.8 --use-cc clang-3.8 --use-c++ clang++-3.8 "
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
before_install:
- ./CI/before_install.${TRAVIS_OS_NAME}.sh

@ -15,8 +15,19 @@ sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so
sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so
cd ~/
git clone https://github.com/TES3MP/CrabNet
cd CrabNet
cmake . -DCRABNET_ENABLE_DLL=OFF -DCRABNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
git clone https://github.com/TES3MP/RakNet
cd RakNet
cmake . -DRAKNET_ENABLE_DLL=OFF -DRAKNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
make -j3
cd ~/
git clone https://github.com/Koncord/CallFF
cd CallFF
mkdir build
cd build
cmake ../
make -j3
cd ~/
wget https://github.com/zdevito/terra/releases/download/release-2016-03-25/terra-Linux-x86_64-332a506.zip
unzip terra-Linux-x86_64-332a506.zip

@ -9,7 +9,8 @@ if [ ! -z "${MATRIX_CC}" ]; then
eval "${MATRIX_CC}"
fi
export RAKNET_ROOT=~/CrabNet
export RAKNET_ROOT=~/RakNet
export Terra_ROOT=~/terra-Linux-x86_64-332a506
export CODE_COVERAGE=0
if [ ! -z "${ANALYZE}" ]; then
@ -35,5 +36,7 @@ ${ANALYZE}cmake .. \
-DBINDIR=/usr/games \
-DCMAKE_BUILD_TYPE="None" \
-DUSE_SYSTEM_TINYXML=TRUE \
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \
-DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a
-DRakNet_LIBRARY_RELEASE=~/RakNet/lib/libRakNetLibStatic.a \
-DRakNet_LIBRARY_DEBUG=~/RakNet/lib/libRakNetLibStatic.a \
-DCallFF_INCLUDES=~/CallFF/include \
-DCallFF_LIBRARY=~/CallFF/build/src/libcallff.a

@ -736,10 +736,7 @@ if (WIN32)
endforeach(d)
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
if (BUILD_OPENMW)
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
if (BUILD_BSATOOL)
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")

@ -2,11 +2,11 @@ TES3MP
======
Copyright (c) 2008-2015, OpenMW Team
Copyright (c) 2016-2019, Stanislav Zhukov & David Cernat
Copyright (c) 2016-2018, TES3MP Team
[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=0.7.0)](https://travis-ci.org/TES3MP/openmw-tes3mp)
[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=master)](https://travis-ci.org/TES3MP/openmw-tes3mp)
TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), an open-source game engine that supports playing "The Elder Scrolls III: Morrowind" by Bethesda Softworks.
TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), an open-source game engine that supports playing "The Elder Scrolls III: Morrowind" by Bethesda Softworks.
* TES3MP version: 0.7.0-alpha
* OpenMW version: 0.44.0
@ -15,35 +15,34 @@ TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information)
Project status
Project Status
--------------
[Version changelog](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-changelog.md)
As of version 0.7.0, TES3MP is fully playable, providing very extensive player, NPC, world and quest synchronization, as well as state saving and loading, all of which are highly customizable via [serverside Lua scripts](https://github.com/TES3MP/CoreScripts).
TES3MP is now playable in most respects. Player and NPC movement, animations, combat and spell casting are properly synchronized with small exceptions, as is picking up and dropping items in the world, using doors and levers, and adding and removing items from containers. Journal entries, faction stats and dialogue topics are also synchronized, allowing the majority of quests to work fine.
Remaining gameplay problems mostly relate to AI and the synchronization of clientside script variables.
Donations
---------------
You can benefit the project by donating on Patreon to our two developers, [David Cernat](https://www.patreon.com/davidcernat) and [Koncord](https://www.patreon.com/Koncord), as well as by supporting [OpenMW](https://openmw.org).
[Serverside Lua scripts](https://github.com/TES3MP/CoreScripts) are used to save and load the state of most of the aforementioned.
Contributing
---------------
--------------
Helping us with documentation, bug hunting and video showcases is always greatly appreciated.
Development has been relatively fast, but any contribution regarding [code](https://github.com/TES3MP/openmw-tes3mp/blob/master/CONTRIBUTING.md), documentation, bug hunting or video showcases is greatly appreciated.
For code contributions, it's best to start out with modestly sized fixes and features and work your way up. There are so many different possible implementations of more major features many of which would cause undesirable code or vision conflicts with OpenMW that those should be talked over in advance with the existing developers before effort is spent on them.
Test sessions are often advertised on [our Discord server](https://discord.gg/ECJk293) or in [our Steam group](https://steamcommunity.com/groups/mwmulti).
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
Getting started
Getting Started
---------------
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](https://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=45)
* [Discord server](https://discord.gg/ECJk293)
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=44)
* [Subreddit](https://www.reddit.com/r/tes3mp)
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)
Donations
---------------
You can benefit the project by contributing to the Patreon pages of our two developers, [Koncord](https://www.patreon.com/Koncord) and [David Cernat](https://www.patreon.com/davidcernat), as well as by supporting [OpenMW](https://openmw.org).

@ -1,5 +1,12 @@
project(tes3mp-server)
if(UNIX) #temporarily disabled for non-unix
if(NOT (${CMAKE_CXX_COMPILER} MATCHES "aarch64" OR ${CMAKE_CXX_COMPILER} MATCHES "arm")) #temporarily disabled for arm
find_package(CallFF REQUIRED)
include_directories(${CallFF_INCLUDES})
endif(NOT (${CMAKE_CXX_COMPILER} MATCHES "aarch64" OR ${CMAKE_CXX_COMPILER} MATCHES "arm"))
endif(UNIX)
option(ENABLE_BREAKPAD "Enable Google Breakpad for Crash reporting" OFF)
if(ENABLE_BREAKPAD)
@ -29,7 +36,7 @@ if(BUILD_WITH_LUA)
Script/LangLua/LangLua.hpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_LUA")
include_directories(SYSTEM ${LuaJit_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/extern/LuaBridge)
include_directories(${LuaJit_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/extern/LuaBridge)
endif(BUILD_WITH_LUA)
set(NativeScript_Sources
@ -73,6 +80,7 @@ set(SERVER_HEADER
Script/ScriptFunctions.hpp Script/API/TimerAPI.hpp Script/API/PublicFnAPI.hpp
${LuaScript_Headers}
${NativeScript_Headers}
${CallFF_INCLUDES}
)
source_group(tes3mp-server FILES ${SERVER} ${SERVER_HEADER})
@ -152,22 +160,7 @@ add_executable(tes3mp-server
${PROCESSORS_ACTOR} ${PROCESSORS_PLAYER} ${PROCESSORS_OBJECT} ${PROCESSORS_WORLDSTATE} ${PROCESSORS}
${APPLE_BUNDLE_RESOURCES}
)
target_compile_options(tes3mp-server PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/permissive->)
if (OPENMW_MP_BUILD)
target_compile_options(tes3mp-server PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/MP>)
endif()
set_target_properties(tes3mp-server PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS YES
)
if (UNIX)
target_compile_options(tes3mp-server PRIVATE -Wno-ignored-qualifiers)
endif()
add_definitions(-std=gnu++14 -Wno-ignored-qualifiers)
target_link_libraries(tes3mp-server
#${Boost_SYSTEM_LIBRARY}
@ -178,6 +171,7 @@ target_link_libraries(tes3mp-server
components
${LuaJit_LIBRARIES}
${Breakpad_Library}
${CallFF_LIBRARY}
)
if (UNIX)

@ -53,21 +53,18 @@ void Cell::addPlayer(Player *player)
players.push_back(player);
}
void Cell::removePlayer(Player *player, bool cleanPlayer)
void Cell::removePlayer(Player *player)
{
for (Iterator it = begin(); it != end(); it++)
{
if (*it == player)
{
if (cleanPlayer)
auto it2 = find(player->cells.begin(), player->cells.end(), this);
if (it2 != player->cells.end())
{
auto it2 = find(player->cells.begin(), player->cells.end(), this);
if (it2 != player->cells.end())
{
LOG_APPEND(Log::LOG_INFO, "- Removing %s from Player %s", getDescription().c_str(), player->npc.mName.c_str());
LOG_APPEND(Log::LOG_INFO, "- Removing %s from Player %s", getDescription().c_str(), player->npc.mName.c_str());
player->cells.erase(it2);
}
player->cells.erase(it2);
}
LOG_APPEND(Log::LOG_INFO, "- Removing %s from Cell %s", player->npc.mName.c_str(), getDescription().c_str());

@ -28,7 +28,7 @@ public:
Iterator end() const;
void addPlayer(Player *player);
void removePlayer(Player *player, bool cleanPlayer = true);
void removePlayer(Player *player);
void readActorList(unsigned char packetID, const mwmp::BaseActorList *newActorList);
bool containsActor(int refNum, int mpNum);

@ -134,35 +134,28 @@ void CellController::removeCell(Cell *cell)
}
}
void CellController::deletePlayer(Player *player)
void CellController::removePlayer(Cell *cell, Player *player)
{
LOG_APPEND(Log::LOG_INFO, "- Iterating through Cells from Player %s", player->npc.mName.c_str());
std::vector<Cell*> toDelete;
cell->removePlayer(player);
auto it = player->getCells()->begin();
const auto endIter = player->getCells()->end();
for (; it != endIter; ++it)
{
Cell *c = *it;
c->removePlayer(player, false);
if (c->players.empty())
toDelete.push_back(c);
}
for (auto &&cell : toDelete)
if (cell->players.empty())
{
LOG_APPEND(Log::LOG_INFO, "- Cell %s has no players left", cell->getDescription().c_str());
removeCell(cell);
}
}
void CellController::update(Player *player)
void CellController::deletePlayer(Player *player)
{
std::vector<Cell*> toDelete;
LOG_APPEND(Log::LOG_INFO, "- Iterating through Cells from Player %s", player->npc.mName.c_str());
for (auto it = player->getCells()->begin(); player->getCells()->size() != 0; ++it)
removePlayer(*it, player);
}
for (auto &&cell : player->cellStateChanges.cellStates)
void CellController::update(Player *player)
{
for (auto cell : player->cellStateChanges.cellStates)
{
if (cell.type == mwmp::CellState::LOAD)
{
@ -178,16 +171,7 @@ void CellController::update(Player *player)
c = getCellByXY(cell.cell.getGridX(), cell.cell.getGridY());
if (c != nullptr)
{
c->removePlayer(player);
if (c->players.empty())
toDelete.push_back(c);
}
removePlayer(c, player);
}
}
for (auto &&cell : toDelete)
{
LOG_APPEND(Log::LOG_INFO, "- Cell %s has no players left", cell->getDescription().c_str());
removeCell(cell);
}
}

@ -30,6 +30,7 @@ public:
Cell * addCell(ESM::Cell cell);
void removeCell(Cell *);
void removePlayer(Cell *cell, Player *player);
void deletePlayer(Player *player);
Cell *getCell(ESM::Cell *esmCell);

@ -31,7 +31,6 @@ Networking *Networking::sThis = 0;
static int currentMpNum = 0;
static bool pluginEnforcementState = true;
static bool scriptErrorIgnoringState = false;
Networking::Networking(RakNet::RakPeerInterface *peer) : mclient(nullptr)
{
@ -75,9 +74,9 @@ Networking::~Networking()
delete worldstatePacketController;
}
void Networking::setServerPassword(std::string password) noexcept
void Networking::setServerPassword(std::string passw) noexcept
{
serverPassword = password.empty() ? TES3MP_DEFAULT_PASSW : password;
serverPassword = passw.empty() ? TES3MP_DEFAULT_PASSW : passw;
}
bool Networking::isPassworded() const
@ -98,32 +97,25 @@ void Networking::processPlayerPacket(RakNet::Packet *packet)
if (!myPacket->isPacketValid())
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Invalid handshake packet from client at %s", packet->systemAddress.ToString());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Invalid handshake packet from %d", player->getId());
kickPlayer(player->guid);
return;
}
if (player->isHandshaked())
{
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong handshake with client at %s", packet->systemAddress.ToString());
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong handshake with player %d, name: %s", player->getId(),
player->npc.mName.c_str());
kickPlayer(player->guid);
return;
}
if (player->serverPassword != serverPassword)
if (player->passw != serverPassword)
{
if (isPassworded())
{
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong server password %s used by client at %s",
player->serverPassword.c_str(), packet->systemAddress.ToString());
kickPlayer(player->guid);
return;
}
else
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Client at %s tried to join using password, despite the server not being passworded",
packet->systemAddress.ToString());
}
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong server password for player %d, name: %s (pass: %s)",
player->getId(), player->npc.mName.c_str(), player->passw.c_str());
kickPlayer(player->guid);
return;
}
player->setHandshake();
return;
@ -132,8 +124,9 @@ void Networking::processPlayerPacket(RakNet::Packet *packet)
if (!player->isHandshaked())
{
player->incrementHandshakeAttempts();
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Have not completed handshake with client at %s", packet->systemAddress.ToString());
LOG_APPEND(Log::LOG_WARN, "- Attempts so far: %i", player->getHandshakeAttempts());
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Have not completed handshake with player %d", player->getId());
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Attempts so far: %i", player->getHandshakeAttempts());
if (player->getHandshakeAttempts() > 20)
kickPlayer(player->guid, false);
@ -147,10 +140,11 @@ void Networking::processPlayerPacket(RakNet::Packet *packet)
{
player->setLoadState(Player::LOADED);
unsigned short pid = Players::getPlayer(packet->guid)->getId();
Script::Call<Script::CallbackIdentity("OnPlayerConnect")>(pid);
static constexpr unsigned int ident = Script::CallbackIdentity("OnPlayerConnect");
Script::CallBackReturn<ident> result = true;
Script::Call<ident>(result, Players::getPlayer(packet->guid)->getId());
if (player->getLoadState() == Player::KICKED) // kicked inside in OnPlayerConnect
if (!result)
{
playerPacketController->GetPacket(ID_USER_DISCONNECTED)->setPlayer(Players::getPlayer(packet->guid));
playerPacketController->GetPacket(ID_USER_DISCONNECTED)->Send(false);
@ -437,16 +431,6 @@ void Networking::setPluginEnforcementState(bool state)
pluginEnforcementState = state;
}
bool Networking::getScriptErrorIgnoringState()
{
return scriptErrorIgnoringState;
}
void Networking::setScriptErrorIgnoringState(bool state)
{
scriptErrorIgnoringState = state;
}
const Networking &Networking::get()
{
return *sThis;
@ -463,6 +447,32 @@ RakNet::SystemAddress Networking::getSystemAddress(RakNet::RakNetGUID guid)
return peer->GetSystemAddressFromGuid(guid);
}
PacketPreInit::PluginContainer Networking::getPluginListSample()
{
PacketPreInit::PluginContainer pls;
unsigned id = 0;
while (true)
{
unsigned field = 0;
auto name = "";
Script::Call<Script::CallbackIdentity("OnRequestPluginList")>(name, id, field++);
if (strlen(name) == 0)
break;
PacketPreInit::HashList hashList;
while (true)
{
auto hash = "";
Script::Call<Script::CallbackIdentity("OnRequestPluginList")>(hash, id, field++);
if (strlen(hash) == 0)
break;
hashList.push_back((unsigned)stoul(hash));
}
pls.push_back({name, hashList});
id++;
}
return pls;
}
void Networking::stopServer(int code)
{
running = false;
@ -585,11 +595,16 @@ void Networking::InitQuery(std::string queryAddr, unsigned short queryPort)
void Networking::postInit()
{
Script::Call<Script::CallbackIdentity("OnRequestDataFileList")>();
Script::Call<Script::CallbackIdentity("OnServerPostInit")>();
}
PacketPreInit::PluginContainer &Networking::getSamples()
{
return samples;
samples = getPluginListSample();
if (mclient)
{
for (auto plugin : samples)
{
if (!plugin.second.empty())
mclient->PushPlugin({plugin.first, plugin.second[0]});
else
mclient->PushPlugin({plugin.first, 0});
}
}
}

@ -56,9 +56,6 @@ namespace mwmp
bool getPluginEnforcementState();
void setPluginEnforcementState(bool state);
bool getScriptErrorIgnoringState();
void setScriptErrorIgnoringState(bool state);
MasterClient *getMasterClient();
void InitQuery(std::string queryAddr, unsigned short queryPort);
void setServerPassword(std::string passw) noexcept;
@ -68,10 +65,9 @@ namespace mwmp
static Networking *getPtr();
void postInit();
PacketPreInit::PluginContainer &getSamples();
private:
bool preInit(RakNet::Packet *packet, RakNet::BitStream &bsIn);
PacketPreInit::PluginContainer getPluginListSample();
std::string serverPassword;
static Networking *sThis;

@ -21,6 +21,7 @@
#include "Cell.hpp"
#include "CellController.hpp"
struct Player;
typedef std::map<RakNet::RakNetGUID, Player*> TPlayers;
typedef std::map<unsigned short, Player*> TSlots;
@ -50,8 +51,7 @@ public:
{
NOTLOADED=0,
LOADED,
POSTLOADED,
KICKED
POSTLOADED
};
Player(RakNet::RakNetGUID guid);

@ -1,3 +1,7 @@
//
// Created by koncord on 15.03.16.
//
#include "TimerAPI.hpp"
#include <chrono>
@ -10,7 +14,7 @@ Timer::Timer(ScriptFunc callback, long msec, const std::string& def, std::vector
{
targetMsec = msec;
this->args = args;
isEnded = true;
end = true;
}
#if defined(ENABLE_LUA)
@ -18,13 +22,13 @@ Timer::Timer(lua_State *lua, ScriptFuncLua callback, long msec, const std::strin
{
targetMsec = msec;
this->args = args;
isEnded = true;
end = true;
}
#endif
void Timer::Tick()
{
if (isEnded)
if (end)
return;
const auto duration = chrono::system_clock::now().time_since_epoch();
@ -32,19 +36,19 @@ void Timer::Tick()
if (time - startTime >= targetMsec)
{
isEnded = true;
end = true;
Call(args);
}
}
bool Timer::IsEnded()
bool Timer::IsEnd()
{
return isEnded;
return end;
}
void Timer::Stop()
{
isEnded = true;
end = true;
}
void Timer::Restart(int msec)
@ -55,7 +59,7 @@ void Timer::Restart(int msec)
void Timer::Start()
{
isEnded = false;
end = false;
const auto duration = chrono::system_clock::now().time_since_epoch();
const auto msec = chrono::duration_cast<chrono::milliseconds>(duration).count();
@ -168,12 +172,12 @@ void TimerAPI::StopTimer(int timerid)
}
}
bool TimerAPI::IsTimerElapsed(int timerid)
bool TimerAPI::IsEndTimer(int timerid)
{
bool ret = false;
try
{
ret = timers.at(timerid)->IsEnded();
ret = timers.at(timerid)->IsEnd();
}
catch(...)
{

@ -1,3 +1,7 @@
//
// Created by koncord on 15.03.16.
//
#ifndef OPENMW_TIMERAPI_HPP
#define OPENMW_TIMERAPI_HPP
@ -23,7 +27,7 @@ namespace mwmp
#endif
void Tick();
bool IsEnded();
bool IsEnd();
void Stop();
void Start();
void Restart(int msec);
@ -32,7 +36,7 @@ namespace mwmp
std::string publ, arg_types;
std::vector<boost::any> args;
Script *scr;
bool isEnded;
bool end;
};
class TimerAPI
@ -46,7 +50,7 @@ namespace mwmp
static void ResetTimer(int timerid, long msec);
static void StartTimer(int timerid);
static void StopTimer(int timerid);
static bool IsTimerElapsed(int timerid);
static bool IsEndTimer(int timerid);
static void Terminate();

@ -537,7 +537,7 @@ void ActorFunctions::InitializeActorList(unsigned short pid) noexcept
void ActorFunctions::CopyLastActorListToStore() noexcept
{
CopyReceivedActorListToStore();
CopyLastActorListToStore();
}
unsigned int ActorFunctions::GetActorRefNumIndex(unsigned int index) noexcept

@ -1,12 +1,38 @@
#include "Miscellaneous.hpp"
#include <components/misc/stringops.hpp>
#include <components/openmw-mp/Log.hpp>
#include <apps/openmw-mp/Script/ScriptFunctions.hpp>
#include <apps/openmw-mp/Networking.hpp>
#include <iostream>
using namespace std;
static std::string tempFilename;
bool MiscellaneousFunctions::DoesFileExist(const char *filePath) noexcept
{
return boost::filesystem::exists(filePath);
}
const char *MiscellaneousFunctions::GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept
{
if (!boost::filesystem::exists(folderPath)) return "invalid";
boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
for (boost::filesystem::directory_iterator itr(folderPath); itr != end_itr; ++itr)
{
if (Misc::StringUtils::ciEqual(itr->path().filename().string(), filename))
{
tempFilename = itr->path().filename().string();
return tempFilename.c_str();
}
}
return "invalid";
}
unsigned int MiscellaneousFunctions::GetLastPlayerId() noexcept
{
return Players::getLastPlayerId();
@ -21,3 +47,23 @@ void MiscellaneousFunctions::SetCurrentMpNum(int mpNum) noexcept
{
mwmp::Networking::getPtr()->setCurrentMpNum(mpNum);
}
int MiscellaneousFunctions::GetPluginEnforcementState() noexcept
{
return mwmp::Networking::getPtr()->getPluginEnforcementState();
}
void MiscellaneousFunctions::SetPluginEnforcementState(bool state) noexcept
{
mwmp::Networking::getPtr()->setPluginEnforcementState(state);
}
void MiscellaneousFunctions::LogMessage(unsigned short level, const char *message) noexcept
{
LOG_MESSAGE_SIMPLE(level, "[Script]: %s", message);
}
void MiscellaneousFunctions::LogAppend(unsigned short level, const char *message) noexcept
{
LOG_APPEND(level, "[Script]: %s", message);
}

@ -4,15 +4,45 @@
#include "../Types.hpp"
#define MISCELLANEOUSAPI \
{"DoesFileExist", MiscellaneousFunctions::DoesFileExist},\
{"GetCaseInsensitiveFilename", MiscellaneousFunctions::GetCaseInsensitiveFilename},\
\
{"GetLastPlayerId", MiscellaneousFunctions::GetLastPlayerId},\
\
{"GetCurrentMpNum", MiscellaneousFunctions::GetCurrentMpNum},\
{"SetCurrentMpNum", MiscellaneousFunctions::SetCurrentMpNum}
{"SetCurrentMpNum", MiscellaneousFunctions::SetCurrentMpNum},\
\
{"GetPluginEnforcementState", MiscellaneousFunctions::GetPluginEnforcementState},\
{"SetPluginEnforcementState", MiscellaneousFunctions::SetPluginEnforcementState},\
\
{"LogMessage", MiscellaneousFunctions::LogMessage},\
{"LogAppend", MiscellaneousFunctions::LogAppend}
class MiscellaneousFunctions
{
public:
/**
* \brief Check whether a certain file exists.
*
* This will be a case sensitive check on case sensitive filesystems.
*
* Whenever you want to enforce case insensitivity, use GetCaseInsensitiveFilename() instead.
*
* \return Whether the file exists or not.
*/
static bool DoesFileExist(const char *filePath) noexcept;
/**
* \brief Get the first filename in a folder that has a case insensitive match with the filename
* argument.
*
* This is used to retain case insensitivity when opening data files on Linux.
*
* \return The filename that matches.
*/
static const char *GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept;
/**
* \brief Get the last player ID currently connected to the server.
*
@ -48,6 +78,49 @@ public:
* \return void
*/
static void SetCurrentMpNum(int mpNum) noexcept;
/**
* \brief Get the plugin enforcement state of the server.
*
* If true, clients are required to use the same plugins as set for the server.
*
* \return The enforcement state.
*/
static int GetPluginEnforcementState() noexcept;
/**
* \brief Set the plugin enforcement state of the server.
*
* If true, clients are required to use the same plugins as set for the server.
*
* \param state The new enforcement state.
* \return void
*/
static void SetPluginEnforcementState(bool state) noexcept;
/**
* \brief Write a log message with its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogMessage(unsigned short level, const char *message) noexcept;
/**
* \brief Write a log message without its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogAppend(unsigned short level, const char *message) noexcept;
};
#endif //OPENMW_MISCELLANEOUSAPI_HPP

@ -399,15 +399,6 @@ void ObjectFunctions::SetObjectRotation(double x, double y, double z) noexcept
tempObject.position.rot[2] = z;
}
void ObjectFunctions::SetObjectActivatingPid(unsigned short pid) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
tempObject.activatingActor.guid = player->guid;
tempObject.activatingActor.isPlayer = true;
}
void ObjectFunctions::SetObjectDoorState(int doorState) noexcept
{
tempObject.doorState = doorState;

@ -85,8 +85,6 @@
{"SetObjectPosition", ObjectFunctions::SetObjectPosition},\
{"SetObjectRotation", ObjectFunctions::SetObjectRotation},\
\
{"SetObjectActivatingPid", ObjectFunctions::SetObjectActivatingPid},\
\
{"SetObjectDoorState", ObjectFunctions::SetObjectDoorState},\
{"SetObjectDoorTeleportState", ObjectFunctions::SetObjectDoorTeleportState},\
{"SetObjectDoorDestinationCell", ObjectFunctions::SetObjectDoorDestinationCell},\
@ -778,15 +776,6 @@ public:
*/
static void SetObjectRotation(double x, double y, double z) noexcept;
/**
* \brief Set the player ID of the player activating the temporary object stored on the
* server. Currently only used for ObjectActivate packets.
*
* \param pid The pid of the player.
* \return void
*/
static void SetObjectActivatingPid(unsigned short pid) noexcept;
/**
* \brief Set the door state of the temporary object stored on the server.
*

@ -7,6 +7,20 @@
#include <iostream>
using namespace std;
void PositionFunctions::GetPos(unsigned short pid, float *x, float *y, float *z) noexcept
{
*x = 0.00;
*y = 0.00;
*z = 0.00;
Player *player;
GET_PLAYER(pid, player,);
*x = player->position.pos[0];
*y = player->position.pos[1];
*z = player->position.pos[2];
}
double PositionFunctions::GetPosX(unsigned short pid) noexcept
{
Player *player;
@ -55,6 +69,20 @@ double PositionFunctions::GetPreviousCellPosZ(unsigned short pid) noexcept
return player->previousCellPosition.pos[2];
}
void PositionFunctions::GetRot(unsigned short pid, float *x, float *y, float *z) noexcept
{
*x = 0.00;
*y = 0.00;
*z = 0.00;
Player *player;
GET_PLAYER(pid, player, );
*x = player->position.rot[0];
*y = player->position.rot[1];
*z = player->position.rot[2];
}
double PositionFunctions::GetRotX(unsigned short pid) noexcept
{
Player *player;

@ -4,6 +4,7 @@
#include "../Types.hpp"
#define POSITIONAPI \
{"GetPos", PositionFunctions::GetPos},\
{"GetPosX", PositionFunctions::GetPosX},\
{"GetPosY", PositionFunctions::GetPosY},\
{"GetPosZ", PositionFunctions::GetPosZ},\
@ -12,6 +13,7 @@
{"GetPreviousCellPosY", PositionFunctions::GetPreviousCellPosY},\
{"GetPreviousCellPosZ", PositionFunctions::GetPreviousCellPosZ},\
\
{"GetRot", PositionFunctions::GetRot},\
{"GetRotX", PositionFunctions::GetRotX},\
{"GetRotZ", PositionFunctions::GetRotZ},\
\
@ -27,6 +29,18 @@ class PositionFunctions
{
public:
/**
* \brief Assign the player's positional coordinate values to the variables passed as
* parameters.
*
* \param pid The player ID.
* \param x The variable for the X position.
* \param y The variable for the Y position.
* \param z The variable for the Z position.
* \return void
*/
static void GetPos(unsigned short pid, float *x, float *y, float *z) noexcept;
/**
* \brief Get the X position of a player.
*
@ -75,6 +89,18 @@ public:
*/
static double GetPreviousCellPosZ(unsigned short pid) noexcept;
/**
* \brief Assign the player's rotational coordinate values to the variables passed as
* parameters.
*
* \param pid The player ID.
* \param x The variable for the X rotation.
* \param y The variable for the Y rotation.
* \param z The variable for the Z rotation.
* \return void
*/
static void GetRot(unsigned short pid, float *x, float *y, float *z) noexcept;
/**
* \brief Get the X rotation of a player.
*

@ -1,6 +1,5 @@
#include "Server.hpp"
#include <components/misc/stringops.hpp>
#include <components/openmw-mp/NetworkMessages.hpp>
#include <components/openmw-mp/Log.hpp>
#include <components/openmw-mp/Version.hpp>
@ -8,20 +7,7 @@
#include <apps/openmw-mp/Script/ScriptFunctions.hpp>
#include <apps/openmw-mp/Networking.hpp>
#include <apps/openmw-mp/MasterClient.hpp>
#include <Script/Script.hpp>
static std::string tempFilename;
static std::chrono::high_resolution_clock::time_point startupTime = std::chrono::high_resolution_clock::now();
void ServerFunctions::LogMessage(unsigned short level, const char *message) noexcept
{
LOG_MESSAGE_SIMPLE(level, "[Script]: %s", message);
}
void ServerFunctions::LogAppend(unsigned short level, const char *message) noexcept
{
LOG_APPEND(level, "[Script]: %s", message);
}
void ServerFunctions::StopServer(int code) noexcept
{
@ -32,10 +18,7 @@ void ServerFunctions::Kick(unsigned short pid) noexcept
{
Player *player;
GET_PLAYER(pid, player,);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Kicking player %s (%i)", player->npc.mName.c_str(), player->getId());
mwmp::Networking::getPtr()->kickPlayer(player->guid);
player->setLoadState(Player::KICKED);
}
void ServerFunctions::BanAddress(const char *ipAddress) noexcept
@ -48,50 +31,6 @@ void ServerFunctions::UnbanAddress(const char *ipAddress) noexcept
mwmp::Networking::getPtr()->unbanAddress(ipAddress);
}
bool ServerFunctions::DoesFilePathExist(const char *filePath) noexcept
{
return boost::filesystem::exists(filePath);
}
const char *ServerFunctions::GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept
{
if (!boost::filesystem::exists(folderPath)) return "invalid";
boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
for (boost::filesystem::directory_iterator itr(folderPath); itr != end_itr; ++itr)
{
if (Misc::StringUtils::ciEqual(itr->path().filename().string(), filename))
{
tempFilename = itr->path().filename().string();
return tempFilename.c_str();
}
}
return "invalid";
}
const char* ServerFunctions::GetDataPath() noexcept
{
return Script::GetModDir();
}
unsigned int ServerFunctions::GetMillisecondsSinceServerStart() noexcept
{
std::chrono::high_resolution_clock::time_point currentTime = std::chrono::high_resolution_clock::now();
std::chrono::milliseconds milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startupTime);
return milliseconds.count();
}
const char *ServerFunctions::GetOperatingSystemType() noexcept
{
return Utils::getOperatingSystemType().c_str();
}
const char *ServerFunctions::GetArchitectureType() noexcept
{
return Utils::getArchitectureType().c_str();
}
const char *ServerFunctions::GetServerVersion() noexcept
{
return TES3MP_VERSION;
@ -133,16 +72,6 @@ bool ServerFunctions::HasPassword() noexcept
return mwmp::Networking::get().isPassworded();
}
bool ServerFunctions::GetPluginEnforcementState() noexcept
{
return mwmp::Networking::getPtr()->getPluginEnforcementState();
}
bool ServerFunctions::GetScriptErrorIgnoringState() noexcept
{
return mwmp::Networking::getPtr()->getScriptErrorIgnoringState();
}
void ServerFunctions::SetGameMode(const char *gameMode) noexcept
{
if (mwmp::Networking::getPtr()->getMasterClient())
@ -160,16 +89,6 @@ void ServerFunctions::SetServerPassword(const char *password) noexcept
mwmp::Networking::getPtr()->setServerPassword(password);
}
void ServerFunctions::SetPluginEnforcementState(bool state) noexcept
{
mwmp::Networking::getPtr()->setPluginEnforcementState(state);
}
void ServerFunctions::SetScriptErrorIgnoringState(bool state) noexcept
{
mwmp::Networking::getPtr()->setScriptErrorIgnoringState(state);
}
void ServerFunctions::SetRuleString(const char *key, const char *value) noexcept
{
auto mc = mwmp::Networking::getPtr()->getMasterClient();
@ -183,47 +102,3 @@ void ServerFunctions::SetRuleValue(const char *key, double value) noexcept
if (mc)
mc->SetRuleValue(key, value);
}
void ServerFunctions::AddDataFileRequirement(const char *dataFilename, const char *checksumString) noexcept
{
auto &samples = mwmp::Networking::getPtr()->getSamples();
auto it = std::find_if(samples.begin(), samples.end(), [&dataFilename](mwmp::PacketPreInit::PluginPair &item) {
return item.first == dataFilename;
});
if (it != samples.end())
it->second.push_back((unsigned) std::stoul(checksumString));
else
{
mwmp::PacketPreInit::HashList checksumList;
unsigned checksum = 0;
if (strlen(checksumString) != 0)
{
checksum = (unsigned) std::stoul(checksumString);
checksumList.push_back(checksum);
}
samples.emplace_back(dataFilename, checksumList);
auto mclient = mwmp::Networking::getPtr()->getMasterClient();
if (mclient)
mclient->PushPlugin({dataFilename, checksum});
}
}
// All methods below are deprecated versions of methods from above
bool ServerFunctions::DoesFileExist(const char *filePath) noexcept
{
return DoesFilePathExist(filePath);
}
const char* ServerFunctions::GetModDir() noexcept
{
return GetDataPath();
}
void ServerFunctions::AddPluginHash(const char *pluginName, const char *checksumString) noexcept
{
AddDataFileRequirement(pluginName, checksumString);
}

@ -4,73 +4,30 @@
#include "../Types.hpp"
#define SERVERAPI \
{"LogMessage", ServerFunctions::LogMessage},\
{"LogAppend", ServerFunctions::LogAppend},\
{"StopServer", ServerFunctions::StopServer},\
\
{"StopServer", ServerFunctions::StopServer},\
{"Kick", ServerFunctions::Kick},\
{"BanAddress", ServerFunctions::BanAddress},\
{"UnbanAddress", ServerFunctions::UnbanAddress},\
\
{"Kick", ServerFunctions::Kick},\
{"BanAddress", ServerFunctions::BanAddress},\
{"UnbanAddress", ServerFunctions::UnbanAddress},\
{"GetServerVersion", ServerFunctions::GetServerVersion},\
{"GetProtocolVersion", ServerFunctions::GetProtocolVersion},\
{"GetAvgPing", ServerFunctions::GetAvgPing},\
{"GetIP", ServerFunctions::GetIP},\
{"GetMaxPlayers", ServerFunctions::GetMaxPlayers},\
{"GetPort", ServerFunctions::GetPort},\
{"HasPassword", ServerFunctions::HasPassword},\
\
{"DoesFilePathExist", ServerFunctions::DoesFilePathExist},\
{"GetCaseInsensitiveFilename", ServerFunctions::GetCaseInsensitiveFilename},\
{"GetDataPath", ServerFunctions::GetDataPath},\
{"GetMillisecondsSinceServerStart", ServerFunctions::GetMillisecondsSinceServerStart},\
{"GetOperatingSystemType", ServerFunctions::GetOperatingSystemType},\
{"GetArchitectureType", ServerFunctions::GetArchitectureType},\
{"GetServerVersion", ServerFunctions::GetServerVersion},\
{"GetProtocolVersion", ServerFunctions::GetProtocolVersion},\
{"GetAvgPing", ServerFunctions::GetAvgPing},\
{"GetIP", ServerFunctions::GetIP},\
{"GetMaxPlayers", ServerFunctions::GetMaxPlayers},\
{"GetPort", ServerFunctions::GetPort},\
{"HasPassword", ServerFunctions::HasPassword},\
{"GetPluginEnforcementState", ServerFunctions::GetPluginEnforcementState},\
{"GetScriptErrorIgnoringState", ServerFunctions::GetScriptErrorIgnoringState},\
\
{"SetGameMode", ServerFunctions::SetGameMode},\
{"SetHostname", ServerFunctions::SetHostname},\
{"SetServerPassword", ServerFunctions::SetServerPassword},\
{"SetPluginEnforcementState", ServerFunctions::SetPluginEnforcementState},\
{"SetScriptErrorIgnoringState", ServerFunctions::SetScriptErrorIgnoringState},\
{"SetRuleString", ServerFunctions::SetRuleString},\
{"SetRuleValue", ServerFunctions::SetRuleValue},\
\
{"AddDataFileRequirement", ServerFunctions::AddDataFileRequirement},\
\
{"DoesFileExist", ServerFunctions::DoesFileExist},\
{"GetModDir", ServerFunctions::GetModDir},\
{"AddPluginHash", ServerFunctions::AddPluginHash}
{"SetGameMode", ServerFunctions::SetGameMode},\
{"SetHostname", ServerFunctions::SetHostname},\
{"SetServerPassword", ServerFunctions::SetServerPassword},\
{"SetRuleString", ServerFunctions::SetRuleString},\
{"SetRuleValue", ServerFunctions::SetRuleValue}
class ServerFunctions
{
public:
/**
* \brief Write a log message with its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogMessage(unsigned short level, const char *message) noexcept;
/**
* \brief Write a log message without its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogAppend(unsigned short level, const char *message) noexcept;
/**
* \brief Shut down the server.
*
@ -103,59 +60,6 @@ public:
*/
static void UnbanAddress(const char *ipAddress) noexcept;
/**
* \brief Check whether a certain file path exists.
*
* This will be a case sensitive check on case sensitive filesystems.
*
* Whenever you want to enforce case insensitivity, use GetCaseInsensitiveFilename() instead.
*
* \return Whether the file exists or not.
*/
static bool DoesFilePathExist(const char *filePath) noexcept;
/**
* \brief Get the first filename in a folder that has a case insensitive match with the filename
* argument.
*
* This is used to retain case insensitivity when opening data files on Linux.
*
* \return The filename that matches.
*/
static const char *GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept;
/**
* \brief Get the path of the server's data folder.
*
* \return The data path.
*/
static const char *GetDataPath() noexcept;
/**
* \brief Get the milliseconds elapsed since the server was started.
*
* \return The time since the server's startup in milliseconds.
*/
static unsigned int GetMillisecondsSinceServerStart() noexcept;
/**
* \brief Get the type of the operating system used by the server.
*
* Note: Currently, the type can be "Windows", "Linux", "OS X" or "Unknown OS".
*
* \return The type of the operating system.
*/
static const char *GetOperatingSystemType() noexcept;
/**
* \brief Get the architecture type used by the server.
*
* Note: Currently, the type can be "64-bit", "32-bit", "ARMv#" or "Unknown architecture".
*
* \return The architecture type.
*/
static const char *GetArchitectureType() noexcept;
/**
* \brief Get the TES3MP version of the server.
*
@ -189,7 +93,7 @@ public:
/**
* \brief Get the port used by the server.
*
* \return The port.
* \return Port
*/
static unsigned short GetPort() noexcept;
@ -207,24 +111,6 @@ public:
*/
static bool HasPassword() noexcept;
/**
* \brief Get the plugin enforcement state of the server.
*
* If true, clients are required to use the same plugins as set for the server.
*
* \return The enforcement state.
*/
static bool GetPluginEnforcementState() noexcept;
/**
* \brief Get the script error ignoring state of the server.
*
* If true, script errors will not crash the server.
*
* \return The script error ignoring state.
*/
static bool GetScriptErrorIgnoringState() noexcept;
/**
* \brief Set the game mode of the server, as displayed in the server browser.
*
@ -247,29 +133,7 @@ public:
* \param password The password.
* \return void
*/
static void SetServerPassword(const char *password) noexcept;
/**
* \brief Set the plugin enforcement state of the server.
*
* If true, clients are required to use the same plugins as set for the server.
*
* \param state The new enforcement state.
* \return void
*/
static void SetPluginEnforcementState(bool state) noexcept;
/**
* \brief Set whether script errors should be ignored or not.
*
* If true, script errors will not crash the server, but could have any number
* of unforeseen consequences, which is why this is a highly experimental
* setting.
*
* \param state The new script error ignoring state.
* \return void
*/
static void SetScriptErrorIgnoringState(bool state) noexcept;
static void SetServerPassword(const char *passw) noexcept;
/**
* \brief Set a rule string for the server details displayed in the server browser.
@ -288,26 +152,6 @@ public:
* \return void
*/
static void SetRuleValue(const char *key, double value) noexcept;
/**
* \brief Add a data file and a corresponding CRC32 checksum to the data file loadout
* that connecting clients need to match.
*
* It can be used multiple times to set multiple checksums for the same data file.
*
* Note: If an empty string is provided for the checksum, a checksum will not be
* required for that data file.
*
* @param dataFilename The filename of the data file.
* @param checksumString A string with the CRC32 checksum required.
*/
static void AddDataFileRequirement(const char *dataFilename, const char *checksumString) noexcept;
// All methods below are deprecated versions of methods from above
static bool DoesFileExist(const char *filePath) noexcept;
static const char *GetModDir() noexcept;
static void AddPluginHash(const char *pluginName, const char *checksumString) noexcept;
};
#endif //OPENMW_SERVERAPI_HPP

@ -201,17 +201,6 @@ int StatsFunctions::GetAttributeModifier(unsigned short pid, unsigned short attr
return player->creatureStats.mAttributes[attributeId].mMod;
}
double StatsFunctions::GetAttributeDamage(unsigned short pid, unsigned short attributeId) noexcept
{
Player *player;
GET_PLAYER(pid, player, 0);
if (attributeId >= Attribute::Length)
return 0;
return player->creatureStats.mAttributes[attributeId].mDamage;
}
int StatsFunctions::GetSkillBase(unsigned short pid, unsigned short skillId) noexcept
{
Player *player;
@ -234,17 +223,6 @@ int StatsFunctions::GetSkillModifier(unsigned short pid, unsigned short skillId)
return player->npcStats.mSkills[skillId].mMod;
}
double StatsFunctions::GetSkillDamage(unsigned short pid, unsigned short skillId) noexcept
{
Player *player;
GET_PLAYER(pid, player, 0);
if (skillId >= Skill::Length)
return 0;
return player->npcStats.mSkills[skillId].mDamage;
}
double StatsFunctions::GetSkillProgress(unsigned short pid, unsigned short skillId) noexcept
{
Player *player;
@ -459,20 +437,6 @@ void StatsFunctions::ClearAttributeModifier(unsigned short pid, unsigned short a
player->attributeIndexChanges.push_back(attributeId);
}
void StatsFunctions::SetAttributeDamage(unsigned short pid, unsigned short attributeId, double value) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
if (attributeId >= Attribute::Length)
return;
player->creatureStats.mAttributes[attributeId].mDamage = value;
if (!Utils::vectorContains(player->attributeIndexChanges, attributeId))
player->attributeIndexChanges.push_back(attributeId);
}
void StatsFunctions::SetSkillBase(unsigned short pid, unsigned short skillId, int value) noexcept
{
Player *player;
@ -501,20 +465,6 @@ void StatsFunctions::ClearSkillModifier(unsigned short pid, unsigned short skill
player->skillIndexChanges.push_back(skillId);
}
void StatsFunctions::SetSkillDamage(unsigned short pid, unsigned short skillId, double value) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
if (skillId >= Skill::Length)
return;
player->npcStats.mSkills[skillId].mDamage = value;
if (!Utils::vectorContains(player->skillIndexChanges, skillId))
player->skillIndexChanges.push_back(skillId);
}
void StatsFunctions::SetSkillProgress(unsigned short pid, unsigned short skillId, double value) noexcept
{
Player *player;

@ -30,11 +30,9 @@
\
{"GetAttributeBase", StatsFunctions::GetAttributeBase},\
{"GetAttributeModifier", StatsFunctions::GetAttributeModifier},\
{"GetAttributeDamage", StatsFunctions::GetAttributeDamage},\
\
{"GetSkillBase", StatsFunctions::GetSkillBase},\
{"GetSkillModifier", StatsFunctions::GetSkillModifier},\
{"GetSkillDamage", StatsFunctions::GetSkillDamage},\
{"GetSkillProgress", StatsFunctions::GetSkillProgress},\
{"GetSkillIncrease", StatsFunctions::GetSkillIncrease},\
\
@ -60,11 +58,9 @@
\
{"SetAttributeBase", StatsFunctions::SetAttributeBase},\
{"ClearAttributeModifier", StatsFunctions::ClearAttributeModifier},\
{"SetAttributeDamage", StatsFunctions::SetAttributeDamage},\
\
{"SetSkillBase", StatsFunctions::SetSkillBase},\
{"ClearSkillModifier", StatsFunctions::ClearSkillModifier},\
{"SetSkillDamage", StatsFunctions::SetSkillDamage},\
{"SetSkillProgress", StatsFunctions::SetSkillProgress},\
{"SetSkillIncrease", StatsFunctions::SetSkillIncrease},\
\
@ -271,16 +267,6 @@ public:
*/
static int GetAttributeModifier(unsigned short pid, unsigned short attributeId) noexcept;
/**
* \brief Get the amount of damage (as caused through the Damage Attribute effect)
* to a player's attribute.
*
* \param pid The player ID.
* \param attributeId The attribute ID.
* \return The amount of damage to the attribute.
*/
static double GetAttributeDamage(unsigned short pid, unsigned short attributeId) noexcept;
/**
* \brief Get the base value of a player's skill.
*
@ -299,16 +285,6 @@ public:
*/
static int GetSkillModifier(unsigned short pid, unsigned short skillId) noexcept;
/**
* \brief Get the amount of damage (as caused through the Damage Skill effect)
* to a player's skill.
*
* \param pid The player ID.
* \param skillId The skill ID.
* \return The amount of damage to the skill.
*/
static double GetSkillDamage(unsigned short pid, unsigned short skillId) noexcept;
/**
* \brief Get the progress the player has made towards increasing a certain skill by 1.
*
@ -501,17 +477,6 @@ public:
*/
static void ClearAttributeModifier(unsigned short pid, unsigned short attributeId) noexcept;
/**
* \brief Set the amount of damage (as caused through the Damage Attribute effect) to
* a player's attribute.
*
* \param pid The player ID.
* \param attributeId The attribute ID.
* \param value The amount of damage to the player's attribute.
* \return void
*/
static void SetAttributeDamage(unsigned short pid, unsigned short attributeId, double value) noexcept;
/**
* \brief Set the base value of a player's skill.
*
@ -536,17 +501,6 @@ public:
*/
static void ClearSkillModifier(unsigned short pid, unsigned short skillId) noexcept;
/**
* \brief Set the amount of damage (as caused through the Damage Skill effect) to
* a player's skill.
*
* \param pid The player ID.
* \param skillId The skill ID.
* \param value The amount of damage to the player's skill.
* \return void
*/
static void SetSkillDamage(unsigned short pid, unsigned short skillId, double value) noexcept;
/**
* \brief Set the progress the player has made towards increasing a certain skill by 1.
*

@ -21,7 +21,7 @@ int ScriptFunctions::CreateTimerEx(ScriptFunc callback, int msec, const char *ty
try
{
vector<boost::any> params;
Utils::getArguments(params, args, types);
GetArguments(params, args, types);
return mwmp::TimerAPI::CreateTimer(callback, msec, types, params);
}
@ -54,5 +54,5 @@ void ScriptFunctions::FreeTimer(int timerId) noexcept
bool ScriptFunctions::IsTimerElapsed(int timerId) noexcept
{
return TimerAPI::IsTimerElapsed(timerId);
return TimerAPI::IsEndTimer(timerId);
}

@ -9,24 +9,6 @@
using namespace std;
std::set<std::string> LangLua::packagePath;
std::set<std::string> LangLua::packageCPath;
void setLuaPath(lua_State* L, const char* path, bool cpath = false)
{
string field = cpath ? "cpath" : "path";
lua_getglobal(L, "package");
lua_getfield(L, -1, field.c_str());
std::string cur_path = lua_tostring(L, -1);
cur_path.append(";");
cur_path.append(path);
lua_pop(L, 1);
lua_pushstring(L, cur_path.c_str());
lua_setfield(L, -2, field.c_str());
lua_pop(L, 1);
}
lib_t LangLua::GetInterface()
{
return reinterpret_cast<lib_t>(lua);
@ -41,17 +23,6 @@ LangLua::LangLua()
{
lua = luaL_newstate();
luaL_openlibs(lua); // load all lua std libs
std::string p, cp;
for (auto& path : packagePath)
p += path + ';';
for (auto& path : packageCPath)
cp += path + ';';
setLuaPath(lua, p.c_str());
setLuaPath(lua, cp.c_str(), true);
}
LangLua::~LangLua()
@ -107,18 +78,8 @@ template<> struct F_<1> { static constexpr LuaFuctionData F{"CreateTimerEx", Lan
template<> struct F_<2> { static constexpr LuaFuctionData F{"MakePublic", LangLua::MakePublic}; };
template<> struct F_<3> { static constexpr LuaFuctionData F{"CallPublic", LangLua::CallPublic}; };
#ifdef __arm__
template<std::size_t... Is>
struct indices {};
template<std::size_t N, std::size_t... Is>
struct build_indices : build_indices<N-1, N-1, Is...> {};
template<std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};
template<std::size_t N>
using IndicesFor = build_indices<N>;
template<size_t... Indices>
LuaFuctionData *functions(indices<Indices...>)
inline LuaFuctionData *LangLua::functions(indices<Indices...>)
{
static LuaFuctionData functions_[sizeof...(Indices)]{
@ -132,41 +93,6 @@ LuaFuctionData *functions(indices<Indices...>)
return functions_;
}
#else
template<unsigned int I>
struct C
{
constexpr static void Fn(LuaFuctionData *functions_)
{
functions_[I] = F_<I>::F;
C<I - 1>::Fn(functions_);
}
};
template<>
struct C<0>
{
constexpr static void Fn(LuaFuctionData *functions_)
{
functions_[0] = F_<0>::F;
}
};
template<size_t LastI>
LuaFuctionData *functions()
{
static LuaFuctionData functions_[LastI];
C<LastI - 1>::Fn(functions_);
static_assert(
sizeof(functions_) / sizeof(functions_[0]) ==
sizeof(ScriptFunctions::functions) / sizeof(ScriptFunctions::functions[0]),
"Not all functions have been mapped to Lua");
return functions_;
}
#endif
void LangLua::LoadProgram(const char *filename)
{
@ -178,11 +104,8 @@ void LangLua::LoadProgram(const char *filename)
constexpr auto functions_n = sizeof(ScriptFunctions::functions) / sizeof(ScriptFunctions::functions[0]);
#if __arm__
LuaFuctionData *functions_ = functions(IndicesFor<functions_n>{});
#else
LuaFuctionData *functions_ = functions<sizeof(ScriptFunctions::functions) / sizeof(ScriptFunctions::functions[0])>();
#endif
luabridge::Namespace tes3mp = luabridge::getGlobalNamespace(lua).beginNamespace("tes3mp");
for (unsigned i = 0; i < functions_n; i++)
@ -210,65 +133,20 @@ boost::any LangLua::Call(const char *name, const char *argl, int buf, ...)
{
va_list vargs;
va_start(vargs, buf);
std::vector<boost::any> args;
int n_args = (int)(strlen(argl));
lua_getglobal(lua, name);
for (int index = 0; index < n_args; index++)
{
switch (argl[index])
{
case 'i':
luabridge::Stack<unsigned int>::push(lua,va_arg(vargs, unsigned int));
break;
case 'q':
luabridge::Stack<signed int>::push(lua,va_arg(vargs, signed int));
break;
case 'l':
luabridge::Stack<unsigned long long>::push(lua, va_arg(vargs, unsigned long long));
break;
case 'w':
luabridge::Stack<signed long long>::push(lua, va_arg(vargs, signed long long));
break;
case 'f':
luabridge::Stack<double>::push(lua, va_arg(vargs, double));
break;
case 'p':
luabridge::Stack<void*>::push(lua, va_arg(vargs, void*));
break;
case 's':
luabridge::Stack<const char*>::push(lua, va_arg(vargs, const char*));
break;
case 'b':
luabridge::Stack<bool>::push(lua, (bool) va_arg(vargs, int));
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + argl[index]);
}
}
ScriptFunctions::GetArguments(args, vargs, argl);
va_end(vargs);
luabridge::LuaException::pcall(lua, n_args, 1);
return boost::any(luabridge::LuaRef::fromStack(lua, -1));
return Call(name, argl, args);
}
boost::any LangLua::Call(const char *name, const char *argl, const std::vector<boost::any> &args)
{
int n_args = (int)(strlen(argl));
int n_args = (int)(strlen(argl)) ;
lua_getglobal(lua, name);
lua_getglobal (lua, name);
for (int index = 0; index < n_args; index++)
for (intptr_t index = 0; index < n_args; index++)
{
switch (argl[index])
{
@ -308,16 +186,6 @@ boost::any LangLua::Call(const char *name, const char *argl, const std::vector<b
}
}
luabridge::LuaException::pcall(lua, n_args, 1);
luabridge::LuaException::pcall (lua, n_args, 1);
return boost::any(luabridge::LuaRef::fromStack(lua, -1));
}
void LangLua::AddPackagePath(const std::string& path)
{
packagePath.emplace(path);
}
void LangLua::AddPackageCPath(const std::string& path)
{
packageCPath.emplace(path);
}

@ -9,7 +9,6 @@
#include <extern/LuaBridge/LuaBridge.h>
#include <LuaBridge.h>
#include <set>
#include <boost/any.hpp>
#include "../ScriptFunction.hpp"
@ -23,17 +22,25 @@ struct LuaFuctionData
class LangLua: public Language
{
private:
template<std::size_t... Is>
struct indices {};
template<std::size_t N, std::size_t... Is>
struct build_indices : build_indices<N-1, N-1, Is...> {};
template<std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};
template<std::size_t N>
using IndicesFor = build_indices<N>;
public:
virtual lib_t GetInterface() override;
template<std::size_t... Indices>
static LuaFuctionData* functions(indices<Indices...>);
lua_State *lua;
public:
LangLua();
LangLua(lua_State *lua);
~LangLua();
static void AddPackagePath(const std::string &path);
static void AddPackageCPath(const std::string &path);
static int MakePublic(lua_State *lua) noexcept;
static int CallPublic(lua_State *lua);
@ -45,9 +52,6 @@ public:
virtual bool IsCallbackPresent(const char *name) override;
virtual boost::any Call(const char *name, const char *argl, int buf, ...) override;
virtual boost::any Call(const char *name, const char *argl, const std::vector<boost::any> &args) override;
private:
static std::set<std::string> packageCPath;
static std::set<std::string> packagePath;
};

@ -12,7 +12,6 @@
using namespace std;
Script::ScriptList Script::scripts;
std::string Script::moddir;
Script::Script(const char *path)
{
@ -95,14 +94,3 @@ void Script::LoadScript(const char *script, const char *base)
snprintf(path, sizeof(path), Utils::convertPath("%s/%s/%s").c_str(), base, "scripts", script);
Script::scripts.emplace_back(new Script(path));
}
void Script::SetModDir(const std::string &moddir)
{
if (Script::moddir.empty()) // do not allow to change in runtime
Script::moddir = moddir;
}
const char* Script::GetModDir()
{
return moddir.c_str();
}

@ -4,18 +4,15 @@
#ifndef PLUGINSYSTEM3_SCRIPT_HPP
#define PLUGINSYSTEM3_SCRIPT_HPP
#include <boost/any.hpp>
#include <unordered_map>
#include <memory>
#include "Types.hpp"
#include "SystemInterface.hpp"
#include "ScriptFunction.hpp"
#include "ScriptFunctions.hpp"
#include "Language.hpp"
#include "Networking.hpp"
#include <boost/any.hpp>
#include <unordered_map>
#include <memory>
class Script : private ScriptFunctions
{
@ -54,29 +51,29 @@ private:
Script(const Script&) = delete;
Script& operator=(const Script&) = delete;
protected:
static std::string moddir;
public:
~Script();
static void LoadScript(const char *script, const char* base);
static void LoadScripts(char* scripts, const char* base);
static void UnloadScripts();
static void SetModDir(const std::string &moddir);
static const char* GetModDir();
static constexpr ScriptCallbackData const& CallBackData(const unsigned int I, const unsigned int N = 0) {
return callbacks[N].index == I ? callbacks[N] : CallBackData(I, N + 1);
}
template<unsigned int I>
using CallBackReturn = typename CharType<CallBackData(I).callback.ret>::type;
template<size_t N>
static constexpr unsigned int CallbackIdentity(const char(&str)[N])
{
return Utils::hash(str);
}
template<unsigned int I, bool B = false, typename... Args>
static unsigned int Call(Args&&... args) {
static unsigned int Call(CallBackReturn<I>& result, Args&&... args) {
constexpr ScriptCallbackData const& data = CallBackData(I);
static_assert(data.callback.matches(TypeString<typename std::remove_reference<Args>::type...>::value),
"Wrong number or types of arguments");
@ -94,22 +91,12 @@ public:
continue;
if (script->script_type == SCRIPT_CPP)
(callback)(std::forward<Args>(args)...);
result = reinterpret_cast<FunctionEllipsis<CallBackReturn<I>>>(callback)(std::forward<Args>(args)...);
#if defined (ENABLE_LUA)
else if (script->script_type == SCRIPT_LUA)
{
try
{
script->lang->Call(data.name, data.callback.types, B, std::forward<Args>(args)...);
}
catch (std::exception &e)
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, e.what());
Script::Call<Script::CallbackIdentity("OnServerScriptCrash")>(e.what());
if (!mwmp::Networking::getPtr()->getScriptErrorIgnoringState())
throw;
}
boost::any any = script->lang->Call(data.name, data.callback.types, B, std::forward<Args>(args)...);
result = static_cast<CallBackReturn<I>>(boost::any_cast<luabridge::LuaRef>(any).cast<CallBackReturn<I>>());
}
#endif
++count;
@ -117,6 +104,36 @@ public:
return count;
}
template<unsigned int I, bool B = false, typename... Args>
static unsigned int Call(Args&&... args) {
constexpr ScriptCallbackData const& data = CallBackData(I);
static_assert(data.callback.matches(TypeString<typename std::remove_reference<Args>::type...>::value),
"Wrong number or types of arguments");
unsigned int count = 0;
for (auto& script : scripts)
{
if (!script->callbacks_.count(I))
script->callbacks_.emplace(I, script->GetScript<FunctionEllipsis<void>>(data.name));
auto callback = script->callbacks_[I];
if (!callback)
continue;
if (script->script_type == SCRIPT_CPP)
reinterpret_cast<FunctionEllipsis<CallBackReturn<I>>>(callback)(std::forward<Args>(args)...);
#if defined (ENABLE_LUA)
else if (script->script_type == SCRIPT_LUA)
script->lang->Call(data.name, data.callback.types, B, std::forward<Args>(args)...);
#endif
++count;
}
return count;
}
};
#endif //PLUGINSYSTEM3_SCRIPT_HPP

@ -6,6 +6,10 @@
#include <stdexcept>
#include "ScriptFunction.hpp"
#if !defined(_WIN32) && !defined(__ARM_ARCH) // temporarily disabled
#include <call.hpp>
#endif
#if defined (ENABLE_LUA)
#include "LangLua/LangLua.hpp"
#endif
@ -68,6 +72,46 @@ boost::any ScriptFunction::Call(const vector<boost::any> &args)
}
}
#endif
else
{
#if !defined(_WIN32) && !defined(__ARM_ARCH) // temporarily disabled
string::iterator it;
vector<boost::any>::const_iterator it2;
vector<intptr_t> data;
CallArgs callArgs;
for (it = def.begin(), it2 = args.begin(); it != def.end(); ++it, ++it2)
{
switch (*it)
{
case 'i':
callArgs.push_integer(boost::any_cast<unsigned int>(*it2));
break;
case 'q':
callArgs.push_integer(boost::any_cast<signed int>(*it2));
break;
case 'f':
callArgs.push_double(boost::any_cast<double>(*it2));
break;
case 'd':
callArgs.push_double(boost::any_cast<double*>(*it2));
break;
case 's':
callArgs.push_stringPtr(boost::any_cast<const char *>(*it2));
break;
case 'v':
result = boost::any();
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + *it);
}
}
Func f = reinterpret_cast<Func>(fCpp);
result = ::Call(f, callArgs);
#else
throw runtime_error("C++ call: Windows and ARM not supported yet.");
#endif
}
return result;
}

@ -13,6 +13,62 @@ constexpr ScriptCallbackData ScriptFunctions::callbacks[];
using namespace std;
void ScriptFunctions::GetArguments(std::vector<boost::any> &params, va_list args, const std::string &def)
{
params.reserve(def.length());
try
{
for (char c : def)
{
switch (c)
{
case 'i':
params.emplace_back(va_arg(args, unsigned int));
break;
case 'q':
params.emplace_back(va_arg(args, signed int));
break;
case 'l':
params.emplace_back(va_arg(args, unsigned long long));
break;
case 'w':
params.emplace_back(va_arg(args, signed long long));
break;
case 'f':
params.emplace_back(va_arg(args, double));
break;
case 'p':
params.emplace_back(va_arg(args, void*));
break;
case 's':
params.emplace_back(va_arg(args, const char*));
break;
case 'b':
params.emplace_back(va_arg(args, int));
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + c);
}
}
}
catch (...)
{
va_end(args);
throw;
}
va_end(args);
}
void ScriptFunctions::MakePublic(ScriptFunc _public, const char *name, char ret_type, const char *def) noexcept
{
Public::MakePublic(_public, name, ret_type, def);
@ -25,7 +81,7 @@ boost::any ScriptFunctions::CallPublic(const char *name, va_list args) noexcept
try
{
string def = Public::GetDefinition(name);
Utils::getArguments(params, args, def);
GetArguments(params, args, def);
return Public::Call(name, params);
}

@ -30,10 +30,6 @@
#include <components/openmw-mp/Log.hpp>
#ifndef __PRETTY_FUNCTION__
#define __PRETTY_FUNCTION__ __FUNCTION__
#endif
#define GET_PLAYER(pid, pl, retvalue) \
pl = Players::getPlayer(pid); \
if (player == 0) {\
@ -47,6 +43,7 @@ class ScriptFunctions
{
public:
static void GetArguments(std::vector<boost::any> &params, va_list args, const std::string &def);
static void MakePublic(ScriptFunc _public, const char *name, char ret_type, const char *def) noexcept;
static boost::any CallPublic(const char *name, va_list args) noexcept;
@ -119,9 +116,9 @@ public:
static constexpr ScriptFunctionData functions[]{
{"CreateTimer", ScriptFunctions::CreateTimer},
{"CreateTimerEx", ScriptFunctions::CreateTimerEx},
{"CreateTimerEx", reinterpret_cast<Function<void>>(ScriptFunctions::CreateTimerEx)},
{"MakePublic", ScriptFunctions::MakePublic},
{"CallPublic", ScriptFunctions::CallPublic},
{"CallPublic", reinterpret_cast<Function<void>>(ScriptFunctions::CallPublic)},
{"StartTimer", ScriptFunctions::StartTimer},
{"StopTimer", ScriptFunctions::StopTimer},
@ -153,63 +150,63 @@ public:
};
static constexpr ScriptCallbackData callbacks[]{
{"OnServerInit", Callback<>()},
{"OnServerPostInit", Callback<>()},
{"OnServerExit", Callback<bool>()},
{"OnServerScriptCrash", Callback<const char*>()},
{"OnPlayerConnect", Callback<unsigned short>()},
{"OnPlayerDisconnect", Callback<unsigned short>()},
{"OnPlayerDeath", Callback<unsigned short>()},
{"OnPlayerResurrect", Callback<unsigned short>()},
{"OnPlayerCellChange", Callback<unsigned short>()},
{"OnPlayerAttribute", Callback<unsigned short>()},
{"OnPlayerSkill", Callback<unsigned short>()},
{"OnPlayerLevel", Callback<unsigned short>()},
{"OnPlayerBounty", Callback<unsigned short>()},
{"OnPlayerReputation", Callback<unsigned short>()},
{"OnPlayerEquipment", Callback<unsigned short>()},
{"OnPlayerInventory", Callback<unsigned short>()},
{"OnPlayerJournal", Callback<unsigned short>()},
{"OnPlayerFaction", Callback<unsigned short>()},
{"OnPlayerShapeshift", Callback<unsigned short>()},
{"OnPlayerSpellbook", Callback<unsigned short>()},
{"OnPlayerQuickKeys", Callback<unsigned short>()},
{"OnPlayerTopic", Callback<unsigned short>()},
{"OnPlayerDisposition", Callback<unsigned short>()},
{"OnPlayerBook", Callback<unsigned short>()},
{"OnPlayerItemUse", Callback<unsigned short>()},
{"OnPlayerMiscellaneous", Callback<unsigned short>()},
{"OnPlayerInput", Callback<unsigned short>()},
{"OnPlayerRest", Callback<unsigned short>()},
{"OnRecordDynamic", Callback<unsigned short>()},
{"OnCellLoad", Callback<unsigned short, const char*>()},
{"OnCellUnload", Callback<unsigned short, const char*>()},
{"OnCellDeletion", Callback<const char*>()},
{"OnContainer", Callback<unsigned short, const char*>()},
{"OnDoorState", Callback<unsigned short, const char*>()},
{"OnObjectActivate", Callback<unsigned short, const char*>()},
{"OnObjectPlace", Callback<unsigned short, const char*>()},
{"OnObjectState", Callback<unsigned short, const char*>()},
{"OnObjectSpawn", Callback<unsigned short, const char*>()},
{"OnObjectDelete", Callback<unsigned short, const char*>()},
{"OnObjectLock", Callback<unsigned short, const char*>()},
{"OnObjectScale", Callback<unsigned short, const char*>()},
{"OnObjectTrap", Callback<unsigned short, const char*>()},
{"OnVideoPlay", Callback<unsigned short, const char*>()},
{"OnActorList", Callback<unsigned short, const char*>()},
{"OnActorEquipment", Callback<unsigned short, const char*>()},
{"OnActorAI", Callback<unsigned short, const char*>()},
{"OnActorDeath", Callback<unsigned short, const char*>()},
{"OnActorCellChange", Callback<unsigned short, const char*>()},
{"OnActorTest", Callback<unsigned short, const char*>()},
{"OnPlayerSendMessage", Callback<unsigned short, const char*>()},
{"OnPlayerEndCharGen", Callback<unsigned short>()},
{"OnGUIAction", Callback<unsigned short, int, const char*>()},
{"OnWorldKillCount", Callback<unsigned short>()},
{"OnWorldMap", Callback<unsigned short>()},
{"OnWorldWeather", Callback<unsigned short>() },
{"OnMpNumIncrement", Callback<int>()},
{"OnRequestDataFileList", Callback<>()}
{"Main", Function<int, int, int>()},
{"OnServerInit", Function<void>()},
{"OnServerPostInit", Function<void>()},
{"OnServerExit", Function<void, bool>()},
{"OnPlayerConnect", Function<bool, unsigned short>()},
{"OnPlayerDisconnect", Function<void, unsigned short>()},
{"OnPlayerDeath", Function<void, unsigned short>()},
{"OnPlayerResurrect", Function<void, unsigned short>()},
{"OnPlayerCellChange", Function<void, unsigned short>()},
{"OnPlayerAttribute", Function<void, unsigned short>()},
{"OnPlayerSkill", Function<void, unsigned short>()},
{"OnPlayerLevel", Function<void, unsigned short>()},
{"OnPlayerBounty", Function<void, unsigned short>()},
{"OnPlayerReputation", Function<void, unsigned short>()},
{"OnPlayerEquipment", Function<void, unsigned short>()},
{"OnPlayerInventory", Function<void, unsigned short>()},
{"OnPlayerJournal", Function<void, unsigned short>()},
{"OnPlayerFaction", Function<void, unsigned short>()},
{"OnPlayerShapeshift", Function<void, unsigned short>()},
{"OnPlayerSpellbook", Function<void, unsigned short>()},
{"OnPlayerQuickKeys", Function<void, unsigned short>()},
{"OnPlayerTopic", Function<void, unsigned short>()},
{"OnPlayerDisposition", Function<void, unsigned short>()},
{"OnPlayerBook", Function<void, unsigned short>()},
{"OnPlayerItemUse", Function<void, unsigned short>()},
{"OnPlayerMiscellaneous", Function<void, unsigned short>()},
{"OnPlayerInput", Function<void, unsigned short>()},
{"OnPlayerRest", Function<void, unsigned short>()},
{"OnRecordDynamic", Function<void, unsigned short>()},
{"OnCellLoad", Function<void, unsigned short, const char*>()},
{"OnCellUnload", Function<void, unsigned short, const char*>()},
{"OnCellDeletion", Function<void, const char*>()},
{"OnContainer", Function<void, unsigned short, const char*>()},
{"OnDoorState", Function<void, unsigned short, const char*>()},
{"OnObjectActivate", Function<void, unsigned short, const char*>()},
{"OnObjectPlace", Function<void, unsigned short, const char*>()},
{"OnObjectState", Function<void, unsigned short, const char*>()},
{"OnObjectSpawn", Function<void, unsigned short, const char*>()},
{"OnObjectDelete", Function<void, unsigned short, const char*>()},
{"OnObjectLock", Function<void, unsigned short, const char*>()},
{"OnObjectScale", Function<void, unsigned short, const char*>()},
{"OnObjectTrap", Function<void, unsigned short, const char*>()},
{"OnVideoPlay", Function<void, unsigned short, const char*>()},
{"OnActorList", Function<void, unsigned short, const char*>()},
{"OnActorEquipment", Function<void, unsigned short, const char*>()},
{"OnActorAI", Function<void, unsigned short, const char*>()},
{"OnActorDeath", Function<void, unsigned short, const char*>()},
{"OnActorCellChange", Function<void, unsigned short, const char*>()},
{"OnActorTest", Function<void, unsigned short, const char*>()},
{"OnPlayerSendMessage", Function<bool, unsigned short, const char*>()},
{"OnPlayerEndCharGen", Function<void, unsigned short>()},
{"OnGUIAction", Function<void, unsigned short, int, const char*>()},
{"OnWorldKillCount", Function<void, unsigned short>()},
{"OnWorldMap", Function<void, unsigned short>()},
{"OnWorldWeather", Function<void, unsigned short>() },
{"OnMpNumIncrement", Function<void, int>()},
{"OnRequestPluginList", Function<const char *, unsigned int, unsigned int>()}
};
};

@ -82,34 +82,14 @@ struct ScriptIdentity
constexpr ScriptIdentity(Function<R, Types...>) : types(TypeString<Types...>::value), ret(TypeChar<R, sizeof_void<R>::value>::value), numargs(sizeof(TypeString<Types...>::value) - 1) {}
};
template<typename... Types>
using Callback = void (*)(Types...);
struct CallbackIdentity
{
const char* types;
const unsigned int numargs;
constexpr bool matches(const char* types, const unsigned int N = 0) const
{
return N < numargs ? this->types[N] == types[N] && matches(types, N + 1) : this->types[N] == types[N];
}
template<typename... Types>
constexpr CallbackIdentity(Callback<Types...>) : types(TypeString<Types...>::value), numargs(sizeof(TypeString<Types...>::value) - 1) {}
};
struct ScriptFunctionPointer : public ScriptIdentity
{
void *addr;
#if (!defined(__clang__) && defined(__GNUC__))
template<typename R, typename... Types>
constexpr ScriptFunctionPointer(Function<R, Types...> addr) : ScriptIdentity(addr), addr((void*)(addr)) {}
#else
Function<void> addr;
template<typename R, typename... Types>
constexpr ScriptFunctionPointer(Function<R, Types...> addr) : ScriptIdentity(addr), addr(addr) {}
#endif
constexpr ScriptFunctionPointer(Function<R, Types...> addr) : ScriptIdentity(addr), addr(reinterpret_cast<Function<void>>(addr)) {}
};
struct ScriptFunctionData
@ -124,10 +104,10 @@ struct ScriptCallbackData
{
const char* name;
const unsigned int index;
const CallbackIdentity callback;
const ScriptIdentity callback;
template<size_t N>
constexpr ScriptCallbackData(const char(&name)[N], CallbackIdentity _callback) : name(name), index(Utils::hash(name)), callback(_callback) {}
constexpr ScriptCallbackData(const char(&name)[N], ScriptIdentity _callback) : name(name), index(Utils::hash(name)), callback(_callback) {}
};
#endif //TMPTYPES_HPP

@ -1,6 +1,8 @@
#include "Utils.hpp"
//
// Created by koncord on 04.03.17.
//
#include <cstdarg>
#include "Utils.hpp"
using namespace std;
@ -50,59 +52,3 @@ ESM::Cell Utils::getCellFromDescription(std::string cellDescription)
return cell;
}
void Utils::getArguments(std::vector<boost::any> &params, va_list args, const std::string &def)
{
params.reserve(def.length());
try
{
for (char c : def)
{
switch (c)
{
case 'i':
params.emplace_back(va_arg(args, unsigned int));
break;
case 'q':
params.emplace_back(va_arg(args, signed int));
break;
case 'l':
params.emplace_back(va_arg(args, unsigned long long));
break;
case 'w':
params.emplace_back(va_arg(args, signed long long));
break;
case 'f':
params.emplace_back(va_arg(args, double));
break;
case 'p':
params.emplace_back(va_arg(args, void*));
break;
case 's':
params.emplace_back(va_arg(args, const char*));
break;
case 'b':
params.emplace_back(va_arg(args, int));
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + c);
}
}
}
catch (...)
{
va_end(args);
throw;
}
va_end(args);
}

@ -1,3 +1,7 @@
//
// Created by koncord on 04.03.17.
//
#ifndef OPENMW_UTILS_HPP
#define OPENMW_UTILS_HPP
@ -5,8 +9,6 @@
#include <regex>
#include <vector>
#include <boost/any.hpp>
#include <components/esm/loadcell.hpp>
#include <components/openmw-mp/Utils.hpp>
@ -25,8 +27,6 @@ namespace Utils
ESM::Cell getCellFromDescription(std::string cellDescription);
void getArguments(std::vector<boost::any> &params, va_list args, const std::string &def);
template<size_t N>
constexpr unsigned int hash(const char(&str)[N], size_t I = N)
{

@ -190,29 +190,48 @@ int main(int argc, char *argv[])
LOG_INIT(logLevel);
int players = mgr.getInt("maximumPlayers", "General");
string address = mgr.getString("localAddress", "General");
string addr = mgr.getString("localAddress", "General");
int port = mgr.getInt("port", "General");
string password = mgr.getString("password", "General");
string passw = mgr.getString("password", "General");
string pluginHome = mgr.getString("home", "Plugins");
string dataDirectory = Utils::convertPath(pluginHome + "/data");
string plugin_home = mgr.getString("home", "Plugins");
string moddir = Utils::convertPath(plugin_home + "/data");
vector<string> plugins(Utils::split(mgr.getString("plugins", "Plugins"), ','));
vector<string> plugins (Utils::split(mgr.getString("plugins", "Plugins"), ','));
Utils::printVersion("TES3MP dedicated server", TES3MP_VERSION, version.mCommitHash, TES3MP_PROTO_VERSION);
Script::SetModDir(dataDirectory);
#ifdef ENABLE_LUA
LangLua::AddPackagePath(Utils::convertPath(pluginHome + "/scripts/?.lua" + ";"
+ pluginHome + "/lib/lua/?.lua" + ";"));
// Check for unmodified tes3mp-credits file; this makes it so people can't repackage official releases with
// their own made-up credits, though it obviously has no bearing on unofficial releases that change
// the checksum below
boost::filesystem::path folderPath(boost::filesystem::initial_path<boost::filesystem::path>());
folderPath = boost::filesystem::system_complete(boost::filesystem::path(argv[0])).remove_filename();
std::string creditsPath = folderPath.string() + "/tes3mp-credits";
unsigned int expectedChecksumInt = Utils::hexStrToInt(TES3MP_CREDITS_CHECKSUM);
bool hasValidCredits = Utils::doesFileHaveChecksum(creditsPath + ".md", expectedChecksumInt);
if (!hasValidCredits)
hasValidCredits = Utils::doesFileHaveChecksum(creditsPath + ".txt", expectedChecksumInt);
if (!hasValidCredits)
{
LOG_MESSAGE_SIMPLE(Log::LOG_FATAL, "The server is shutting down");
LOG_APPEND(Log::LOG_FATAL, "- %s", TES3MP_CREDITS_ERROR);
return 1;
}
setenv("MOD_DIR", moddir.c_str(), 1); // hack for lua
setenv("LUA_PATH", Utils::convertPath(plugin_home + "/scripts/?.lua" + ";"
+ plugin_home + "/scripts/?.t" + ";"
+ plugin_home + "/lib/lua/?.lua" + ";"
+ plugin_home + "/lib/lua/?.t").c_str(), 1);
#ifdef _WIN32
LangLua::AddPackageCPath(Utils::convertPath(pluginHome + "/lib/?.dll"));
setenv("LUA_CPATH", Utils::convertPath(plugin_home + "/lib/?.dll").c_str(), 1);
#else
LangLua::AddPackageCPath(Utils::convertPath(pluginHome + "/lib/?.so"));
#endif
setenv("LUA_CPATH", Utils::convertPath(plugin_home + "/lib/?.so").c_str(), 1);
#endif
int code;
@ -226,18 +245,18 @@ int main(int argc, char *argv[])
peer->SetIncomingPassword(sstr.str().c_str(), (int) sstr.str().size());
if (RakNet::NonNumericHostString(address.c_str()))
if (RakNet::NonNumericHostString(addr.c_str()))
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "You cannot use non-numeric addresses for the server.");
return 1;
}
RakNet::SocketDescriptor sd((unsigned short) port, address.c_str());
RakNet::SocketDescriptor sd((unsigned short) port, addr.c_str());
try
{
for (auto plugin : plugins)
Script::LoadScript(plugin.c_str(), pluginHome.c_str());
Script::LoadScript(plugin.c_str(), plugin_home.c_str());
switch (peer->Startup((unsigned) players, &sd, 1))
{
@ -252,7 +271,7 @@ int main(int argc, char *argv[])
case RakNet::SOCKET_FAILED_TO_BIND:
case RakNet::SOCKET_PORT_ALREADY_IN_USE:
case RakNet::PORT_CANNOT_BE_ZERO:
throw runtime_error("Failed to bind port. Make sure a server isn't already running on that port.");
throw runtime_error("Failed to bind port");
case RakNet::SOCKET_FAILED_TEST_SEND:
case RakNet::SOCKET_FAMILY_NOT_SUPPORTED:
case RakNet::FAILED_TO_CREATE_NETWORK_THREAD:
@ -264,7 +283,7 @@ int main(int argc, char *argv[])
peer->SetMaximumIncomingConnections((unsigned short) (players));
Networking networking(peer);
networking.setServerPassword(password);
networking.setServerPassword(passw);
if (mgr.getBool("enabled", "MasterServer"))
{
@ -307,7 +326,6 @@ int main(int argc, char *argv[])
catch (std::exception &e)
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, e.what());
Script::Call<Script::CallbackIdentity("OnServerScriptCrash")>(e.what());
throw; //fall through
}

@ -21,7 +21,16 @@ namespace mwmp
{
DEBUG_PRINTF(strPacketID.c_str());
Script::Call<Script::CallbackIdentity("OnPlayerSendMessage")>(player.getId(), player.chatMessage.c_str());
Script::CallBackReturn<Script::CallbackIdentity("OnPlayerSendMessage")> result = true;
Script::Call<Script::CallbackIdentity("OnPlayerSendMessage")>(result, player.getId(), player.chatMessage.c_str());
if (result)
{
player.chatMessage = player.npc.mName + " (" + std::to_string(player.getId()) + "): "
+ player.chatMessage + "\n";
packet.Send(false);
packet.Send(true);
}
}
};
}

@ -1,3 +1,7 @@
//
// Created by koncord on 31.03.17.
//
#ifndef OPENMW_PROCESSORPLAYERPOSITION_HPP
#define OPENMW_PROCESSORPLAYERPOSITION_HPP
@ -15,7 +19,11 @@ namespace mwmp
void Do(PlayerPacket &packet, Player &player) override
{
player.sendToLoaded(&packet);
//DEBUG_PRINTF(strPacketID);
if (!player.creatureStats.mDead)
{
player.sendToLoaded(&packet);
}
}
};
}

@ -134,7 +134,7 @@ add_openmw_dir (mwmp/processors/object BaseObjectProcessor
ProcessorScriptGlobalFloat
)
add_openmw_dir (mwmp/processors/worldstate ProcessorCellCreate ProcessorCellReset ProcessorRecordDynamic
add_openmw_dir (mwmp/processors/worldstate ProcessorCellCreate ProcessorCellReplace ProcessorRecordDynamic
ProcessorWorldCollisionOverride ProcessorWorldMap ProcessorWorldRegionAuthority ProcessorWorldTime
ProcessorWorldWeather
)

@ -221,11 +221,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
/*
Start of tes3mp addition
Check for unmodified tes3mp-credits file on Windows; this makes it so people can't repackage official
releases with their own made-up credits, though it obviously has no bearing on unofficial releases that
change the checksum below
Check for unmodified tes3mp-credits file; this makes it so people can't repackage official releases with
their own made-up credits, though it obviously has no bearing on unofficial releases that change
the checksum below
*/
#ifdef _WIN32
boost::filesystem::path folderPath(boost::filesystem::initial_path<boost::filesystem::path>());
folderPath = boost::filesystem::system_complete(boost::filesystem::path(argv[0])).remove_filename();
std::string creditsPath = folderPath.string() + "/tes3mp-credits";
@ -243,7 +242,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "tes3mp", TES3MP_CREDITS_ERROR, 0);
return false;
}
#endif
/*
End of tes3mp addition
*/

@ -553,10 +553,9 @@ namespace MWClass
/*
Start of tes3mp addition
If the attacker was the LocalPlayer or LocalActor, record their target and send a
packet with it
If the attacker was the LocalPlayer or LocalActor, record their target and send a packet with it
If the victim was a LocalActor who died, record their attacker as the killer
If the victim was a LocalActor who died, record their attacker as the deathReason
*/
mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(attacker);

@ -133,9 +133,9 @@ namespace
}
modifierSum += add;
}
creatureStats.setAttribute(attribute, std::min(
round_ieee_754(creatureStats.getAttribute(attribute).getBase()
+ (level-1) * modifierSum), 100) );
creatureStats.setAttribute(attribute,
round_ieee_754(creatureStats.getAttribute(attribute).getBase() + (level-1) * modifierSum) );
}
// initial health
@ -230,13 +230,12 @@ namespace
}
npcStats.getSkill(skillIndex).setBase(
std::min(
round_ieee_754(
npcStats.getSkill(skillIndex).getBase()
+ 5
+ raceBonus
+ specBonus
+(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
+(int(level)-1) * (majorMultiplier + specMultiplier))); // Must gracefully handle level 0
}
int skills[ESM::Skill::Length];
@ -966,13 +965,12 @@ namespace MWClass
/*
Start of tes3mp addition
If the attacker was the LocalPlayer or LocalActor, record their target and send a
packet with it
If the attacker was the LocalPlayer or LocalActor, record their target and send a packet with it
If the victim was the LocalPlayer, check whether packets should be sent about
their new dynamic stats and position
If the victim was a LocalActor who died, record their attacker as the killer
If the victim was a LocalActor who died, record their attacker as the deathReason
*/
mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(attacker);

@ -197,15 +197,14 @@ namespace MWGui
/*
Start of tes3mp change (major)
For valid drops, avoid running the original code for the item transfer, to prevent unilateral
item duping or interaction on this client
Avoid running any of the original code for dropping items, to prevent possibilities
for item duping or interaction with restricted containers
Instead, finish the drag in a way that removes the items in it, and let the server's reply handle
the rest
Instead, finish the drag in a way that removes the items in it
*/
if (success)
// mDragAndDrop->drop(mModel, mItemView);
mDragAndDrop->finish(true);
//if (success)
// mDragAndDrop->drop(mModel, mItemView);
mDragAndDrop->finish(true);
/*
End of tes3mp change (major)
*/

@ -458,15 +458,6 @@ namespace MWGui
updateMagicMarkers();
updateCustomMarkers();
/*
Start of tes3mp addition
Update player markers when cell changes to fix their locations
*/
updatePlayerMarkers();
/*
End of tes3mp addition
*/
}
void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell)

@ -164,10 +164,10 @@ namespace MWGui
Start of tes3mp addition
Send a PLAYER_QUICKKEYS packet whenever a key is unassigned, but only if the player
is logged in on the server, so as to avoid doing anything doing at startup when all
has finished character generation, so as to avoid doing anything doing startup when all
quick keys get unassigned by default
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && !mwmp::Main::get().getLocalPlayer()->isReceivingQuickKeys)
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen() && !mwmp::Main::get().getLocalPlayer()->isReceivingQuickKeys)
{
mwmp::Main::get().getLocalPlayer()->sendQuickKey(key->index, Type_Unassigned);
}

@ -9,19 +9,6 @@
#include <components/misc/rng.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/MechanicsHelper.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@ -176,26 +163,9 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item)
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
item.getClass().getEnchantment(item));
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original item with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
mwmp::Item removedItem = MechanicsHelper::getItem(item, 1);
item.getCellRef().setEnchantmentCharge(
std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast<float>(enchantment->mData.mCharge)));
mwmp::Item addedItem = MechanicsHelper::getItem(item, 1);
localPlayer->sendItemChange(addedItem, mwmp::InventoryChanges::ADD);
localPlayer->sendItemChange(removedItem, mwmp::InventoryChanges::REMOVE);
/*
End of tes3mp change (minor)
*/
MWBase::Environment::get().getWindowManager()->playSound("Enchant Success");
player.getClass().getContainerStore(player).restack(item);

@ -244,33 +244,18 @@ namespace MWGui
nameWidget->setSize(nameWidget->getWidth() - (widthAfter-widthBefore), nameWidget->getHeight());
}
if (value.getBase() < 100)
{
nameWidget->setUserString("Visible_SkillMaxed", "false");
nameWidget->setUserString("UserData^Hidden_SkillMaxed", "true");
nameWidget->setUserString("Visible_SkillProgressVBox", "true");
nameWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false");
valueWidget->setUserString("Visible_SkillMaxed", "false");
valueWidget->setUserString("UserData^Hidden_SkillMaxed", "true");
valueWidget->setUserString("Visible_SkillProgressVBox", "true");
valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false");
setSkillProgress(nameWidget, value.getProgress(), parSkill);
setSkillProgress(valueWidget, value.getProgress(), parSkill);
}
else
{
nameWidget->setUserString("Visible_SkillMaxed", "true");
nameWidget->setUserString("UserData^Hidden_SkillMaxed", "false");
nameWidget->setUserString("Visible_SkillProgressVBox", "false");
nameWidget->setUserString("UserData^Hidden_SkillProgressVBox", "true");
valueWidget->setUserString("Visible_SkillMaxed", "true");
valueWidget->setUserString("UserData^Hidden_SkillMaxed", "false");
valueWidget->setUserString("Visible_SkillProgressVBox", "false");
valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "true");
}
nameWidget->setUserString("Visible_SkillMaxed", "false");
nameWidget->setUserString("UserData^Hidden_SkillMaxed", "true");
nameWidget->setUserString("Visible_SkillProgressVBox", "true");
nameWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false");
valueWidget->setUserString("Visible_SkillMaxed", "false");
valueWidget->setUserString("UserData^Hidden_SkillMaxed", "true");
valueWidget->setUserString("Visible_SkillProgressVBox", "true");
valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false");
setSkillProgress(nameWidget, value.getProgress(), parSkill);
setSkillProgress(valueWidget, value.getProgress(), parSkill);
}
}

@ -1070,9 +1070,9 @@ namespace MWInput
/*
Start of tes3mp addition
Ignore attempts to rest if the player has not logged in on the server yet
Ignore attempts to rest if the player has not finished character generation yet
*/
if (!mwmp::Main::get().getLocalPlayer()->isLoggedIn())
if (!mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
return;
/*
End of tes3mp addition
@ -1133,21 +1133,10 @@ namespace MWInput
{
if (!mControlSwitch["playercontrols"])
return;
if (MyGUI::InputManager::getInstance ().isModalAny())
return;
/*
Start of tes3mp addition
Ignore attempts to open inventory if the player has not logged in on the server yet
*/
if (!mwmp::Main::get().getLocalPlayer()->isLoggedIn())
return;
/*
End of tes3mp addition
*/
// Toggle between game mode and inventory mode
if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory);

@ -240,22 +240,8 @@ namespace MWMechanics
// Set the soul on just one of the gems, not the whole stack
gem->getContainerStore()->unstack(*gem, caster);
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original gem with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
localPlayer->sendItemChange(*gem, 1, mwmp::InventoryChanges::REMOVE);
gem->getCellRef().setSoul(mCreature.getCellRef().getRefId());
localPlayer->sendItemChange(*gem, 1, mwmp::InventoryChanges::ADD);
/*
End of tes3mp change (minor)
*/
// Restack the gem with other gems with the same soul
gem->getContainerStore()->restack(*gem);
@ -842,22 +828,6 @@ namespace MWMechanics
if (isDamageEffect)
{
/*
Start of tes3mp addition
If the victim was a LocalActor who died, record the caster as the killer
*/
if (mwmp::Main::get().getCellController()->isLocalActor(ptr))
{
bool isSuicide = ptr == caster || caster.isEmpty();
mwmp::Main::get().getCellController()->getLocalActor(ptr)->killer = isSuicide ?
MechanicsHelper::getTarget(ptr) : MechanicsHelper::getTarget(caster);
}
/*
End of tes3mp addition
*/
if (caster == player || playerFollowers.find(caster) != playerFollowers.end())
{
if (caster.getClass().getNpcStats(caster).isWerewolf())

@ -578,7 +578,7 @@ namespace MWMechanics
if (localAttack && localAttack->pressed != true)
{
MechanicsHelper::resetAttack(localAttack);
localAttack->type = distantCombat ? mwmp::Attack::RANGED : mwmp::Attack::MELEE;
localAttack->type = distantCombat ? mwmp::Attack::MELEE : mwmp::Attack::RANGED;
localAttack->pressed = true;
localAttack->shouldSend = true;
}

@ -218,9 +218,6 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas
{
int base = getSkill (skillIndex).getBase();
if (base >= 100)
return;
base += 1;
const MWWorld::Store<ESM::GameSetting> &gmst =

@ -4,19 +4,6 @@
#include <components/misc/rng.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/MechanicsHelper.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
@ -70,25 +57,8 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
// repair by 'y' points
int charge = itemToRepair.getClass().getItemHealth(itemToRepair);
charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair));
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original item with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
mwmp::Item removedItem = MechanicsHelper::getItem(itemToRepair, 1);
itemToRepair.getCellRef().setCharge(charge);
mwmp::Item addedItem = MechanicsHelper::getItem(itemToRepair, 1);
localPlayer->sendItemChange(addedItem, mwmp::InventoryChanges::ADD);
localPlayer->sendItemChange(removedItem, mwmp::InventoryChanges::REMOVE);
/*
End of tes3mp change (minor)
*/
// attempt to re-stack item, in case it was fully repaired
MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair);

@ -537,7 +537,7 @@ namespace MWMechanics
/*
Start of tes3mp addition
If the victim was a LocalPlayer or LocalActor who died, record the caster as the killer
If the victim was a LocalPlayer or LocalActor who died, record their attacker as the deathReason
*/
if (!wasDead && isDead)
{

@ -82,10 +82,8 @@ void Cell::updateLocal(bool forceUpdate)
}
else
{
// Forcibly update this local actor if its data has never been sent before;
// otherwise, use the current forceUpdate value
if (actor->getPtr().getRefData().isEnabled())
actor->update(actor->hasSentData ? forceUpdate : true);
actor->update(forceUpdate);
++it;
}
@ -399,9 +397,6 @@ void Cell::readCellChange(ActorList& actorList)
void Cell::initializeLocalActor(const MWWorld::Ptr& ptr)
{
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
LOG_APPEND(Log::LOG_VERBOSE, "- Initializing LocalActor %s in %s", mapIndex.c_str(), getDescription().c_str());
LocalActor *actor = new LocalActor();
actor->cell = *store->getCell();
actor->setPtr(ptr);
@ -411,17 +406,16 @@ void Cell::initializeLocalActor(const MWWorld::Ptr& ptr)
if (ptr.getClass().getCreatureStats(ptr).isDead())
actor->wasDead = true;
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
localActors[mapIndex] = actor;
Main::get().getCellController()->setLocalActorRecord(mapIndex, getDescription());
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized LocalActor %s in %s", mapIndex.c_str(), getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Initialized LocalActor %s in %s", mapIndex.c_str(), getDescription().c_str());
}
void Cell::initializeLocalActors()
{
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Initializing LocalActors in %s", getDescription().c_str());
for (const auto &mergedRef : store->getMergedRefs())
{
if (mergedRef->mClass->isActor())
@ -438,24 +432,20 @@ void Cell::initializeLocalActors()
initializeLocalActor(ptr);
}
}
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized LocalActors in %s", getDescription().c_str());
}
void Cell::initializeDedicatedActor(const MWWorld::Ptr& ptr)
{
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
LOG_APPEND(Log::LOG_VERBOSE, "- Initializing DedicatedActor %s in %s", mapIndex.c_str(), getDescription().c_str());
DedicatedActor *actor = new DedicatedActor();
actor->cell = *store->getCell();
actor->setPtr(ptr);
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
dedicatedActors[mapIndex] = actor;
Main::get().getCellController()->setDedicatedActorRecord(mapIndex, getDescription());
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized DedicatedActor %s in %s", mapIndex.c_str(), getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Initialized DedicatedActor %s in %s", mapIndex.c_str(), getDescription().c_str());
}
void Cell::initializeDedicatedActors(ActorList& actorList)

@ -77,8 +77,6 @@ void CellController::initializeCell(const ESM::Cell& cell)
// If this key doesn't exist, create it
if (cellsInitialized.count(mapIndex) == 0)
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Initializing mwmp::Cell %s", cell.getDescription().c_str());
MWWorld::CellStore *cellStore = getCellStore(cell);
if (!cellStore) return;
@ -86,7 +84,7 @@ void CellController::initializeCell(const ESM::Cell& cell)
mwmp::Cell *mpCell = new mwmp::Cell(cellStore);
cellsInitialized[mapIndex] = mpCell;
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized mwmp::Cell %s", cell.getDescription().c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "- Initialized mwmp::Cell %s", mpCell->getDescription().c_str());
}
}

@ -71,13 +71,6 @@ DedicatedPlayer::~DedicatedPlayer()
void DedicatedPlayer::update(float dt)
{
// Only move and set anim flags if the framerate isn't too low
if (dt < 0.1)
{
move(dt);
setAnimFlags();
}
MWMechanics::CreatureStats *ptrCreatureStats = &ptr.getClass().getCreatureStats(ptr);
MWMechanics::DynamicStat<float> value;
@ -107,6 +100,13 @@ void DedicatedPlayer::update(float dt)
ptrCreatureStats->setAiSetting(MWMechanics::CreatureStats::AI_Fight, 0);
ptrCreatureStats->setAiSetting(MWMechanics::CreatureStats::AI_Flee, 0);
ptrCreatureStats->setAiSetting(MWMechanics::CreatureStats::AI_Hello, 0);
// Only move and set anim flags if the framerate isn't too low
if (dt < 0.1)
{
move(dt);
setAnimFlags();
}
}
void DedicatedPlayer::move(float dt)

@ -162,9 +162,9 @@ namespace mwmp
windowState++;
if (windowState == 3) windowState = 0;
std::string chatMode = windowState == CHAT_DISABLED ? "Chat hidden" :
windowState == CHAT_ENABLED ? "Chat visible" :
"Chat appearing when needed";
std::string chatMode = windowState == CHAT_DISABLED ? "Chat disabled" :
windowState == CHAT_ENABLED ? "Chat enabled" :
"Chat in hidden mode";
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Switch chat mode to %s", chatMode.c_str());
MWBase::Environment::get().getWindowManager()->messageBox(chatMode);

@ -22,7 +22,6 @@ using namespace std;
LocalActor::LocalActor()
{
hasSentData = false;
posWasChanged = false;
equipmentChanged = false;
@ -62,16 +61,14 @@ void LocalActor::update(bool forceUpdate)
updateSpeech();
updateAttack();
}
hasSentData = true;
}
void LocalActor::updateCell()
{
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Sending ID_ACTOR_CELL_CHANGE about %s %i-%i in cell %s to server",
refId.c_str(), refNum, mpNum, cell.getDescription().c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Sending ID_ACTOR_CELL_CHANGE about %s %i-%i to server",
refId.c_str(), refNum, mpNum);
LOG_APPEND(Log::LOG_VERBOSE, "- Moved to cell %s", ptr.getCell()->getCell()->getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Moved from %s to %s", cell.getDescription().c_str(), ptr.getCell()->getCell()->getDescription().c_str());
cell = *ptr.getCell()->getCell();
position = ptr.getRefData().getPosition();
@ -194,8 +191,8 @@ void LocalActor::updateStatsDynamic(bool forceUpdate)
if (MechanicsHelper::isEmptyTarget(killer))
killer = MechanicsHelper::getTarget(ptr);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i in cell %s to server",
refId.c_str(), refNum, mpNum, cell.getDescription().c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i to server",
refId.c_str(), refNum, mpNum);
mwmp::Main::get().getNetworking()->getActorList()->addDeathActor(*this);

@ -28,7 +28,6 @@ namespace mwmp
MWWorld::Ptr getPtr();
void setPtr(const MWWorld::Ptr& newPtr);
bool hasSentData;
bool wasDead;
private:

@ -46,7 +46,6 @@ using namespace std;
LocalPlayer::LocalPlayer()
{
deathTime = time(0);
receivedCharacter = false;
charGenState.currentStage = 0;
charGenState.endStage = 1;
@ -194,12 +193,9 @@ bool LocalPlayer::processCharGen()
return true;
}
bool LocalPlayer::isLoggedIn()
bool LocalPlayer::hasFinishedCharGen()
{
if (charGenState.isFinished && (charGenState.endStage > 1 || receivedCharacter))
return true;
return false;
return charGenState.isFinished;
}
void LocalPlayer::updateStatsDynamic(bool forceUpdate)
@ -225,16 +221,16 @@ void LocalPlayer::updateStatsDynamic(bool forceUpdate)
|| abs(oldVal.getCurrent() - newVal.getCurrent()) >= limit);
};
if (forceUpdate || needUpdate(oldHealth, health, 2))
if (needUpdate(oldHealth, health, 2))
statsDynamicIndexChanges.push_back(0);
if (forceUpdate || needUpdate(oldMagicka, magicka, 4))
if (needUpdate(oldMagicka, magicka, 4))
statsDynamicIndexChanges.push_back(1);
if (forceUpdate || needUpdate(oldFatigue, fatigue, 4))
if (needUpdate(oldFatigue, fatigue, 4))
statsDynamicIndexChanges.push_back(2);
if (forceUpdate || statsDynamicIndexChanges.size() > 0)
if (statsDynamicIndexChanges.size() > 0 || forceUpdate)
{
oldHealth = health;
oldMagicka = magicka;
@ -284,7 +280,6 @@ void LocalPlayer::updateAttributes(bool forceUpdate)
{
if (ptrNpcStats.getAttribute(i).getBase() != creatureStats.mAttributes[i].mBase ||
ptrNpcStats.getAttribute(i).getModifier() != creatureStats.mAttributes[i].mMod ||
ptrNpcStats.getAttribute(i).getDamage() != creatureStats.mAttributes[i].mDamage ||
ptrNpcStats.getSkillIncrease(i) != npcStats.mSkillIncrease[i] ||
forceUpdate)
{
@ -319,7 +314,6 @@ void LocalPlayer::updateSkills(bool forceUpdate)
// Update a skill if its base value has changed at all or its progress has changed enough
if (ptrNpcStats.getSkill(i).getBase() != npcStats.mSkills[i].mBase ||
ptrNpcStats.getSkill(i).getModifier() != npcStats.mSkills[i].mMod ||
ptrNpcStats.getSkill(i).getDamage() != npcStats.mSkills[i].mDamage ||
abs(ptrNpcStats.getSkill(i).getProgress() - npcStats.mSkills[i].mProgress) > 0.75 ||
forceUpdate)
{
@ -677,7 +671,6 @@ void LocalPlayer::updateAnimFlags(bool forceUpdate)
void LocalPlayer::addItems()
{
MWWorld::Ptr ptrPlayer = getPlayerPtr();
const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore();
MWWorld::ContainerStore &ptrStore = ptrPlayer.getClass().getContainerStore(ptrPlayer);
for (const auto &item : inventoryChanges.items)
@ -688,9 +681,7 @@ void LocalPlayer::addItems()
try
{
MWWorld::ManualRef itemRef(esmStore, item.refId, item.count);
MWWorld::Ptr itemPtr = itemRef.getPtr();
MWWorld::Ptr itemPtr = *ptrStore.add(item.refId, item.count, ptrPlayer);
if (item.charge != -1)
itemPtr.getCellRef().setCharge(item.charge);
@ -699,8 +690,6 @@ void LocalPlayer::addItems()
if (!item.soul.empty())
itemPtr.getCellRef().setSoul(item.soul);
ptrStore.add(itemPtr, item.count, ptrPlayer);
}
catch (std::exception&)
{
@ -843,11 +832,11 @@ void LocalPlayer::resurrect()
// Ensure we unequip any items with constant effects that can put us into an infinite
// death loop
static const int damageEffects[5] = { ESM::MagicEffect::DrainHealth, ESM::MagicEffect::FireDamage,
ESM::MagicEffect::FrostDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::SunDamage };
for (const auto &damageEffect : damageEffects)
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, damageEffect);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::DrainHealth);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FireDamage);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FrostDamage);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::ShockDamage);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::SunDamage);
Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_RESURRECT)->setPlayer(this);
Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_RESURRECT)->Send();
@ -866,8 +855,6 @@ void LocalPlayer::closeInventoryWindows()
void LocalPlayer::setCharacter()
{
receivedCharacter = true;
MWBase::World *world = MWBase::Environment::get().getWorld();
// Ignore invalid races
@ -922,7 +909,7 @@ void LocalPlayer::setAttributes()
{
MWWorld::Ptr ptrPlayer = getPlayerPtr();
MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer);
MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer);
MWMechanics::AttributeValue attributeValue;
for (int attributeIndex = 0; attributeIndex < 8; ++attributeIndex)
@ -930,14 +917,14 @@ void LocalPlayer::setAttributes()
// If the server wants to clear our attribute's non-zero modifier, we need to remove
// the spell effect causing it, to avoid an infinite loop where the effect keeps resetting
// the modifier
if (creatureStats.mAttributes[attributeIndex].mMod == 0 && ptrNpcStats->getAttribute(attributeIndex).getModifier() > 0)
if (creatureStats.mAttributes[attributeIndex].mMod == 0 && ptrCreatureStats->getAttribute(attributeIndex).getModifier() > 0)
{
ptrNpcStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, attributeIndex);
ptrCreatureStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, attributeIndex);
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(ptrPlayer);
// Is the modifier for this attribute still higher than 0? If so, unequip items that
// fortify the attribute
if (ptrNpcStats->getAttribute(attributeIndex).getModifier() > 0)
if (ptrCreatureStats->getAttribute(attributeIndex).getModifier() > 0)
{
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FortifyAttribute, attributeIndex, -1);
mwmp::Main::get().getGUIController()->refreshGuiMode(MWGui::GM_Inventory);
@ -945,9 +932,7 @@ void LocalPlayer::setAttributes()
}
attributeValue.readState(creatureStats.mAttributes[attributeIndex]);
ptrNpcStats->setAttribute(attributeIndex, attributeValue);
ptrNpcStats->setSkillIncrease(attributeIndex, npcStats.mSkillIncrease[attributeIndex]);
ptrCreatureStats->setAttribute(attributeIndex, attributeValue);
}
}
@ -980,6 +965,11 @@ void LocalPlayer::setSkills()
skillValue.readState(npcStats.mSkills[skillIndex]);
ptrNpcStats->setSkill(skillIndex, skillValue);
}
for (int attributeIndex = 0; attributeIndex < 8; ++attributeIndex)
ptrNpcStats->setSkillIncrease(attributeIndex, npcStats.mSkillIncrease[attributeIndex]);
ptrNpcStats->setLevelProgress(npcStats.mLevelProgress);
}
void LocalPlayer::setLevel()
@ -987,9 +977,8 @@ void LocalPlayer::setLevel()
MWBase::World *world = MWBase::Environment::get().getWorld();
MWWorld::Ptr ptrPlayer = world->getPlayerPtr();
MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer);
ptrNpcStats->setLevel(creatureStats.mLevel);
ptrNpcStats->setLevelProgress(npcStats.mLevelProgress);
MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer);
ptrCreatureStats->setLevel(creatureStats.mLevel);
}
void LocalPlayer::setBounty()
@ -1370,7 +1359,7 @@ void LocalPlayer::sendClass()
void LocalPlayer::sendInventory()
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending entire inventory to server");
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending entire inventory to server");
MWWorld::Ptr ptrPlayer = getPlayerPtr();
MWWorld::InventoryStore &ptrInventory = ptrPlayer.getClass().getInventoryStore(ptrPlayer);
@ -1403,28 +1392,36 @@ void LocalPlayer::sendInventory()
getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send();
}
void LocalPlayer::sendItemChange(const mwmp::Item& item, unsigned int action)
void LocalPlayer::sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action)
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending item change for %s with action %i, count %i",
item.refId.c_str(), action, item.count);
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending item change for %s with action %i, count %i",
itemPtr.getCellRef().getRefId().c_str(), action, count);
inventoryChanges.items.clear();
mwmp::Item item;
if (itemPtr.getClass().isGold(itemPtr))
item.refId = MWWorld::ContainerStore::sGoldId;
else
item.refId = itemPtr.getCellRef().getRefId();
item.count = count;
item.charge = itemPtr.getCellRef().getCharge();
item.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge();
item.soul = itemPtr.getCellRef().getSoul();
inventoryChanges.items.push_back(item);
inventoryChanges.action = action;
inventoryChanges.action = action;
getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->setPlayer(this);
getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send();
}
void LocalPlayer::sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action)
{
mwmp::Item item = MechanicsHelper::getItem(itemPtr, count);
sendItemChange(item, action);
}
void LocalPlayer::sendItemChange(const std::string& refId, int count, unsigned int action)
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending item change for %s with action %i, count %i",
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending item change for %s with action %i, count %i",
refId.c_str(), action, count);
inventoryChanges.items.clear();

@ -16,7 +16,6 @@ namespace mwmp
virtual ~LocalPlayer();
time_t deathTime;
bool receivedCharacter;
bool isReceivingInventory;
bool isReceivingQuickKeys;
@ -26,7 +25,7 @@ namespace mwmp
void update();
bool processCharGen();
bool isLoggedIn();
bool hasFinishedCharGen();
void updateStatsDynamic(bool forceUpdate = false);
void updateAttributes(bool forceUpdate = false);
@ -77,7 +76,6 @@ namespace mwmp
void sendClass();
void sendInventory();
void sendItemChange(const mwmp::Item& item, unsigned int action);
void sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action);
void sendItemChange(const std::string& refId, int count, unsigned int action);
void sendSpellbook();

@ -51,8 +51,8 @@ using namespace mwmp;
using namespace std;
Main *Main::pMain = 0;
std::string Main::address = "";
std::string Main::serverPassword = TES3MP_DEFAULT_PASSW;
std::string Main::addr = "";
std::string Main::passw = TES3MP_DEFAULT_PASSW;
std::string Main::resourceDir = "";
std::string Main::getResDir()
@ -118,8 +118,8 @@ void Main::optionsDesc(boost::program_options::options_description *desc)
void Main::configure(const boost::program_options::variables_map &variables)
{
Main::address = variables["connect"].as<string>();
Main::serverPassword = variables["password"].as<string>();
Main::addr = variables["connect"].as<string>();
Main::passw = variables["password"].as<string>();
resourceDir = variables["resources"].as<Files::EscapeHashString>().toStdString();
}
@ -155,22 +155,22 @@ bool Main::init(std::vector<std::string> &content, Files::Collections &collectio
int logLevel = mgr.getInt("logLevel", "General");
Log::SetLevel(logLevel);
if (address.empty())
if (addr.empty())
{
pMain->server = mgr.getString("destinationAddress", "General");
pMain->port = (unsigned short) mgr.getInt("port", "General");
serverPassword = mgr.getString("password", "General");
if (serverPassword.empty())
serverPassword = TES3MP_DEFAULT_PASSW;
passw = mgr.getString("password", "General");
if (passw.empty())
passw = TES3MP_DEFAULT_PASSW;
}
else
{
size_t delimPos = address.find(':');
pMain->server = address.substr(0, delimPos);
pMain->port = atoi(address.substr(delimPos + 1).c_str());
size_t delim_pos = addr.find(':');
pMain->server = addr.substr(0, delim_pos);
pMain->port = atoi(addr.substr(delim_pos + 1).c_str());
}
get().mLocalPlayer->serverPassword = serverPassword;
get().mLocalPlayer->passw = passw;
pMain->mNetworking->connect(pMain->server, pMain->port, content, collections);
RestoreMgr(mgr);

@ -39,8 +39,8 @@ namespace mwmp
private:
static std::string resourceDir;
static std::string address;
static std::string serverPassword;
static std::string addr;
static std::string passw;
Main (const Main&);
///< not implemented
Main& operator= (const Main&);

@ -83,7 +83,7 @@ bool MechanicsHelper::isUsingRangedWeapon(const MWWorld::Ptr& ptr)
MWWorld::ContainerStoreIterator weaponSlot = inventoryStore.getSlot(
MWWorld::InventoryStore::Slot_CarriedRight);
if (weaponSlot != inventoryStore.end() && weaponSlot->getTypeName() == typeid(ESM::Weapon).name())
if (weaponSlot != inventoryStore.end())
{
const ESM::Weapon* weaponRecord = weaponSlot->get<ESM::Weapon>()->mBase;
@ -125,23 +125,6 @@ MWWorld::Ptr MechanicsHelper::getPlayerPtr(const Target& target)
return nullptr;
}
mwmp::Item MechanicsHelper::getItem(const MWWorld::Ptr& itemPtr, int count)
{
mwmp::Item item;
if (itemPtr.getClass().isGold(itemPtr))
item.refId = MWWorld::ContainerStore::sGoldId;
else
item.refId = itemPtr.getCellRef().getRefId();
item.count = count;
item.charge = itemPtr.getCellRef().getCharge();
item.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge();
item.soul = itemPtr.getCellRef().getSoul();
return item;
}
mwmp::Target MechanicsHelper::getTarget(const MWWorld::Ptr& ptr)
{
mwmp::Target target;
@ -410,14 +393,18 @@ void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker)
break;
}
// Add the item if it's missing
if (it == inventoryStore.end())
it = attacker.getClass().getContainerStore(attacker).add(attack.itemId, 1, attacker);
inventoryStore.setSelectedEnchantItem(it);
LOG_APPEND(Log::LOG_VERBOSE, "- itemId: %s", attack.itemId.c_str());
MWBase::Environment::get().getWorld()->castSpell(attacker);
inventoryStore.setSelectedEnchantItem(inventoryStore.end());
if (it != inventoryStore.end())
{
inventoryStore.setSelectedEnchantItem(it);
LOG_APPEND(Log::LOG_VERBOSE, "- itemId: %s", attack.itemId.c_str());
MWBase::Environment::get().getWorld()->castSpell(attacker);
inventoryStore.setSelectedEnchantItem(inventoryStore.end());
}
else
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Could not find item %s used by %s to cast item spell!",
attack.itemId.c_str(), attacker.getCellRef().getRefId().c_str());
}
}
}
@ -465,25 +452,17 @@ void MechanicsHelper::unequipItemsByEffect(const MWWorld::Ptr& ptr, short enchan
MWWorld::Ptr MechanicsHelper::getItemPtrFromStore(const mwmp::Item& item, MWWorld::ContainerStore& store)
{
MWWorld::Ptr closestPtr;
for (MWWorld::ContainerStoreIterator storeIterator = store.begin(); storeIterator != store.end(); ++storeIterator)
{
// Enchantment charges are often in the process of refilling themselves, so don't check for them here
if (Misc::StringUtils::ciEqual(item.refId, storeIterator->getCellRef().getRefId()) &&
item.count == storeIterator->getRefData().getCount() &&
item.charge == storeIterator->getCellRef().getCharge() &&
item.enchantmentCharge == storeIterator->getCellRef().getEnchantmentCharge() &&
Misc::StringUtils::ciEqual(item.soul, storeIterator->getCellRef().getSoul()))
{
// If we have no closestPtr, set it to the Ptr corresponding to this storeIterator; otherwise, make
// sure the storeIterator's enchantmentCharge is closer to our goal than that of the previous closestPtr
if (!closestPtr || abs(storeIterator->getCellRef().getEnchantmentCharge() - item.enchantmentCharge) <
abs(closestPtr.getCellRef().getEnchantmentCharge() - item.enchantmentCharge))
{
closestPtr = *storeIterator;
}
return *storeIterator;
}
}
return closestPtr;
return 0;
}

@ -22,7 +22,6 @@ namespace MechanicsHelper
MWWorld::Ptr getPlayerPtr(const mwmp::Target& target);
mwmp::Item getItem(const MWWorld::Ptr& itemPtr, int count);
mwmp::Target getTarget(const MWWorld::Ptr& ptr);
void clearTarget(mwmp::Target& target);
bool isEmptyTarget(const mwmp::Target& target);

@ -45,7 +45,7 @@ using namespace mwmp;
string listDiscrepancies(PacketPreInit::PluginContainer checksums, PacketPreInit::PluginContainer checksumsResponse)
{
std::ostringstream sstr;
sstr << "Your plugins or their load order don't match the server's. A full comparison is included in your debug window and latest log file. In short, the following discrepancies have been found:\n\n";
sstr << "Your plugins or their load order don't match the server's. A full comparison is included in your client console and latest log file. In short, the following discrepancies have been found:\n\n";
int discrepancyCount = 0;

@ -333,7 +333,7 @@ void ObjectList::placeObjects(MWWorld::CellStore* cellStore)
for (const auto &baseObject : baseObjects)
{
LOG_APPEND(Log::LOG_VERBOSE, "- cellRef: %s %i-%i, count: %i, charge: %i, enchantmentCharge: %.2f, soul: %s",
LOG_APPEND(Log::LOG_VERBOSE, "- cellRef: %s %i-%i, count: %i, charge: %i, enchantmentCharge: %i, soul: %s",
baseObject.refId.c_str(), baseObject.refNum, baseObject.mpNum, baseObject.count, baseObject.charge,
baseObject.enchantmentCharge, baseObject.soul.c_str());

@ -110,7 +110,7 @@ void RecordHelper::overrideCreatureRecord(const mwmp::CreatureRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -164,11 +164,6 @@ void RecordHelper::overrideCreatureRecord(const mwmp::CreatureRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -180,7 +175,7 @@ void RecordHelper::overrideNpcRecord(const mwmp::NpcRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -191,12 +186,12 @@ void RecordHelper::overrideNpcRecord(const mwmp::NpcRecord& record)
{
if (!doesRaceRecordExist(recordData.mRace))
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new NPC record with invalid race provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new NPC record with invalid race provided");
return;
}
else if (!doesClassRecordExist(recordData.mClass))
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new NPC record with invalid class provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new NPC record with invalid class provided");
return;
}
else
@ -277,11 +272,6 @@ void RecordHelper::overrideNpcRecord(const mwmp::NpcRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -293,17 +283,18 @@ void RecordHelper::overrideEnchantmentRecord(const mwmp::EnchantmentRecord& reco
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
bool isExistingId = doesEnchantmentRecordExist(recordData.mId);
MWBase::World *world = MWBase::Environment::get().getWorld();
if (record.baseId.empty())
{
if (recordData.mEffects.mList.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new enchantment record with no effects");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new enchantment record with no effects");
return;
}
else
@ -332,11 +323,6 @@ void RecordHelper::overrideEnchantmentRecord(const mwmp::EnchantmentRecord& reco
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
}
void RecordHelper::overridePotionRecord(const mwmp::PotionRecord& record)
@ -345,7 +331,7 @@ void RecordHelper::overridePotionRecord(const mwmp::PotionRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -388,11 +374,6 @@ void RecordHelper::overridePotionRecord(const mwmp::PotionRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -404,7 +385,7 @@ void RecordHelper::overrideSpellRecord(const mwmp::SpellRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -438,11 +419,9 @@ void RecordHelper::overrideSpellRecord(const mwmp::SpellRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
}
void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
@ -451,7 +430,7 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -462,7 +441,7 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new armor record with invalid enchantmentId %s", recordData.mEnchant.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new armor record with invalid enchantment provided");
return;
}
else
@ -500,13 +479,8 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
if (record.baseOverrides.hasArmorRating)
finalData.mData.mArmor = recordData.mData.mArmor;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -519,11 +493,6 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -535,7 +504,7 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -546,7 +515,7 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new book record with invalid enchantmentId %s", recordData.mEnchant.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new book record with invalid enchantment provided");
return;
}
else
@ -582,13 +551,8 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
if (record.baseOverrides.hasSkillId)
finalData.mData.mSkillId = recordData.mData.mSkillId;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -598,11 +562,6 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -614,7 +573,7 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -625,7 +584,7 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new clothing record with invalid enchantmentId %s", recordData.mEnchant.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new clothing record with invalid enchantment provided");
return;
}
else
@ -655,13 +614,8 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
if (record.baseOverrides.hasValue)
finalData.mData.mValue = recordData.mData.mValue;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -674,11 +628,6 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -690,7 +639,7 @@ void RecordHelper::overrideMiscellaneousRecord(const mwmp::MiscellaneousRecord&
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -730,11 +679,6 @@ void RecordHelper::overrideMiscellaneousRecord(const mwmp::MiscellaneousRecord&
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -746,7 +690,7 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
if (recordData.mId.empty())
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
return;
}
@ -757,7 +701,7 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new weapon record with invalid enchantmentId %s", recordData.mEnchant.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new weapon record with invalid enchantment provided");
return;
}
else
@ -817,13 +761,8 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
if (record.baseOverrides.hasFlags)
finalData.mData.mFlags = recordData.mData.mFlags;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -833,11 +772,6 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);

@ -35,7 +35,7 @@ Networking *Worldstate::getNetworking()
void Worldstate::addRecords()
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_RECORD_DYNAMIC with %i records of type %i",
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Received ID_RECORD_DYNAMIC with %i records of type %i",
recordsCount, recordsType);
if (recordsType == mwmp::RECORD_TYPE::SPELL)
@ -44,7 +44,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- spell record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- spell record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideSpellRecord(record);
@ -56,7 +56,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- potion record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- potion record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overridePotionRecord(record);
@ -68,7 +68,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- enchantment record %s, %i\n-- baseId is %s", record.data.mId.c_str(), record.data.mData.mType,
LOG_APPEND(Log::LOG_ERROR, "- enchantment record %s, %i\n-- baseId is %s", record.data.mId.c_str(), record.data.mData.mType,
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideEnchantmentRecord(record);
@ -80,7 +80,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- creature record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- creature record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideCreatureRecord(record);
@ -92,7 +92,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- NPC record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- NPC record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideNpcRecord(record);
@ -104,7 +104,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- armor record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- armor record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideArmorRecord(record);
@ -116,7 +116,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- book record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- book record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideBookRecord(record);
@ -128,7 +128,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- clothing record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- clothing record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideClothingRecord(record);
@ -140,7 +140,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- miscellaneous record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- miscellaneous record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideMiscellaneousRecord(record);
@ -152,7 +152,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_INFO, "- weapon record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_ERROR, "- weapon record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideWeaponRecord(record);
@ -251,7 +251,7 @@ void Worldstate::sendEnchantmentRecord(const ESM::Enchantment* enchantment)
{
enchantmentRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with enchantment");
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with enchantment");
recordsType = mwmp::RECORD_TYPE::ENCHANTMENT;
@ -267,7 +267,7 @@ void Worldstate::sendPotionRecord(const ESM::Potion* potion)
{
potionRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with potion %s", potion->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with potion %s", potion->mName.c_str());
recordsType = mwmp::RECORD_TYPE::POTION;
@ -283,7 +283,7 @@ void Worldstate::sendSpellRecord(const ESM::Spell* spell)
{
spellRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with spell %s", spell->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with spell %s", spell->mName.c_str());
recordsType = mwmp::RECORD_TYPE::SPELL;
@ -299,7 +299,7 @@ void Worldstate::sendArmorRecord(const ESM::Armor* armor, std::string baseId)
{
armorRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with armor %s", armor->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with armor %s", armor->mName.c_str());
recordsType = mwmp::RECORD_TYPE::ARMOR;
@ -319,7 +319,7 @@ void Worldstate::sendBookRecord(const ESM::Book* book, std::string baseId)
{
bookRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with book %s", book->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with book %s", book->mName.c_str());
recordsType = mwmp::RECORD_TYPE::BOOK;
@ -339,7 +339,7 @@ void Worldstate::sendClothingRecord(const ESM::Clothing* clothing, std::string b
{
clothingRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with clothing %s", clothing->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with clothing %s", clothing->mName.c_str());
recordsType = mwmp::RECORD_TYPE::CLOTHING;
@ -359,7 +359,7 @@ void Worldstate::sendWeaponRecord(const ESM::Weapon* weapon, std::string baseId)
{
weaponRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with weapon %s", weapon->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with weapon %s", weapon->mName.c_str());
recordsType = mwmp::RECORD_TYPE::WEAPON;

@ -89,7 +89,7 @@
#include "WorldstateProcessor.hpp"
#include "worldstate/ProcessorCellCreate.hpp"
#include "worldstate/ProcessorCellReset.hpp"
#include "worldstate/ProcessorCellReplace.hpp"
#include "worldstate/ProcessorRecordDynamic.hpp"
#include "worldstate/ProcessorWorldCollisionOverride.hpp"
#include "worldstate/ProcessorWorldMap.hpp"
@ -186,7 +186,7 @@ void ProcessorInitializer()
ActorProcessor::AddProcessor(new ProcessorActorTest());
WorldstateProcessor::AddProcessor(new ProcessorCellCreate());
WorldstateProcessor::AddProcessor(new ProcessorCellReset());
WorldstateProcessor::AddProcessor(new ProcessorCellReplace());
WorldstateProcessor::AddProcessor(new ProcessorRecordDynamic());
WorldstateProcessor::AddProcessor(new ProcessorWorldCollisionOverride());
WorldstateProcessor::AddProcessor(new ProcessorWorldMap());

@ -28,7 +28,7 @@ namespace mwmp
if (!isRequest())
{
LOG_APPEND(Log::LOG_INFO, "- refId: %s, count: %i, charge: %i, enchantmentCharge: %f, soul: %s",
LOG_APPEND(Log::LOG_INFO, "- refId: %s, count: %i, charge: %f, enchantmentCharge: %f, soul: %s",
player->usedItem.refId.c_str(), player->usedItem.count, player->usedItem.charge,
player->usedItem.enchantmentCharge, player->usedItem.soul.c_str());
@ -36,11 +36,7 @@ namespace mwmp
MWWorld::InventoryStore &inventoryStore = playerPtr.getClass().getInventoryStore(playerPtr);
MWWorld::Ptr itemPtr = MechanicsHelper::getItemPtrFromStore(player->usedItem, inventoryStore);
if (itemPtr)
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(itemPtr);
else
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Cannot use non-existent item %s", player->usedItem.refId.c_str());
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(itemPtr);
}
}
};

@ -0,0 +1,23 @@
#ifndef OPENMW_PROCESSORCELLREPLACE_HPP
#define OPENMW_PROCESSORCELLREPLACE_HPP
#include "../WorldstateProcessor.hpp"
namespace mwmp
{
class ProcessorCellReplace : public WorldstateProcessor
{
public:
ProcessorCellReplace()
{
BPP_INIT(ID_CELL_REPLACE)
}
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
{
// Placeholder
}
};
}
#endif //OPENMW_PROCESSORCELLREPLACE_HPP

@ -1,23 +0,0 @@
#ifndef OPENMW_PROCESSORCELLRESET_HPP
#define OPENMW_PROCESSORCELLRESET_HPP
#include "../WorldstateProcessor.hpp"
namespace mwmp
{
class ProcessorCellReset : public WorldstateProcessor
{
public:
ProcessorCellReset()
{
BPP_INIT(ID_CELL_RESET)
}
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
{
// Placeholder
}
};
}
#endif //OPENMW_PROCESSORCELLRESET_HPP

@ -80,24 +80,18 @@ namespace MWScript
/*
Start of tes3mp change (major)
Allow unilateral item removal on this client from client scripts and dialogue (but not console commands)
to prevent infinite loops in certain mods. Otherwise, expect the server's reply to our packet to do the
removal instead, except for changes to player inventories which still require the PlayerInventory to be
reworked.
Disable unilateral item addition on this client and expect the server's reply to our
packet to do it instead, except for changes to player inventories which still require
the PlayerInventory to be reworked
*/
unsigned char packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
MWWorld::Ptr itemPtr;
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() || packetOrigin != mwmp::CLIENT_CONSOLE)
itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr);
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() )
{
MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr);
/*
End of tes3mp change (major)
*/
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
{
// The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory
std::string msgBox;
std::string itemName = itemPtr.getClass().getName(itemPtr);
@ -119,12 +113,11 @@ namespace MWScript
Send an ID_CONTAINER packet every time an item is added to a Ptr
that doesn't belong to a DedicatedPlayer
*/
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
(!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr)))
else if (!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr))
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = packetOrigin;
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->cell = *ptr.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::ADD;
objectList->containerSubAction = mwmp::BaseObjectList::NONE;
@ -209,15 +202,13 @@ namespace MWScript
/*
Start of tes3mp change (major)
Allow unilateral item removal on this client from client scripts and dialogue (but not console commands)
to prevent infinite loops in certain mods. Otherwise, expect the server's reply to our packet to do the
removal instead, except for changes to player inventories which still require the PlayerInventory to be
reworked.
Disable unilateral item removal on this client and expect the server's reply to our
packet to do it instead, except for changes to player inventories which still require
the PlayerInventory to be reworked
*/
unsigned char packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
int numRemoved = 0;
if (ptr == MWMechanics::getPlayer() || packetOrigin != mwmp::CLIENT_CONSOLE)
if (ptr == MWMechanics::getPlayer())
numRemoved = store.remove(item, count, ptr);
// Spawn a messagebox (only for items removed from player's inventory)
@ -249,12 +240,11 @@ namespace MWScript
Send an ID_CONTAINER packet every time an item is removed from a Ptr
that doesn't belong to a DedicatedPlayer
*/
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
(!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr)))
else if (!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr))
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = packetOrigin;
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->cell = *ptr.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::REMOVE;
objectList->containerSubAction = mwmp::BaseObjectList::NONE;

@ -63,7 +63,7 @@ namespace MWScript
Send an ID_PLAYER_JOURNAL packet every time a new journal entry is added
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && !MWBase::Environment::get().getJournal()->hasEntry(quest, index))
if (!MWBase::Environment::get().getJournal()->hasEntry(quest, index))
mwmp::Main::get().getLocalPlayer()->sendJournalEntry(quest, index, ptr);
/*
End of tes3mp addition
@ -99,8 +99,7 @@ namespace MWScript
Send an ID_PLAYER_JOURNAL packet every time a journal index is set
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
mwmp::Main::get().getLocalPlayer()->sendJournalIndex(quest, index);
mwmp::Main::get().getLocalPlayer()->sendJournalIndex(quest, index);
/*
End of tes3mp addition
*/
@ -138,8 +137,7 @@ namespace MWScript
Send an ID_PLAYER_TOPIC packet every time a new topic is added
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
MWBase::Environment::get().getDialogueManager()->isNewTopic(Misc::StringUtils::lowerCase(topic)))
if (MWBase::Environment::get().getDialogueManager()->isNewTopic(Misc::StringUtils::lowerCase(topic)))
mwmp::Main::get().getLocalPlayer()->sendTopic(Misc::StringUtils::lowerCase(topic));
/*
End of tes3mp addition

@ -613,17 +613,13 @@ namespace MWScript
Start of tes3mp addition
Send an ID_OBJECT_STATE packet whenever an object is enabled, as long as
the player is logged in on the server, the object is still disabled, and our last
packet regarding its state did not already attempt to enable it (to prevent
packet spam)
the player has finished character generation and the object wasn't already
enabled previously
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
{
if (ref.isInCell() && !ref.getRefData().isEnabled() &&
ref.getRefData().getLastCommunicatedState() != MWWorld::RefData::StateCommunication::Enabled)
if (ref.isInCell() && !ref.getRefData().isEnabled())
{
ref.getRefData().setLastCommunicatedState(MWWorld::RefData::StateCommunication::Enabled);
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(getContextType());
@ -654,18 +650,14 @@ namespace MWScript
/*
Start of tes3mp addition
Send an ID_OBJECT_STATE packet whenever an object should be disabled, as long as
the player is logged in on the server, the object is still enabled, and our last
packet regarding its state did not already attempt to disable it (to prevent
packet spam)
Send an ID_OBJECT_STATE packet whenever an object is disabled, as long as
the player has finished character generation and the object wasn't already
disabled previously
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
{
if (ref.isInCell() && ref.getRefData().isEnabled() &&
ref.getRefData().getLastCommunicatedState() != MWWorld::RefData::StateCommunication::Disabled)
if (ref.isInCell() && ref.getRefData().isEnabled())
{
ref.getRefData().setLastCommunicatedState(MWWorld::RefData::StateCommunication::Disabled);
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(getContextType());

@ -9,7 +9,6 @@
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/ObjectList.hpp"
#include "../mwmp/ScriptController.hpp"
/*
@ -106,14 +105,11 @@ namespace MWScript
Send an ID_VIDEO_PLAY packet every time a video is played
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addVideoPlay(name, allowSkipping);
objectList->sendVideoPlay();
}
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addVideoPlay(name, allowSkipping);
objectList->sendVideoPlay();
/*
End of tes3mp addition
*/
@ -220,14 +216,11 @@ namespace MWScript
Send an ID_OBJECT_LOCK packet every time an object is locked
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, lockLevel);
objectList->sendObjectLock();
}
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, lockLevel);
objectList->sendObjectLock();
/*
End of tes3mp addition
*/
@ -273,14 +266,11 @@ namespace MWScript
Send an ID_OBJECT_LOCK packet every time an object is unlocked
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, 0);
objectList->sendObjectLock();
}
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, 0);
objectList->sendObjectLock();
/*
End of tes3mp addition
*/
@ -780,14 +770,11 @@ namespace MWScript
Send an ID_OBJECT_DELETE packet every time an object is deleted
through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectDelete(ptr);
objectList->sendObjectDelete();
}
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectDelete(ptr);
objectList->sendObjectDelete();
/*
End of tes3mp addition
*/

@ -470,24 +470,18 @@ namespace MWScript
// make sure a spell with this ID actually exists.
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (id);
/*
Start of tes3mp change (major)
ptr.getClass().getCreatureStats (ptr).getSpells().add (id);
Only add the spell if the target doesn't already have it
/*
Start of tes3mp addition
Send an ID_PLAYER_SPELLBOOK packet every time a player gains a spell here
Send an ID_PLAYER_SPELLBOOK packet every time a player gains a spell
through a script
*/
MWMechanics::Spells &spells = ptr.getClass().getCreatureStats(ptr).getSpells();
if (!spells.hasSpell(id))
{
spells.add(id);
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && ptr == MWMechanics::getPlayer())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::ADD);
}
if (ptr == MWMechanics::getPlayer())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::ADD);
/*
End of tes3mp change (major)
End of tes3mp addition
*/
}
};
@ -504,32 +498,26 @@ namespace MWScript
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
/*
Start of tes3mp change (major)
Only remove the spell if the target has it
ptr.getClass().getCreatureStats (ptr).getSpells().remove (id);
Send an ID_PLAYER_SPELLBOOK packet every time a player loses a spell here
*/
MWMechanics::Spells &spells = ptr.getClass().getCreatureStats(ptr).getSpells();
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
if (spells.hasSpell(id))
if (ptr == MWMechanics::getPlayer() &&
id == wm->getSelectedSpell())
{
ptr.getClass().getCreatureStats(ptr).getSpells().remove(id);
if (ptr == MWMechanics::getPlayer())
{
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
wm->unsetSelectedSpell();
}
if (id == wm->getSelectedSpell())
wm->unsetSelectedSpell();
/*
Start of tes3mp addition
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::REMOVE);
}
}
Send an ID_PLAYER_SPELLBOOK packet every time a player loses a spell
through a script
*/
if (ptr == MWMechanics::getPlayer())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::REMOVE);
/*
End of tes3mp change (major)
End of tes3mp addition
*/
}
};

@ -63,14 +63,15 @@ namespace MWScript
Prevent players from changing their own scale
Send an ID_OBJECT_SCALE every time an object's scale is changed through a script
Send an ID_OBJECT_SCALE every time an object's
scale is changed through a script
*/
if (ptr == MWMechanics::getPlayer())
{
MWBase::Environment::get().getWindowManager()->
messageBox("You can't change your own scale in multiplayer. Only the server can.");
}
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && ptr.isInCell() && ptr.getCellRef().getScale() != scale)
else if (ptr.isInCell() && ptr.getCellRef().getScale() != scale)
{
// Ignore attempts to change another player's scale
if (mwmp::PlayerList::isDedicatedPlayer(ptr))
@ -503,44 +504,6 @@ namespace MWScript
ref.getPtr().getCellRef().setPosition(pos);
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos);
placed.getClass().adjustPosition(placed, true);
/*
Start of tes3mp addition
Send an ID_OBJECT_PLACE or ID_OBJECT_SPAWN packet every time an object is placed
in the world through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (placed.getClass().isActor())
{
objectList->addObjectSpawn(placed);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(placed);
objectList->sendObjectPlace();
}
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp change (major)
Instead of actually keeping this object as is, delete it after sending the packet
and wait for the server to send it back with a unique mpNum of its own
*/
MWBase::Environment::get().getWorld()->deleteObject(placed);
/*
End of tes3mp change (major)
*/
}
}
};
@ -589,44 +552,6 @@ namespace MWScript
ref.getPtr().getCellRef().setPosition(pos);
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos);
placed.getClass().adjustPosition(placed, true);
/*
Start of tes3mp addition
Send an ID_OBJECT_PLACE or ID_OBJECT_SPAWN packet every time an object is placed
in the world through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (placed.getClass().isActor())
{
objectList->addObjectSpawn(placed);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(placed);
objectList->sendObjectPlace();
}
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp change (major)
Instead of actually keeping this object as is, delete it after sending the packet
and wait for the server to send it back with a unique mpNum of its own
*/
MWBase::Environment::get().getWorld()->deleteObject(placed);
/*
End of tes3mp change (major)
*/
}
};
@ -674,22 +599,19 @@ namespace MWScript
Send an ID_OBJECT_PLACE or ID_OBJECT_SPAWN packet every time an object is placed
in the world through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (ptr.getClass().isActor())
{
objectList->addObjectSpawn(ptr);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(ptr);
objectList->sendObjectPlace();
}
if (ptr.getClass().isActor())
{
objectList->addObjectSpawn(ptr);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(ptr);
objectList->sendObjectPlace();
}
/*
End of tes3mp addition

@ -194,25 +194,6 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr,
if (ptr.getRefData().getCount() <= count)
return end();
MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-count);
/*
Start of tes3mp addition
Send an ID_PLAYER_INVENTORY packet every time an item stack gets added for a player here
*/
Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (container == player && this == &player.getClass().getContainerStore(player))
{
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
if (!localPlayer->isReceivingInventory)
localPlayer->sendItemChange(ptr, ptr.getRefData().getCount() - count, mwmp::InventoryChanges::ADD);
}
/*
End of tes3mp addition
*/
const std::string script = it->getClass().getScript(*it);
if (!script.empty())
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it);
@ -385,18 +366,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1);
}
/*
Start of tes3mp change (major)
Disable the listener here because it keeps causing crashes; this should only be
a temporary solution that doesn't affect much anyway given that the listener is
only used in relation to light-emitting items
*/
//if (mListener)
// mListener->itemAdded(item, count);
/*
End of tes3mp change (major)
*/
if (mListener)
mListener->itemAdded(item, count);
return it;
}
@ -522,18 +493,8 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
flagAsModified();
/*
Start of tes3mp change (major)
Disable the listener here because it keeps causing crashes; this should only be
a temporary solution that doesn't affect much anyway given that the listener is
only used in relation to light-emitting items
*/
//if (mListener)
// mListener->itemRemoved(item, count - toRemove);
/*
End of tes3mp change (major)
*/
if (mListener)
mListener->itemRemoved(item, count - toRemove);
// number of removed items
return count - toRemove;

@ -13,7 +13,6 @@
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
#include "../mwmp/Main.hpp"
#include "../mwmp/CellController.hpp"
#include "../mwmp/PlayerList.hpp"
@ -252,19 +251,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) con
{
// Object has been deleted
// This should no longer happen, since the new remove function will unequip first
/*
Start of tes3mp change (major)
Instead of throwing an error, display an error log message with information about
the item
*/
//throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object");
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Invalid slot, make sure you are not calling RefData::setCount for a container object\n- item was %s",
mSlots[slot]->getCellRef().getRefId().c_str());
/*
End of tes3mp change (major)
*/
throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object");
}
return mSlots[slot];

@ -138,34 +138,6 @@ namespace MWWorld
const ESM::AnimationState& getAnimationState() const;
ESM::AnimationState& getAnimationState();
/*
Start of tes3mp addition
Track the last state communicated to the server for this reference,
to avoid packet spam when the server denies our state change request or
is slow to reply
*/
enum StateCommunication
{
None = 0,
Enabled = 1,
Disabled = 2
};
private:
short mLastCommunicatedState = StateCommunication::None;
public:
short getLastCommunicatedState() { return mLastCommunicatedState; };
void setLastCommunicatedState(short communicationState) { mLastCommunicatedState = communicationState; };
/*
End of tes3mp addition
*/
};
}

@ -489,9 +489,9 @@ namespace MWWorld
Start of tes3mp addition
Send an ID_PLAYER_CELL_STATE packet with all cell states stored in LocalPlayer
and then clear them, but only if the player is logged in on the server
and then clear them, but only if the player has finished character generation
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
{
mwmp::Main::get().getLocalPlayer()->sendCellStates();
mwmp::Main::get().getLocalPlayer()->clearCellStates();
@ -631,9 +631,9 @@ namespace MWWorld
Start of tes3mp addition
Send an ID_PLAYER_CELL_STATE packet with all cell states stored in LocalPlayer
and then clear them, but only if the player is logged in on the server
and then clear them, but only if the player has finished character generation
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
{
mwmp::Main::get().getLocalPlayer()->sendCellStates();
mwmp::Main::get().getLocalPlayer()->clearCellStates();

@ -282,16 +282,24 @@ namespace MWWorld
/*
Start of tes3mp change (major)
Spawn at 0, -7 by default
If Pelagiad exists, spawn there; otherwise, spawn at 0 ,0
*/
const int cellSize = 8192;
pos.pos[0] = cellSize / 2;
pos.pos[1] = cellSize * -7 + cellSize / 2;
pos.pos[2] = 0;
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos, true);
if (findExteriorPosition("Pelagiad", pos))
{
changeToExteriorCell(pos, true);
fixPosition(getPlayerPtr());
}
else
{
const int cellSize = 8192;
pos.pos[0] = cellSize / 2;
pos.pos[1] = cellSize / 2;
pos.pos[2] = 0;
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos, true);
}
/*
End of tes3mp change (major)
*/
@ -3258,11 +3266,10 @@ namespace MWWorld
If this actor is a LocalPlayer or LocalActor, get their Attack and prepare
it for sending
Set the attack details before going through with the casting, in case it's
a one use item that would get removed through the casting (like a scroll)
*/
{
cast.cast(*inv.getSelectedEnchantItem());
mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(actor);
if (localAttack)
@ -3272,8 +3279,6 @@ namespace MWWorld
localAttack->itemId = inv.getSelectedEnchantItem()->getCellRef().getRefId();
localAttack->shouldSend = true;
}
cast.cast(*inv.getSelectedEnchantItem());
}
/*
End of tes3mp addition

@ -202,7 +202,7 @@ add_component_dir (openmw-mp/Packets/Object
add_component_dir (openmw-mp/Packets/Worldstate
WorldstatePacket
PacketCellCreate PacketCellReset PacketRecordDynamic PacketWorldCollisionOverride PacketWorldMap
PacketCellCreate PacketCellReplace PacketRecordDynamic PacketWorldCollisionOverride PacketWorldMap
PacketWorldRegionAuthority PacketWorldTime PacketWorldWeather
)

@ -232,8 +232,6 @@ namespace mwmp
}
RakNet::RakNetGUID guid;
std::string serverPassword;
GUIMessageBox guiMessageBox;
// Track only the indexes of the attributes that have been changed,
@ -297,6 +295,7 @@ namespace mwmp
std::string birthsign;
std::string chatMessage;
CharGenState charGenState;
std::string passw;
std::string sound;
Animation animation;

@ -1,5 +1,5 @@
#include "../Packets/Worldstate/PacketCellCreate.hpp"
#include "../Packets/Worldstate/PacketCellReset.hpp"
#include "../Packets/Worldstate/PacketCellReplace.hpp"
#include "../Packets/Worldstate/PacketRecordDynamic.hpp"
#include "../Packets/Worldstate/PacketWorldCollisionOverride.hpp"
#include "../Packets/Worldstate/PacketWorldMap.hpp"
@ -20,7 +20,7 @@ inline void AddPacket(mwmp::WorldstatePacketController::packets_t *packets, RakN
mwmp::WorldstatePacketController::WorldstatePacketController(RakNet::RakPeerInterface *peer)
{
AddPacket<PacketCellCreate>(&packets, peer);
AddPacket<PacketCellReset>(&packets, peer);
AddPacket<PacketCellReplace>(&packets, peer);
AddPacket<PacketRecordDynamic>(&packets, peer);
AddPacket<PacketWorldCollisionOverride>(&packets, peer);
AddPacket<PacketWorldMap>(&packets, peer);

@ -104,7 +104,7 @@ enum GameMessages
ID_GAME_PREINIT,
ID_CELL_CREATE,
ID_CELL_RESET,
ID_CELL_REPLACE,
ID_RECORD_DYNAMIC,
ID_WORLD_COLLISION_OVERRIDE,
ID_WORLD_MAP,

@ -1,3 +1,7 @@
//
// Created by koncord on 28.04.16.
//
#include <components/openmw-mp/NetworkMessages.hpp>
#include "PacketHandshake.hpp"
@ -13,8 +17,8 @@ void PacketHandshake::Packet(RakNet::BitStream *bs, bool send)
{
PlayerPacket::Packet(bs, send);
if (!RW(player->npc.mName, send, true, maxNameLength) ||
!RW(player->serverPassword, send, true, maxPasswordLength))
if (!RW(player->npc.mName, send, true, maxNameLen) ||
!RW(player->passw, send, true, maxPasswLen))
{
packetValid = false;
return;

@ -16,8 +16,8 @@ namespace mwmp
virtual void Packet(RakNet::BitStream *bs, bool send);
const static uint32_t maxNameLength = 256;
const static uint32_t maxPasswordLength = 256;
const static uint32_t maxNameLen = 256;
const static uint32_t maxPasswLen = 256;
};
}

@ -0,0 +1,17 @@
#include "PacketCellReplace.hpp"
#include <components/openmw-mp/NetworkMessages.hpp>
using namespace mwmp;
PacketCellReplace::PacketCellReplace(RakNet::RakPeerInterface *peer) : WorldstatePacket(peer)
{
packetID = ID_CELL_REPLACE;
orderChannel = CHANNEL_SYSTEM;
}
void PacketCellReplace::Packet(RakNet::BitStream *bs, bool send)
{
WorldstatePacket::Packet(bs, send);
// Placeholder
}

@ -1,18 +1,18 @@
#ifndef OPENMW_PACKETCELLRESET_HPP
#define OPENMW_PACKETCELLRESET_HPP
#ifndef OPENMW_PACKETCELLREPLACE_HPP
#define OPENMW_PACKETCELLREPLACE_HPP
#include <components/openmw-mp/Packets/Worldstate/WorldstatePacket.hpp>
#include <components/openmw-mp/NetworkMessages.hpp>
namespace mwmp
{
class PacketCellReset: public WorldstatePacket
class PacketCellReplace: public WorldstatePacket
{
public:
PacketCellReset(RakNet::RakPeerInterface *peer);
PacketCellReplace(RakNet::RakPeerInterface *peer);
virtual void Packet(RakNet::BitStream *bs, bool send);
};
}
#endif //OPENMW_PACKETCELLRESET_HPP
#endif //OPENMW_PACKETCELLREPLACE_HPP

@ -1,17 +0,0 @@
#include "PacketCellReset.hpp"
#include <components/openmw-mp/NetworkMessages.hpp>
using namespace mwmp;
PacketCellReset::PacketCellReset(RakNet::RakPeerInterface *peer) : WorldstatePacket(peer)
{
packetID = ID_CELL_RESET;
orderChannel = CHANNEL_SYSTEM;
}
void PacketCellReset::Packet(RakNet::BitStream *bs, bool send)
{
WorldstatePacket::Packet(bs, send);
// Placeholder
}

@ -117,7 +117,7 @@ std::string Utils::toString(int num)
return stream.str();
}
std::string Utils::replaceString(const string& source, const char* find, const char* replace)
string Utils::replaceString(const string& source, const char* find, const char* replace)
{
unsigned int find_len = strlen(find);
unsigned int replace_len = strlen(replace);
@ -175,44 +175,37 @@ unsigned int ::Utils::crc32Checksum(const std::string &file)
return crc32.checksum();
}
std::string Utils::getOperatingSystemType()
void Utils::printVersion(std::string appName, std::string version, std::string commitHash, int protocol)
{
cout << appName << " " << version;
cout << " (";
#if defined(_WIN32)
return "Windows";
cout << "Windows";
#elif defined(__linux)
return "Linux";
cout << "Linux";
#elif defined(__APPLE__)
return "OS X";
cout << "OS X";
#else
return "Unknown OS";
cout << "Unknown OS";
#endif
}
std::string Utils::getArchitectureType()
{
cout << " ";
#if defined(__x86_64__) || defined(_M_X64)
return "64-bit";
#elif defined(__i386__) || defined(_M_I86) || defined(_M_IX86)
return "32-bit";
cout << "64-bit";
#elif defined(__i386__) || defined(_M_I86)
cout << "32-bit";
#elif defined(__ARM_ARCH)
std::string architectureType = "ARMv" + __ARM_ARCH;
cout << "ARMv" << __ARM_ARCH << " ";
#ifdef __aarch64__
architectureType = architectureType + " 64-bit";
cout << "64-bit";
#else
architectureType = architectureType + " 32-bit";
cout << "32-bit";
#endif
return architectureType;
#else
return "Unknown architecture";
cout << "Unknown architecture";
#endif
}
void Utils::printVersion(std::string appName, std::string version, std::string commitHash, int protocol)
{
cout << appName << " " << version;
cout << " (" << getOperatingSystemType() << " " << getArchitectureType() << ")" << endl;
cout << ")" << endl;
cout << "Protocol version: " << protocol << endl;
cout << "Oldest compatible commit hash: " << commitHash.substr(0, 10) << endl;
cout << "Commit hash: " << commitHash.substr(0, 10) << endl;
cout << "------------------------------------------------------------" << endl;
}

@ -60,9 +60,6 @@ namespace Utils
unsigned int crc32Checksum(const std::string &file);
std::string getOperatingSystemType();
std::string getArchitectureType();
void printVersion(std::string appName, std::string version, std::string commitHash, int protocol);
void printWithWidth(std::ostringstream &sstr, std::string str, size_t width);

@ -7,7 +7,6 @@
#define TES3MP_DEFAULT_PASSW "SuperPassword"
#define TES3MP_MASTERSERVER_PASSW "12345"
#define TES3MP_CREDITS_CHECKSUM "BAEFF920"
#define TES3MP_CREDITS_CHECKSUM "BC39D2E9"
#endif //OPENMW_VERSION_HPP

@ -9,7 +9,7 @@ logLevel = 1
password =
[Plugins]
home = ./server
home = ~/local/openmw/tes3mp
plugins = serverCore.lua
[MasterServer]

@ -4,14 +4,14 @@ tes3mp Credits
Programmers
----------------
Stanislav Zhukov (Koncord) - Architecture, networking & scripting systems, player sync, server browser & master server
Stanislav Zhukov (Koncord) - Overall architecture, networking & scripting systems, player sync, server browser & master server
David Cernat - World, NPC & quest sync, player sync improvements, state saving & loading, extensive bug fixes
Additional programming
----------------------
Grim Kriegor - Linux deployment scripts, Lua teleportation commands, early script fixes
Grim Kriegor - Lua teleportation commands, early script fixes
Battlerax - Various small fixes
@ -26,7 +26,6 @@ Community administrators
Community moderators
--------------------
Lysol
Michael Fitzmayer (mupf)
Nac
NicholasAH
@ -54,7 +53,6 @@ Translation
Super special thanks
--------------------
Alexander Ovsyannikov
Bret Curtis (psi29a)
Gabriel Pascu (iGrebla)
greetasdf
@ -75,9 +73,8 @@ Special thanks
Gluka
Goodevil
Ignatious
James Wards of Gore Corps LAN Club (gorecorps.co.nz)
James Wards of Gore Corps LAN Club (www.gorecorps.co.nz)
Jeremiah
Kyle Willey of Loreshaper Games (steempeak.com/@loreshapergames)
Lewis Sadlier
Luc Keating
Michael Zagar (Zoops)
@ -87,7 +84,6 @@ Special thanks
Scorcio
Simon Nemes
Texafornian
Thrud
Zaphida
All the developers of OpenMW for creating an amazing open source project

Loading…
Cancel
Save