mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 07:45:32 +00:00
b57807407a
Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
376 lines
12 KiB
C++
376 lines
12 KiB
C++
#include <components/openmw-mp/Log.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwgui/windowmanagerimp.hpp"
|
|
|
|
#include "../mwworld/player.hpp"
|
|
#include "../mwworld/worldimp.hpp"
|
|
|
|
#include "Worldstate.hpp"
|
|
#include "Main.hpp"
|
|
#include "Networking.hpp"
|
|
#include "RecordHelper.hpp"
|
|
|
|
using namespace mwmp;
|
|
using namespace std;
|
|
|
|
Worldstate::Worldstate()
|
|
{
|
|
hasPlayerCollision = true;
|
|
hasActorCollision = true;
|
|
hasPlacedObjectCollision = false;
|
|
useActorCollisionForPlacedObjects = false;
|
|
}
|
|
|
|
Worldstate::~Worldstate()
|
|
{
|
|
|
|
}
|
|
|
|
Networking *Worldstate::getNetworking()
|
|
{
|
|
return mwmp::Main::get().getNetworking();
|
|
}
|
|
|
|
void Worldstate::addRecords()
|
|
{
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Received ID_RECORD_DYNAMIC with %i records of type %i",
|
|
recordsCount, recordsType);
|
|
|
|
if (recordsType == mwmp::RECORD_TYPE::SPELL)
|
|
{
|
|
for (auto &&record : spellRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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.data);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::POTION)
|
|
{
|
|
for (auto &&record : potionRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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.data);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::ENCHANTMENT)
|
|
{
|
|
for (auto &&record : enchantmentRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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.data);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::CREATURE)
|
|
{
|
|
for (auto &&record : creatureRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::NPC)
|
|
{
|
|
for (auto &&record : npcRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::ARMOR)
|
|
{
|
|
for (auto &&record : armorRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::BOOK)
|
|
{
|
|
for (auto &&record : bookRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::CLOTHING)
|
|
{
|
|
for (auto &&record : clothingRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::MISCELLANEOUS)
|
|
{
|
|
for (auto &&record : miscellaneousRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
else if (recordsType == mwmp::RECORD_TYPE::WEAPON)
|
|
{
|
|
for (auto &&record : weaponRecords)
|
|
{
|
|
bool hasBaseId = !record.baseId.empty();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Worldstate::containsExploredMapTile(int cellX, int cellY)
|
|
{
|
|
for (const auto &mapTile : exploredMapTiles)
|
|
{
|
|
if (mapTile.x == cellX && mapTile.y == cellY)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Worldstate::markExploredMapTile(int cellX, int cellY)
|
|
{
|
|
mwmp::MapTile exploredTile;
|
|
exploredTile.x = cellX;
|
|
exploredTile.y = cellY;
|
|
exploredMapTiles.push_back(exploredTile);
|
|
}
|
|
|
|
void Worldstate::setMapExplored()
|
|
{
|
|
for (const auto &mapTile : mapTiles)
|
|
{
|
|
const MWWorld::CellStore *cellStore = MWBase::Environment::get().getWorld()->getExterior(mapTile.x, mapTile.y);
|
|
|
|
if (!cellStore->getCell()->mName.empty())
|
|
MWBase::Environment::get().getWindowManager()->addVisitedLocation(cellStore->getCell()->mName, mapTile.x, mapTile.y);
|
|
|
|
MWBase::Environment::get().getWindowManager()->setGlobalMapImage(mapTile.x, mapTile.y, mapTile.imageData);
|
|
|
|
// Keep this tile marked as explored so we don't send any more packets for it
|
|
markExploredMapTile(mapTile.x, mapTile.y);
|
|
}
|
|
}
|
|
|
|
void Worldstate::setWeather()
|
|
{
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
|
|
// There's a chance we've been sent the weather for a region right after a teleportation
|
|
// that hasn't been registered in the WeatherManager yet, meaning the WeatherManager
|
|
// doesn't have the correct new region set for us, so make sure we update it
|
|
world->updateWeather(0);
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Setting weather for region: %s, currentWeather: %i, "
|
|
"nextWeather: %i, queuedWeather: %i, transitionFactor: %f, forceWeather is %s",
|
|
weather.region.c_str(), weather.currentWeather, weather.nextWeather,
|
|
weather.queuedWeather, weather.transitionFactor, forceWeather ? "true" : "false");
|
|
|
|
world->setRegionWeather(weather.region.c_str(), weather.currentWeather, weather.nextWeather,
|
|
weather.queuedWeather, weather.transitionFactor, forceWeather);
|
|
}
|
|
|
|
void Worldstate::sendMapExplored(int cellX, int cellY, const std::vector<char>& imageData)
|
|
{
|
|
mapTiles.clear();
|
|
|
|
mwmp::MapTile mapTile;
|
|
mapTile.x = cellX;
|
|
mapTile.y = cellY;
|
|
mapTile.imageData = imageData;
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_MAP with x: %i, y: %i", cellX, cellY);
|
|
|
|
mapTiles.push_back(mapTile);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_WORLD_MAP)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_WORLD_MAP)->Send();
|
|
}
|
|
|
|
void Worldstate::sendWeather(std::string region, int currentWeather, int nextWeather, int queuedWeather, float transitionFactor)
|
|
{
|
|
forceWeather = false;
|
|
weather.region = region;
|
|
weather.currentWeather = currentWeather;
|
|
weather.nextWeather = nextWeather;
|
|
weather.queuedWeather = queuedWeather;
|
|
weather.transitionFactor = transitionFactor;
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_WEATHER with region: %s, currentWeather: %i, "
|
|
"nextWeather: %i, queuedWeather, %i, transitionFactor: %f",
|
|
region.c_str(), currentWeather, nextWeather, queuedWeather, transitionFactor);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_WORLD_WEATHER)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_WORLD_WEATHER)->Send();
|
|
}
|
|
|
|
void Worldstate::sendEnchantmentRecord(const ESM::Enchantment* enchantment)
|
|
{
|
|
enchantmentRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with enchantment");
|
|
|
|
recordsType = mwmp::RECORD_TYPE::ENCHANTMENT;
|
|
|
|
mwmp::EnchantmentRecord record;
|
|
record.data = *enchantment;
|
|
enchantmentRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|
|
|
|
void Worldstate::sendPotionRecord(const ESM::Potion* potion)
|
|
{
|
|
potionRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with potion %s", potion->mName.c_str());
|
|
|
|
recordsType = mwmp::RECORD_TYPE::POTION;
|
|
|
|
mwmp::PotionRecord record;
|
|
record.data = *potion;
|
|
potionRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|
|
|
|
void Worldstate::sendSpellRecord(const ESM::Spell* spell)
|
|
{
|
|
spellRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with spell %s", spell->mName.c_str());
|
|
|
|
recordsType = mwmp::RECORD_TYPE::SPELL;
|
|
|
|
mwmp::SpellRecord record;
|
|
record.data = *spell;
|
|
spellRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|
|
|
|
void Worldstate::sendArmorRecord(const ESM::Armor* armor, std::string baseId)
|
|
{
|
|
armorRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with armor %s", armor->mName.c_str());
|
|
|
|
recordsType = mwmp::RECORD_TYPE::ARMOR;
|
|
|
|
mwmp::ArmorRecord record;
|
|
record.data = *armor;
|
|
record.baseId = baseId;
|
|
record.baseOverrides.hasName = true;
|
|
record.baseOverrides.hasEnchantmentId = true;
|
|
record.baseOverrides.hasEnchantmentCharge = true;
|
|
armorRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|
|
|
|
void Worldstate::sendBookRecord(const ESM::Book* book, std::string baseId)
|
|
{
|
|
bookRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with book %s", book->mName.c_str());
|
|
|
|
recordsType = mwmp::RECORD_TYPE::BOOK;
|
|
|
|
mwmp::BookRecord record;
|
|
record.data = *book;
|
|
record.baseId = baseId;
|
|
record.baseOverrides.hasName = true;
|
|
record.baseOverrides.hasEnchantmentId = true;
|
|
record.baseOverrides.hasEnchantmentCharge = true;
|
|
bookRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|
|
|
|
void Worldstate::sendClothingRecord(const ESM::Clothing* clothing, std::string baseId)
|
|
{
|
|
clothingRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with clothing %s", clothing->mName.c_str());
|
|
|
|
recordsType = mwmp::RECORD_TYPE::CLOTHING;
|
|
|
|
mwmp::ClothingRecord record;
|
|
record.data = *clothing;
|
|
record.baseId = baseId;
|
|
record.baseOverrides.hasName = true;
|
|
record.baseOverrides.hasEnchantmentId = true;
|
|
record.baseOverrides.hasEnchantmentCharge = true;
|
|
clothingRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|
|
|
|
void Worldstate::sendWeaponRecord(const ESM::Weapon* weapon, std::string baseId)
|
|
{
|
|
weaponRecords.clear();
|
|
|
|
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with weapon %s", weapon->mName.c_str());
|
|
|
|
recordsType = mwmp::RECORD_TYPE::WEAPON;
|
|
|
|
mwmp::WeaponRecord record;
|
|
record.data = *weapon;
|
|
record.baseId = baseId;
|
|
record.baseOverrides.hasName = true;
|
|
record.baseOverrides.hasEnchantmentId = true;
|
|
record.baseOverrides.hasEnchantmentCharge = true;
|
|
weaponRecords.push_back(record);
|
|
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->setWorldstate(this);
|
|
getNetworking()->getWorldstatePacket(ID_RECORD_DYNAMIC)->Send();
|
|
}
|