diff --git a/apps/openmw-mp/Networking.cpp b/apps/openmw-mp/Networking.cpp index c3067f58c..bfda43863 100644 --- a/apps/openmw-mp/Networking.cpp +++ b/apps/openmw-mp/Networking.cpp @@ -25,6 +25,8 @@ #include "processors/ObjectProcessor.hpp" #include "processors/WorldstateProcessor.hpp" +#include "handleInput.cpp" + using namespace mwmp; using namespace std; @@ -496,6 +498,20 @@ void signalHandler(int signum) } } +#ifdef _WIN32 +BOOL WINAPI sigIntHandler(_In_ DWORD dwCtrlType) { + switch (dwCtrlType) + { + case CTRL_C_EVENT: + signalHandler(15); + return TRUE; + default: + // Pass signal on to the next handler + return FALSE; + } +} +#endif + int Networking::mainLoop() { RakNet::Packet *packet; @@ -506,16 +522,15 @@ int Networking::mainLoop() sigIntHandler.sa_handler = signalHandler; sigemptyset(&sigIntHandler.sa_mask); sigIntHandler.sa_flags = 0; + sigaction(SIGTERM, &sigIntHandler, NULL); + sigaction(SIGINT, &sigIntHandler, NULL); +#else + SetConsoleCtrlHandler(sigIntHandler, TRUE); #endif while (running and !killLoop) { -#ifndef _WIN32 - sigaction(SIGTERM, &sigIntHandler, NULL); - sigaction(SIGINT, &sigIntHandler, NULL); -#endif - if (kbhit() && getch() == '\n') - break; + mwmp_input::handler(); for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive()) { if (getMasterClient()->Process(packet)) diff --git a/apps/openmw-mp/Script/Functions/Objects.cpp b/apps/openmw-mp/Script/Functions/Objects.cpp index e99ce59a4..db5b9c16b 100644 --- a/apps/openmw-mp/Script/Functions/Objects.cpp +++ b/apps/openmw-mp/Script/Functions/Objects.cpp @@ -484,6 +484,13 @@ void ObjectFunctions::SetObjectRotation(double x, double y, double z) noexcept tempObject.position.rot[2] = z; } +void ObjectFunctions::SetObjectSound(const char* soundId, double volume, double pitch) noexcept +{ + tempObject.soundId = soundId; + tempObject.volume = volume; + tempObject.pitch = pitch; +} + void ObjectFunctions::SetObjectSummonState(bool summonState) noexcept { tempObject.isSummon = summonState; diff --git a/apps/openmw-mp/Script/Functions/Objects.hpp b/apps/openmw-mp/Script/Functions/Objects.hpp index da9d9bace..9e3c17236 100644 --- a/apps/openmw-mp/Script/Functions/Objects.hpp +++ b/apps/openmw-mp/Script/Functions/Objects.hpp @@ -102,6 +102,7 @@ {"SetObjectDroppedByPlayerState", ObjectFunctions::SetObjectDroppedByPlayerState},\ {"SetObjectPosition", ObjectFunctions::SetObjectPosition},\ {"SetObjectRotation", ObjectFunctions::SetObjectRotation},\ + {"SetObjectSound", ObjectFunctions::SetObjectSound},\ \ {"SetObjectSummonState", ObjectFunctions::SetObjectSummonState},\ {"SetObjectSummonEffectId", ObjectFunctions::SetObjectSummonEffectId},\ @@ -934,6 +935,8 @@ public: */ static void SetObjectRotation(double x, double y, double z) noexcept; + static void SetObjectSound(const char* soundId, double volume, double pitch) noexcept; + /** * \brief Set the summon state of the temporary object stored on the server. * diff --git a/apps/openmw-mp/Script/Functions/RecordsDynamic.cpp b/apps/openmw-mp/Script/Functions/RecordsDynamic.cpp index 6654ce2cb..08a5d335f 100644 --- a/apps/openmw-mp/Script/Functions/RecordsDynamic.cpp +++ b/apps/openmw-mp/Script/Functions/RecordsDynamic.cpp @@ -36,6 +36,7 @@ ProbeRecord tempProbe; RepairRecord tempRepair; ScriptRecord tempScript; StaticRecord tempStatic; +SoundRecord tempSound; BaseOverrides tempOverrides; @@ -85,6 +86,7 @@ void RecordsDynamicFunctions::ClearRecords() noexcept WorldstateFunctions::writeWorldstate.repairRecords.clear(); WorldstateFunctions::writeWorldstate.scriptRecords.clear(); WorldstateFunctions::writeWorldstate.staticRecords.clear(); + WorldstateFunctions::writeWorldstate.soundRecords.clear(); } unsigned short RecordsDynamicFunctions::GetRecordType() noexcept @@ -399,6 +401,8 @@ void RecordsDynamicFunctions::SetRecordId(const char* id) noexcept tempScript.data.mId = id; else if (writeRecordsType == mwmp::RECORD_TYPE::STATIC) tempStatic.data.mId = id; + else if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + tempSound.data.mId = id; else LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Tried to set id for record type %i which lacks that property", writeRecordsType); } @@ -453,6 +457,8 @@ void RecordsDynamicFunctions::SetRecordBaseId(const char* baseId) noexcept tempScript.baseId = baseId; else if (writeRecordsType == mwmp::RECORD_TYPE::STATIC) tempStatic.baseId = baseId; + else if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + tempSound.baseId = baseId; else LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Tried to set baseId for record type %i which lacks that property", writeRecordsType); } @@ -1407,6 +1413,8 @@ void RecordsDynamicFunctions::SetRecordSound(const char* sound) noexcept if (writeRecordsType == mwmp::RECORD_TYPE::LIGHT) tempLight.data.mSound = sound; + else if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + tempSound.data.mSound = sound; else { LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Tried to set sound for record type %i which lacks that property", writeRecordsType); @@ -1416,6 +1424,51 @@ void RecordsDynamicFunctions::SetRecordSound(const char* sound) noexcept tempOverrides.hasSound = true; } +void RecordsDynamicFunctions::SetRecordVolume(double volume) noexcept +{ + unsigned short writeRecordsType = WorldstateFunctions::writeWorldstate.recordsType; + + if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + tempSound.data.mData.mVolume = volume; + else + { + LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Tried to set sound for record type %i which lacks that property", writeRecordsType); + return; + } + + tempOverrides.hasVolume = true; +} + +void RecordsDynamicFunctions::SetRecordMinRange(double minRange) noexcept +{ + unsigned short writeRecordsType = WorldstateFunctions::writeWorldstate.recordsType; + + if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + tempSound.data.mData.mMinRange = minRange; + else + { + LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Tried to set sound for record type %i which lacks that property", writeRecordsType); + return; + } + + tempOverrides.hasMinRange = true; +} + +void RecordsDynamicFunctions::SetRecordMaxRange(double maxRange) noexcept +{ + unsigned short writeRecordsType = WorldstateFunctions::writeWorldstate.recordsType; + + if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + tempSound.data.mData.mMinRange = maxRange; + else + { + LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Tried to set sound for record type %i which lacks that property", writeRecordsType); + return; + } + + tempOverrides.hasMaxRange = true; +} + void RecordsDynamicFunctions::SetRecordOpenSound(const char* sound) noexcept { unsigned short writeRecordsType = WorldstateFunctions::writeWorldstate.recordsType; @@ -1860,6 +1913,12 @@ void RecordsDynamicFunctions::AddRecord() noexcept WorldstateFunctions::writeWorldstate.staticRecords.push_back(tempStatic); tempStatic = {}; } + else if (writeRecordsType == mwmp::RECORD_TYPE::SOUND) + { + tempSound.baseOverrides = tempOverrides; + WorldstateFunctions::writeWorldstate.soundRecords.push_back(tempSound); + tempSound = {}; + } effectCount = 0; tempOverrides = {}; diff --git a/apps/openmw-mp/Script/Functions/RecordsDynamic.hpp b/apps/openmw-mp/Script/Functions/RecordsDynamic.hpp index f62da688c..ffda546c2 100644 --- a/apps/openmw-mp/Script/Functions/RecordsDynamic.hpp +++ b/apps/openmw-mp/Script/Functions/RecordsDynamic.hpp @@ -99,6 +99,8 @@ {"SetRecordAIServices", RecordsDynamicFunctions::SetRecordAIServices},\ \ {"SetRecordSound", RecordsDynamicFunctions::SetRecordSound},\ + {"SetRecordMinRange", RecordsDynamicFunctions::SetRecordMinRange},\ + {"SetRecordMaxRange", RecordsDynamicFunctions::SetRecordMaxRange},\ {"SetRecordOpenSound", RecordsDynamicFunctions::SetRecordOpenSound},\ {"SetRecordCloseSound", RecordsDynamicFunctions::SetRecordCloseSound},\ \ @@ -859,6 +861,33 @@ public: */ static void SetRecordSound(const char* sound) noexcept; + /** + * \brief Set the volume of the temporary record stored on the server for the currently + * specified record type. + * + * \param volume The volume of the record. + * \return void + */ + static void SetRecordVolume(double volume) noexcept; + + /** + * \brief Set the minimum range of the temporary record stored on the server for the currently + * specified record type. + * + * \param volume The minimum range of the record. + * \return void + */ + static void SetRecordMinRange(double minRange) noexcept; + + /** + * \brief Set the maximum range of the temporary record stored on the server for the currently + * specified record type. + * + * \param volume The maximum range of the record. + * \return void + */ + static void SetRecordMaxRange(double maxRange) noexcept; + /** * \brief Set the opening sound of the temporary record stored on the server for the * currently specified record type. diff --git a/apps/openmw-mp/Script/ScriptFunctions.hpp b/apps/openmw-mp/Script/ScriptFunctions.hpp index fe04c739f..a1d539410 100644 --- a/apps/openmw-mp/Script/ScriptFunctions.hpp +++ b/apps/openmw-mp/Script/ScriptFunctions.hpp @@ -214,7 +214,8 @@ public: {"OnWorldWeather", Callback()}, {"OnClientScriptGlobal", Callback()}, {"OnMpNumIncrement", Callback()}, - {"OnRequestDataFileList", Callback<>()} + {"OnRequestDataFileList", Callback<>()}, + {"OnServerWindowInput", Callback()} }; }; diff --git a/apps/openmw-mp/handleInput.cpp b/apps/openmw-mp/handleInput.cpp new file mode 100644 index 000000000..702d007bc --- /dev/null +++ b/apps/openmw-mp/handleInput.cpp @@ -0,0 +1,27 @@ +using namespace std; +namespace mwmp_input { + string windowInputBuffer; + void handler() { + char c; +#ifndef WIN32 + while (kbhit()) { + c = getch(); +#else // on Windows conio.h getch() and kbhit() are deprecated, use _getch() and _kbhit() instead + while (_kbhit()) { + c = _getch(); +#endif + cout << c << flush; + if (c == '\n' || c == '\r') { // handle carriage return as new line on Windows + cout << endl; + Script::Call(windowInputBuffer.c_str()); + windowInputBuffer.assign(""); + } + else if (c == '\b') { + auto size = windowInputBuffer.size(); + if (size > 0) + windowInputBuffer.erase(size - 1); + } + else windowInputBuffer += c; + } + } +} diff --git a/apps/openmw/mwmp/RecordHelper.cpp b/apps/openmw/mwmp/RecordHelper.cpp index 87e6b39d7..d78483d02 100644 --- a/apps/openmw/mwmp/RecordHelper.cpp +++ b/apps/openmw/mwmp/RecordHelper.cpp @@ -1497,3 +1497,49 @@ void RecordHelper::overrideRecord(const mwmp::WeaponRecord& record) if (isExistingId) world->updatePtrsWithRefId(recordData.mId); } + +void RecordHelper::overrideRecord(const mwmp::SoundRecord& record) { + const ESM::Sound& recordData = record.data; + + if (recordData.mId.empty()) + { + LOG_APPEND(TimedLog::LOG_INFO, "-- Ignoring record override with no id provided"); + return; + } + + bool isExistingId = doesRecordIdExist(recordData.mId); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + if (record.baseId.empty()) + { + world->getModifiableStore().overrideRecord(recordData); + } + else if (doesRecordIdExist(record.baseId)) + { + const ESM::Sound* baseData = world->getStore().get().search(record.baseId); + ESM::Sound finalData = *baseData; + finalData.mId = recordData.mId; + + if (record.baseOverrides.hasSound) + finalData.mSound = recordData.mSound; + + if (record.baseOverrides.hasVolume) + finalData.mData.mVolume = recordData.mData.mVolume; + + if (record.baseOverrides.hasMinRange) + finalData.mData.mMinRange = recordData.mData.mMinRange; + + if (record.baseOverrides.hasMaxRange) + finalData.mData.mMaxRange = recordData.mData.mMaxRange; + + world->getModifiableStore().overrideRecord(finalData); + } + else + { + LOG_APPEND(TimedLog::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str()); + return; + } + + if (isExistingId) + world->updatePtrsWithRefId(recordData.mId); +} diff --git a/apps/openmw/mwmp/RecordHelper.hpp b/apps/openmw/mwmp/RecordHelper.hpp index 6d5c61e33..b6ca21e38 100644 --- a/apps/openmw/mwmp/RecordHelper.hpp +++ b/apps/openmw/mwmp/RecordHelper.hpp @@ -30,6 +30,7 @@ namespace RecordHelper void overrideRecord(const mwmp::SpellRecord& record); void overrideRecord(const mwmp::StaticRecord& record); void overrideRecord(const mwmp::WeaponRecord& record); + void overrideRecord(const mwmp::SoundRecord& record); template void overrideRecord(const RecordType &record) diff --git a/apps/openmw/mwmp/Worldstate.cpp b/apps/openmw/mwmp/Worldstate.cpp index 7efbcf6fc..0c5752edd 100644 --- a/apps/openmw/mwmp/Worldstate.cpp +++ b/apps/openmw/mwmp/Worldstate.cpp @@ -316,6 +316,18 @@ void Worldstate::addRecords() RecordHelper::overrideRecord(record); } } + else if (recordsType == mwmp::RECORD_TYPE::SOUND) + { + for (auto&& record : soundRecords) + { + bool hasBaseId = !record.baseId.empty(); + + LOG_APPEND(TimedLog::LOG_INFO, "- sound record %s\n-- baseId is %s", record.data.mId.c_str(), + hasBaseId ? record.baseId.c_str() : "empty"); + + RecordHelper::overrideRecord(record); + } + } } bool Worldstate::containsExploredMapTile(int cellX, int cellY) diff --git a/components/openmw-mp/Base/BaseWorldstate.hpp b/components/openmw-mp/Base/BaseWorldstate.hpp index 245143e17..dfc4b0f74 100644 --- a/components/openmw-mp/Base/BaseWorldstate.hpp +++ b/components/openmw-mp/Base/BaseWorldstate.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -57,7 +58,8 @@ namespace mwmp SCRIPT, SPELL, STATIC, - WEAPON + WEAPON, + SOUND }; // When using an existing record as a base, this struct tracks which changes @@ -149,6 +151,9 @@ namespace mwmp bool hasQuasiEx = false; bool hasRegion = false; + bool hasVolume = false; + bool hasMinRange = false; + bool hasMaxRange = false; }; struct ActivatorRecord @@ -318,6 +323,13 @@ namespace mwmp BaseOverrides baseOverrides; }; + struct SoundRecord + { + ESM::Sound data; + std::string baseId; + BaseOverrides baseOverrides; + }; + static const int maxImageDataSize = 1800; struct MapTile @@ -404,6 +416,7 @@ namespace mwmp std::vector probeRecords; std::vector repairRecords; std::vector scriptRecords; + std::vector soundRecords; std::vector spellRecords; std::vector staticRecords; std::vector weaponRecords; diff --git a/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp b/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp index ee5a4c4fc..41b75973f 100644 --- a/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp +++ b/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp @@ -68,6 +68,8 @@ void PacketRecordDynamic::Packet(RakNet::BitStream *newBitstream, bool send) worldstate->recordsCount = Utils::getVectorSize(worldstate->scriptRecords); else if (worldstate->recordsType == mwmp::RECORD_TYPE::STATIC) worldstate->recordsCount = Utils::getVectorSize(worldstate->staticRecords); + else if (worldstate->recordsType == mwmp::RECORD_TYPE::SOUND) + worldstate->recordsCount = Utils::getVectorSize(worldstate->soundRecords); else { LOG_MESSAGE_SIMPLE(TimedLog::LOG_ERROR, "Processed invalid ID_RECORD_DYNAMIC packet about unimplemented recordsType %i", @@ -134,6 +136,8 @@ void PacketRecordDynamic::Packet(RakNet::BitStream *newBitstream, bool send) Utils::resetVector(worldstate->scriptRecords, worldstate->recordsCount); else if (worldstate->recordsType == mwmp::RECORD_TYPE::STATIC) Utils::resetVector(worldstate->staticRecords, worldstate->recordsCount); + else if (worldstate->recordsType == mwmp::RECORD_TYPE::SOUND) + Utils::resetVector(worldstate->soundRecords, worldstate->recordsCount); } if (worldstate->recordsType == mwmp::RECORD_TYPE::SPELL) @@ -868,6 +872,29 @@ void PacketRecordDynamic::Packet(RakNet::BitStream *newBitstream, bool send) } } } + else if (worldstate->recordsType == mwmp::RECORD_TYPE::SOUND) + { + for (auto&& record : worldstate->soundRecords) + { + auto& recordData = record.data; + + RW(record.baseId, send, true); + RW(recordData.mId, send, true); + RW(recordData.mSound, send, true); + RW(recordData.mData.mVolume, send); + RW(recordData.mData.mMinRange, send); + RW(recordData.mData.mMaxRange, send); + + if (!record.baseId.empty()) + { + auto&& overrides = record.baseOverrides; + RW(overrides.hasSound, send); + RW(overrides.hasVolume, send); + RW(overrides.hasMinRange, send); + RW(overrides.hasMaxRange, send); + } + } + } } void PacketRecordDynamic::ProcessEffects(ESM::EffectList &effectList, bool send)