forked from teamnwah/openmw-tes3coop
Merge pull request #59 from OpenMW/master
Add OpenMW commits up to 18 Sep
This commit is contained in:
commit
79c561057c
18 changed files with 114 additions and 38 deletions
|
@ -91,15 +91,17 @@ namespace MWGui
|
|||
|
||||
if (mSaveList->getItemCount() == 0)
|
||||
{
|
||||
// The character might be deleted now
|
||||
size_t previousIndex = mCharacterSelection->getIndexSelected();
|
||||
open();
|
||||
mCurrentCharacter = NULL;
|
||||
mCharacterSelection->removeItemAt(previousIndex);
|
||||
if (mCharacterSelection->getItemCount())
|
||||
{
|
||||
size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1);
|
||||
mCharacterSelection->setIndexSelected(nextCharacter);
|
||||
onCharacterSelected(mCharacterSelection, nextCharacter);
|
||||
}
|
||||
else
|
||||
fillSaveList();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1236,9 +1236,7 @@ bool CharacterController::updateWeaponState()
|
|||
cast.playSpellCastingEffects(spellid);
|
||||
|
||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||
|
||||
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.at(spell->mEffects.mList.size() - 1);
|
||||
|
||||
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back();
|
||||
const ESM::MagicEffect *effect;
|
||||
|
||||
effect = store.get<ESM::MagicEffect>().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
||||
{
|
||||
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
|
||||
|
@ -544,12 +543,14 @@ namespace MWMechanics
|
|||
else
|
||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
|
||||
|
||||
std::string texture = magicEffect->mParticle;
|
||||
|
||||
// TODO: VFX are no longer active after saving/reloading the game
|
||||
bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
|
||||
// Note: in case of non actor, a free effect should be fine as well
|
||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (anim)
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -958,15 +959,18 @@ namespace MWMechanics
|
|||
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
|
||||
|
||||
if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a spell cast vfx
|
||||
if (mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx
|
||||
{
|
||||
const ESM::Static* castStatic;
|
||||
|
||||
if (!effect->mCasting.empty())
|
||||
castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||
else
|
||||
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
|
||||
|
||||
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
|
||||
std::string texture = effect->mParticle;
|
||||
|
||||
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture);
|
||||
}
|
||||
|
||||
if (!mCaster.getClass().isActor())
|
||||
|
|
|
@ -1342,7 +1342,7 @@ namespace MWRender
|
|||
SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr<SceneUtil::ControllerSource>(params.mAnimTime));
|
||||
node->accept(assignVisitor);
|
||||
|
||||
overrideTexture(texture, mResourceSystem, node);
|
||||
overrideFirstRootTexture(texture, mResourceSystem, node);
|
||||
|
||||
// TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box.
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ EffectManager::~EffectManager()
|
|||
clear();
|
||||
}
|
||||
|
||||
void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale)
|
||||
void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX)
|
||||
{
|
||||
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model);
|
||||
|
||||
|
@ -46,7 +46,10 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu
|
|||
SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime);
|
||||
node->accept(assignVisitor);
|
||||
|
||||
overrideTexture(textureOverride, mResourceSystem, node);
|
||||
if (isMagicVFX)
|
||||
overrideFirstRootTexture(textureOverride, mResourceSystem, node);
|
||||
else
|
||||
overrideTexture(textureOverride, mResourceSystem, node);
|
||||
|
||||
mParentNode->addChild(trans);
|
||||
mResourceSystem->getSceneManager()->notifyAttached(node);
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace MWRender
|
|||
~EffectManager();
|
||||
|
||||
/// Add an effect. When it's finished playing, it will be removed automatically.
|
||||
void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale);
|
||||
void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true);
|
||||
|
||||
void update(float dt);
|
||||
|
||||
|
|
|
@ -970,12 +970,12 @@ Resource::ResourceSystem* NpcAnimation::getResourceSystem()
|
|||
return mResourceSystem;
|
||||
}
|
||||
|
||||
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)
|
||||
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew)
|
||||
{
|
||||
// During first auto equip, we don't play any sounds.
|
||||
// Basically we don't want sounds when the actor is first loaded,
|
||||
// the items should appear as if they'd always been equipped.
|
||||
if (playSound)
|
||||
if (isNew)
|
||||
{
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
|
@ -994,7 +994,7 @@ void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, boo
|
|||
bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
|
||||
// Don't play particle VFX unless the effect is new or it should be looping.
|
||||
if (isNew || loop)
|
||||
addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
||||
addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::I
|
|||
{
|
||||
public:
|
||||
virtual void equipmentChanged();
|
||||
virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound);
|
||||
virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew);
|
||||
|
||||
public:
|
||||
typedef std::map<ESM::PartReferenceType,std::string> PartBoneMap;
|
||||
|
|
|
@ -742,9 +742,9 @@ namespace MWRender
|
|||
mObjects->updatePtr(old, updated);
|
||||
}
|
||||
|
||||
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale)
|
||||
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX)
|
||||
{
|
||||
mEffectManager->addEffect(model, texture, worldPosition, scale);
|
||||
mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX);
|
||||
}
|
||||
|
||||
void RenderingManager::notifyWorldSpaceChanged()
|
||||
|
|
|
@ -138,7 +138,7 @@ namespace MWRender
|
|||
|
||||
SkyManager* getSkyManager();
|
||||
|
||||
void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f);
|
||||
void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f, bool isMagicVFX = true);
|
||||
|
||||
/// Clear all savegame-specific data
|
||||
void clear();
|
||||
|
|
|
@ -1,14 +1,47 @@
|
|||
#include "util.hpp"
|
||||
|
||||
#include <osg/Node>
|
||||
#include <osg/ValueObject>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class TextureOverrideVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
TextureOverrideVisitor(std::string texture, Resource::ResourceSystem* resourcesystem)
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mTexture(texture)
|
||||
, mResourcesystem(resourcesystem)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void apply(osg::Node& node)
|
||||
{
|
||||
int index;
|
||||
osg::ref_ptr<osg::Node> nodePtr(&node);
|
||||
if (node.getUserValue("overrideFx", index))
|
||||
{
|
||||
if (index == 1)
|
||||
overrideTexture(mTexture, mResourcesystem, nodePtr);
|
||||
}
|
||||
traverse(node);
|
||||
}
|
||||
std::string mTexture;
|
||||
Resource::ResourceSystem* mResourcesystem;
|
||||
};
|
||||
|
||||
void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr<osg::Node> node)
|
||||
{
|
||||
TextureOverrideVisitor overrideVisitor(texture, resourceSystem);
|
||||
node->accept(overrideVisitor);
|
||||
}
|
||||
|
||||
void overrideTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr<osg::Node> node)
|
||||
{
|
||||
if (texture.empty())
|
||||
|
|
|
@ -17,6 +17,10 @@ namespace Resource
|
|||
|
||||
namespace MWRender
|
||||
{
|
||||
// Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty of the .NIF file's root node,
|
||||
// if it had a NiTexturingProperty. Used for applying "particle textures" to magic effects.
|
||||
void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr<osg::Node> node);
|
||||
|
||||
void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Node> node);
|
||||
|
||||
// Node callback to entirely skip the traversal.
|
||||
|
|
|
@ -414,8 +414,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
|||
// During first auto equip, we don't play any sounds.
|
||||
// Basically we don't want sounds when the actor is first loaded,
|
||||
// the items should appear as if they'd always been equipped.
|
||||
mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip,
|
||||
!mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin());
|
||||
mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip);
|
||||
}
|
||||
|
||||
if (magnitude)
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace MWWorld
|
|||
* If it isn't new, non-looping VFX should not be played.
|
||||
* @param playSound Play effect sound?
|
||||
*/
|
||||
virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) {}
|
||||
virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "../mwrender/animation.hpp"
|
||||
#include "../mwrender/vismask.hpp"
|
||||
#include "../mwrender/renderingmanager.hpp"
|
||||
#include "../mwrender/util.hpp"
|
||||
|
||||
#include "../mwsound/sound.hpp"
|
||||
|
||||
|
@ -37,7 +38,7 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::vector<std::string>& sounds, float& speed, const ESM::EffectList& effects)
|
||||
ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::vector<std::string>& sounds, float& speed, std::string& texture, const ESM::EffectList& effects)
|
||||
{
|
||||
int count = 0;
|
||||
speed = 0.0f;
|
||||
|
@ -74,6 +75,14 @@ namespace
|
|||
if (count != 0)
|
||||
speed /= count;
|
||||
|
||||
// the particle texture is only used if there is only one projectile
|
||||
if (projectileEffects.mList.size() == 1)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effects.mList.begin()->mEffectID);
|
||||
texture = magicEffect->mParticle;
|
||||
}
|
||||
|
||||
if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
|
||||
{
|
||||
std::ostringstream ID;
|
||||
|
@ -127,7 +136,7 @@ namespace MWWorld
|
|||
};
|
||||
|
||||
|
||||
void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate)
|
||||
void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, std::string texture)
|
||||
{
|
||||
state.mNode = new osg::PositionAttitudeTransform;
|
||||
state.mNode->setNodeMask(MWRender::Mask_Effect);
|
||||
|
@ -144,7 +153,7 @@ namespace MWWorld
|
|||
attachTo = rotateNode;
|
||||
}
|
||||
|
||||
mResourceSystem->getSceneManager()->getInstance(model, attachTo);
|
||||
osg::ref_ptr<osg::Node> projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo);
|
||||
|
||||
if (state.mIdMagic.size() > 1)
|
||||
for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter)
|
||||
|
@ -169,6 +178,8 @@ namespace MWWorld
|
|||
|
||||
SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime);
|
||||
state.mNode->accept(assignVisitor);
|
||||
|
||||
MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile);
|
||||
}
|
||||
|
||||
void ProjectileManager::update(State& state, float duration)
|
||||
|
@ -207,7 +218,9 @@ namespace MWWorld
|
|||
state.mActorId = -1;
|
||||
state.mStack = stack;
|
||||
|
||||
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, effects);
|
||||
std::string texture = "";
|
||||
|
||||
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, effects);
|
||||
|
||||
// Non-projectile should have been removed by getMagicBoltData
|
||||
if (state.mEffects.mList.empty())
|
||||
|
@ -216,7 +229,7 @@ namespace MWWorld
|
|||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
|
||||
MWWorld::Ptr ptr = ref.getPtr();
|
||||
|
||||
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true);
|
||||
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, texture);
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
for (size_t it = 0; it != state.mSoundIds.size(); it++)
|
||||
|
@ -486,7 +499,8 @@ namespace MWWorld
|
|||
state.mSpellId = esm.mSpellId;
|
||||
state.mActorId = esm.mActorId;
|
||||
state.mStack = esm.mStack;
|
||||
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, esm.mEffects);
|
||||
std::string texture = "";
|
||||
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, esm.mEffects);
|
||||
state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as
|
||||
// projectile effects, so we can't calculate it from the save
|
||||
// file's effect list, which is already trimmed of non-projectile
|
||||
|
@ -504,7 +518,7 @@ namespace MWWorld
|
|||
return true;
|
||||
}
|
||||
|
||||
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true);
|
||||
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, texture);
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ namespace MWWorld
|
|||
void moveProjectiles(float dt);
|
||||
void moveMagicBolts(float dt);
|
||||
|
||||
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate);
|
||||
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, std::string texture = "");
|
||||
void update (State& state, float duration);
|
||||
|
||||
void operator=(const ProjectileManager&);
|
||||
|
|
|
@ -3166,7 +3166,7 @@ namespace MWWorld
|
|||
modelName << roll;
|
||||
std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str());
|
||||
|
||||
mRendering->spawnEffect(model, texture, worldPosition);
|
||||
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
|
||||
}
|
||||
|
||||
void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos)
|
||||
|
@ -3183,7 +3183,7 @@ namespace MWWorld
|
|||
{
|
||||
const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
||||
|
||||
if ((effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()) || effectIt->mRange != rangeType)
|
||||
if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
|
||||
continue; // Not right range type, or not area effect and hit an actor
|
||||
|
||||
// Spawn the explosion orb effect
|
||||
|
@ -3193,13 +3193,15 @@ namespace MWWorld
|
|||
else
|
||||
areaStatic = getStore().get<ESM::Static>().find ("VFX_DefaultArea");
|
||||
|
||||
std::string texture = effect->mParticle;
|
||||
|
||||
if (effectIt->mArea <= 0)
|
||||
{
|
||||
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, 1.0f);
|
||||
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, 1.0f);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, static_cast<float>(effectIt->mArea * 2));
|
||||
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast<float>(effectIt->mArea * 2));
|
||||
|
||||
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
|
||||
static const std::string schools[] = {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <osg/Array>
|
||||
#include <osg/LOD>
|
||||
#include <osg/TexGen>
|
||||
#include <osg/ValueObject>
|
||||
|
||||
// resource
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
@ -276,11 +277,13 @@ namespace NifOsg
|
|||
public:
|
||||
/// @param filename used for warning messages.
|
||||
LoaderImpl(const std::string& filename)
|
||||
: mFilename(filename)
|
||||
: mFilename(filename), mFirstRootTextureIndex(-1), mFoundFirstRootTexturingProperty(false)
|
||||
{
|
||||
|
||||
}
|
||||
std::string mFilename;
|
||||
size_t mFirstRootTextureIndex;
|
||||
bool mFoundFirstRootTexturingProperty;
|
||||
|
||||
static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target)
|
||||
{
|
||||
|
@ -371,10 +374,24 @@ namespace NifOsg
|
|||
void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector<int>& boundTextures, int animflags)
|
||||
{
|
||||
const Nif::PropertyList& props = nifNode->props;
|
||||
for (size_t i = 0; i <props.length();++i)
|
||||
for (size_t i = 0; i <props.length(); ++i)
|
||||
{
|
||||
if (!props[i].empty())
|
||||
{
|
||||
// Get the lowest numbered recIndex of the NiTexturingProperty root node.
|
||||
// This is what is overridden when a spell effect "particle texture" is used.
|
||||
if (nifNode->parent == NULL && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty)
|
||||
{
|
||||
mFirstRootTextureIndex = props[i].getPtr()->recIndex;
|
||||
mFoundFirstRootTexturingProperty = true;
|
||||
}
|
||||
else if (props[i].getPtr()->recType == Nif::RC_NiTexturingProperty)
|
||||
{
|
||||
if (props[i].getPtr()->recIndex == mFirstRootTextureIndex)
|
||||
applyTo->setUserValue("overrideFx", 1);
|
||||
}
|
||||
handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue