Browse Source

Merge pull request #206 from Hoikas/quabs

Engine fixes for AhnyQuabs
Branan Purvine-Riley 13 years ago
parent
commit
c3eb5300df
  1. 11
      Sources/Plasma/FeatureLib/pfPython/cyMisc.cpp
  2. 3
      Sources/Plasma/FeatureLib/pfPython/cyMiscGlue.cpp
  3. 28
      Sources/Plasma/FeatureLib/pfPython/plPythonFileMod.cpp
  4. 18
      Sources/Plasma/FeatureLib/pfPython/pyCritterBrain.cpp
  5. 3
      Sources/Plasma/FeatureLib/pfPython/pyCritterBrain.h
  6. 21
      Sources/Plasma/FeatureLib/pfPython/pyCritterBrainGlue.cpp
  7. 8
      Sources/Plasma/PubUtilLib/plAvatar/plArmatureMod.cpp
  8. 19
      Sources/Plasma/PubUtilLib/plAvatar/plAvBrainCritter.cpp
  9. 15
      Sources/Plasma/PubUtilLib/plAvatar/plAvBrainCritter.h
  10. 30
      Sources/Plasma/PubUtilLib/plAvatar/plAvatarMgr.cpp
  11. 12
      Sources/Plasma/PubUtilLib/plAvatar/plAvatarMgr.h
  12. 11
      Sources/Plasma/PubUtilLib/plAvatar/plNPCSpawnMod.cpp
  13. 1
      Sources/Plasma/PubUtilLib/plAvatar/plNPCSpawnMod.h
  14. 1
      Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeLeaver.cpp
  15. 53
      Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp
  16. 11
      Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h
  17. 19
      Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgrLoad.cpp

11
Sources/Plasma/FeatureLib/pfPython/cyMisc.cpp

@ -2700,7 +2700,7 @@ PyObject* cyMisc::LoadAvatarModel(const char* modelName, pyKey& spawnPoint, cons
void cyMisc::UnLoadAvatarModel(pyKey& avatar)
{
plAvatarMgr::GetInstance()->UnLoadAvatar(avatar.getKey(), false);
plAvatarMgr::GetInstance()->UnLoadAvatar(avatar.getKey(), false, true);
}
void cyMisc::ForceCursorHidden()
@ -2868,14 +2868,7 @@ PyObject* cyMisc::GetAIAvatarsByModelName(const char* name)
Py_DECREF(tuple);
}
}
if (PyList_Size(avList) > 0)
return avList;
else
{
Py_DECREF(avList);
PYTHON_RETURN_NONE;
}
return avList;
}
void cyMisc::ForceVaultNodeUpdate(unsigned nodeId)

3
Sources/Plasma/FeatureLib/pfPython/cyMiscGlue.cpp

@ -374,7 +374,8 @@ PYTHON_GLOBAL_METHOD_DEFINITION(PtLoadAvatarModel, args, "Params: modelName, spa
return cyMisc::LoadAvatarModel(modelName, *key, userStr.c_str());
}
PYTHON_GLOBAL_METHOD_DEFINITION(PtUnLoadAvatarModel, args, "Params: avatarKey\nUnloads the specified avatar model")
PYTHON_GLOBAL_METHOD_DEFINITION(PtUnLoadAvatarModel, args, "Params: avatarKey\nForcibly unloads the specified avatar model.\n"
"Do not use this method unless you require fine-grained control of avatar unloading.")
{
PyObject* keyObj = NULL;
if (!PyArg_ParseTuple(args, "O", &keyObj))

28
Sources/Plasma/FeatureLib/pfPython/plPythonFileMod.cpp

@ -852,6 +852,34 @@ void plPythonFileMod::AddTarget(plSceneObject* sobj)
DisplayPythonOutput();
}
// Oversight fix... Sometimes PythonFileMods are loaded after the AgeInitialState is received.
// We should really let the script know about that via OnServerInitComplete anyway because it's
// not good to make assumptions about game state in workarounds for that method not being called
plNetClientApp* na = plNetClientApp::GetInstance();
if (!na->GetFlagsBit(plNetClientApp::kLoadingInitialAgeState) &&
na->GetFlagsBit(plNetClientApp::kPlayingGame))
{
plgDispatch::Dispatch()->UnRegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
plProfile_BeginTiming(PythonUpdate);
// call it
PyObject* retVal = PyObject_CallMethod(
fPyFunctionInstances[kfunc_OnServerInitComplete],
(char*)fFunctionNames[kfunc_OnServerInitComplete], nil);
if ( retVal == nil )
{
#ifndef PLASMA_EXTERNAL_RELEASE
// for some reason this function didn't, remember that and not call it again
fPyFunctionInstances[kfunc_OnServerInitComplete] = nil;
#endif //PLASMA_EXTERNAL_RELEASE
// if there was an error make sure that the stderr gets flushed so it can be seen
ReportError();
}
Py_XDECREF(retVal);
plProfile_EndTiming(PythonUpdate);
// display any output (NOTE: this would be disabled in production)
DisplayPythonOutput();
}
// display python output
DisplayPythonOutput();
}

18
Sources/Plasma/FeatureLib/pfPython/pyCritterBrain.cpp

@ -41,8 +41,10 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
*==LICENSE==*/
#include "pyCritterBrain.h"
#include "pyGeometry3.h"
#include "pySceneObject.h"
#include "plAvatar/plAvBrainCritter.h"
#include "pnSceneObject/plSceneObject.h"
pyCritterBrain::pyCritterBrain(): fBrain(nil) {}
@ -66,18 +68,12 @@ void pyCritterBrain::RemoveReceiver(pyKey& oldReceiver)
fBrain->RemoveReceiver(oldReceiver.getKey());
}
void pyCritterBrain::LocallyControlled(bool local)
PyObject* pyCritterBrain::GetSceneObject()
{
if (!fBrain)
return;
fBrain->LocallyControlled(local);
}
bool pyCritterBrain::LocallyControlled() const
{
if (!fBrain)
return false;
return fBrain->LocallyControlled();
if (fBrain)
if (plSceneObject* obj = fBrain->GetTarget())
return pySceneObject::New(obj->GetKey());
PYTHON_RETURN_NONE;
}
void pyCritterBrain::AddBehavior(const std::string& animationName, const std::string& behaviorName, bool loop /* = true */,

3
Sources/Plasma/FeatureLib/pfPython/pyCritterBrain.h

@ -83,8 +83,7 @@ public:
void AddReceiver(pyKey& newReceiver);
void RemoveReceiver(pyKey& oldReceiver);
void LocallyControlled(bool local);
bool LocallyControlled() const;
PyObject* GetSceneObject();
void AddBehavior(const std::string& animationName, const std::string& behaviorName, bool loop = true, bool randomStartPos = true,
float fadeInLen = 2.f, float fadeOutLen = 2.f);

21
Sources/Plasma/FeatureLib/pfPython/pyCritterBrainGlue.cpp

@ -139,21 +139,9 @@ PYTHON_METHOD_DEFINITION(ptCritterBrain, removeReceiver, args)
PYTHON_RETURN_NONE;
}
PYTHON_METHOD_DEFINITION(ptCritterBrain, setLocallyControlled, args)
PYTHON_METHOD_DEFINITION(ptCritterBrain, getSceneObject, GetSceneObject)
{
char local;
if (!PyArg_ParseTuple(args, "b", &local))
{
PyErr_SetString(PyExc_TypeError, "setLocallyControlled expects a boolean");
PYTHON_RETURN_ERROR;
}
self->fThis->LocallyControlled(local != 0);
PYTHON_RETURN_NONE;
}
PYTHON_METHOD_DEFINITION_NOARGS(ptCritterBrain, getLocallyControlled)
{
PYTHON_RETURN_BOOL(self->fThis->LocallyControlled());
return self->fThis->GetSceneObject();
}
PYTHON_METHOD_DEFINITION_WKEY(ptCritterBrain, addBehavior, args, keywords)
@ -472,10 +460,7 @@ PYTHON_METHOD_DEFINITION(ptCritterBrain, vectorToPlayer, args)
PYTHON_START_METHODS_TABLE(ptCritterBrain)
PYTHON_METHOD(ptCritterBrain, addReceiver, "Params: key\nTells the brain that the specified key wants AI messages"),
PYTHON_METHOD(ptCritterBrain, removeReceiver, "Params: key\nTells the brain that the specified key no longer wants AI messages"),
PYTHON_METHOD(ptCritterBrain, setLocallyControlled, "Params: local\nTells the brain that we are the ones making all the AI decisions, and to prop location "
"and other information to the server."),
PYTHON_METHOD_NOARGS(ptCritterBrain, getLocallyControlled, "Are we the one making AI decisions? NOTE: Not set automatically, some python script needs to "
"tell the brain this using setLocallyControlled()."),
PYTHON_METHOD(ptCritterBrain, getSceneObject, "Returns the ptSceneObject this brain controls."),
PYTHON_METHOD_WKEY(ptCritterBrain, addBehavior, "Params: animName, behaviorName, loop = 1, randomStartPos = 1, fadeInLen = 2.0, fadeOutLen = 2.0\n"
"Adds a new animation to the brain as a behavior with the specified name and parameters. If multiple animations are assigned to the same behavior, "
"they will be randomly picked from when started."),

8
Sources/Plasma/PubUtilLib/plAvatar/plArmatureMod.cpp

@ -1952,10 +1952,10 @@ hsBool plArmatureMod::IsLocalAvatar()
hsBool plArmatureMod::IsLocalAI()
{
plAvBrainCritter* ai = plAvBrainCritter::ConvertNoRef(FindBrainByClass(plAvBrainCritter::Index()));
if (ai)
return ai->LocallyControlled();
return false; // not an AI, obviously not local
// Formerly a lot of silly cached rigamaroll... Now, we'll just rely
// on the fact that one player is the game master. HACK TURD if net groups
// are ever brought back.
return plNetClientApp::GetInstance()->IsLocallyOwned(this);
}
void plArmatureMod::SynchIfLocal(double timeNow, int force)

19
Sources/Plasma/PubUtilLib/plAvatar/plAvBrainCritter.cpp

@ -126,7 +126,7 @@ protected:
///////////////////////////////////////////////////////////////////////////////
plAvBrainCritter::plAvBrainCritter(): fCallbackAction(nil), fCurMode(kIdle), fNextMode(kIdle), fFadingNextBehavior(true),
fLocallyControlled(false), fAvoidingAvatars(false), fFinalGoalPos(0, 0, 0), fImmediateGoalPos(0, 0, 0), fDotGoal(0),
fAvoidingAvatars(false), fFinalGoalPos(0, 0, 0), fImmediateGoalPos(0, 0, 0), fDotGoal(0),
fAngRight(0)
{
SightCone(M_PI/2); // 90deg
@ -229,6 +229,13 @@ void plAvBrainCritter::Resume()
plArmatureBrain::Resume();
}
plSceneObject* plAvBrainCritter::GetTarget() const
{
if (fArmature)
return fArmature->GetTarget(0);
return nil;
}
void plAvBrainCritter::AddBehavior(const std::string& animationName, const std::string& behaviorName, bool loop /* = true */, bool randomStartPos /* = true */,
float fadeInLen /* = 2.f */, float fadeOutLen /* = 2.f */)
{
@ -310,9 +317,13 @@ std::string plAvBrainCritter::RunBehaviorName() const
void plAvBrainCritter::GoToGoal(hsPoint3 newGoal, bool avoidingAvatars /* = false */)
{
fFinalGoalPos = newGoal;
fAvoidingAvatars = avoidingAvatars;
fNextMode = IPickBehavior(kRun);
// TODO: Pathfinding here!
fAvoidingAvatars = avoidingAvatars; // TODO: make this do something?
// Only play the run behavior if it's not already activated
// Why? This might just be an update to a preexisting goal.
if(!RunningBehavior(RunBehaviorName()))
fNextMode = IPickBehavior(kRun);
// Missing TODO Turd: Pathfinding.
}
bool plAvBrainCritter::AtGoal() const

15
Sources/Plasma/PubUtilLib/plAvatar/plAvBrainCritter.h

@ -83,14 +83,21 @@ public:
virtual void Suspend();
virtual void Resume();
/**
* Gets the SceneObject root for this avatar
*
* This is most useful in scripts that need to act upon the SceneObject directly.
* There are other ways of obtaining the SceneObject, but network synchronization often
* makes those ways more difficult than they need to be, so we have included this method
* to make the scripter's life easier.
*/
plSceneObject* GetTarget() const;
void AddBehavior(const std::string& animationName, const std::string& behaviorName, bool loop = true, bool randomStartPos = true,
float fadeInLen = 2.f, float fadeOutLen = 2.f);
void StartBehavior(const std::string& behaviorName, bool fade = true);
bool RunningBehavior(const std::string& behaviorName) const;
void LocallyControlled(bool local) {fLocallyControlled = local;}
bool LocallyControlled() const {return fLocallyControlled;}
std::string BehaviorName(int behavior) const;
plString AnimationName(int behavior) const;
int CurBehavior() const {return fCurMode;}
@ -157,8 +164,6 @@ protected:
int fNextMode; // the next behavior to run (-1 if we aren't switching on next eval)
bool fFadingNextBehavior; // is the next behavior supposed to blend?
bool fLocallyControlled; // is our local AI script the one making all the choices?
bool fAvoidingAvatars; // are we avoiding avatars to the best of our ability when pathfinding?
hsPoint3 fFinalGoalPos; // the location we are pathfinding to
hsPoint3 fImmediateGoalPos; // the location of the point we are immediately going towards (not necessarily our final goal)

30
Sources/Plasma/PubUtilLib/plAvatar/plAvatarMgr.cpp

@ -217,11 +217,17 @@ plKey plAvatarMgr::LoadAvatar(const char *name, const char *accountName, bool is
return result;
}
void plAvatarMgr::UnLoadAvatar(plKey avatarKey, bool isPlayer)
void plAvatarMgr::UnLoadAvatar(const plKey& avatarKey, bool isPlayer, bool netPropagate) const
{
hsBool isLoading = false;
plLoadAvatarMsg *msg = new plLoadAvatarMsg(avatarKey, GetKey(), 0, isPlayer, isLoading);
msg->Send();
if (avatarKey)
{
plKey requestor = GetKey();
plLoadAvatarMsg* msg = new plLoadAvatarMsg(avatarKey, requestor, 0, isPlayer, false);
// only netprop if the user has a death wish
msg->SetBCastFlag(plMessage::kNetPropagate, netPropagate);
msg->Send();
}
}
// our player's already loaded locally, but we've just linked into an age and others there need to be
@ -271,22 +277,6 @@ bool plAvatarMgr::UnPropagateLocalPlayer()
return false;
}
// UNLOADREMOTEPLAYER
void plAvatarMgr::UnLoadRemotePlayer(plKey remotePlayer)
{
if(remotePlayer)
{
plKey requestor = GetKey();
bool isPlayer = true;
hsBool isLoading = false;
plLoadAvatarMsg * msg = new plLoadAvatarMsg(remotePlayer, requestor, 0, isPlayer, isLoading);
// don't propagate over the network. this is just for removing our local version
msg->SetBCastFlag(plMessage::kNetPropagate, false);
msg->Send();
}
}
// UNLOADLOCALPLAYER
void plAvatarMgr::UnLoadLocalPlayer()
{

12
Sources/Plasma/PubUtilLib/plAvatar/plAvatarMgr.h

@ -115,15 +115,21 @@ public:
plKey LoadPlayer(const char* name, const char *account);
plKey LoadPlayer(const char* name, const char *account, const char *linkName);
plKey LoadAvatar(const char *name, const char *accountName, bool isPlayer, plKey spawnPoint, plAvTask *initialTask, const char *userStr = nil);
/** Unload an avatar - player or npc - both locally and remotely. */
void UnLoadAvatar(plKey avKey, bool isPlayer);
/**
* Unload an avatar clone
*
* This unloads the clone of an avatar (remote player or NPC) from our local game.
* The avatar clone can be unloaded globally by setting netPropagate; however, this
* is highly discouraged.
*/
void UnLoadAvatar(const plKey& avKey, bool isPlayer, bool netPropagate=false) const;
/** send our (already loaded) local player to newly-associated clients - used when linking */
void PropagateLocalPlayer(int spawnPoint = -1);
/** Unload our local player on other machines because we're leaving this age.
The player will stay around on our local machine, though. */
bool UnPropagateLocalPlayer();
void UnLoadRemotePlayer(plKey playerKey);
void UnLoadLocalPlayer();
void AddAvatar(plArmatureMod *avatar);

11
Sources/Plasma/PubUtilLib/plAvatar/plNPCSpawnMod.cpp

@ -99,16 +99,6 @@ void plNPCSpawnMod::AddTarget(plSceneObject* so)
Trigger();
}
void plNPCSpawnMod::RemoveTarget(plSceneObject *so)
{
plSingleModifier::RemoveTarget(so);
if(fSpawnedKey)
{
plAvatarMgr::GetInstance()->UnLoadAvatar(fSpawnedKey, false);
}
}
// TRIGGER
bool plNPCSpawnMod::Trigger()
{
@ -123,6 +113,7 @@ bool plNPCSpawnMod::Trigger()
// spawn the NPC
plKey spawnPoint = GetTarget(0)->GetKey();
// Note: we will be unloaded by the NetApp's NPC magick
fSpawnedKey = plAvatarMgr::GetInstance()->LoadAvatar(fModelName, fAccountName, false, spawnPoint, nil);
ISendNotify(fSpawnedKey);

1
Sources/Plasma/PubUtilLib/plAvatar/plNPCSpawnMod.h

@ -58,7 +58,6 @@ public:
GETINTERFACE_ANY( plNPCSpawnMod, plSingleModifier );
virtual void AddTarget(plSceneObject* so);
virtual void RemoveTarget(plSceneObject *so);
// hsBool MsgReceive(plMessage* msg);
virtual void Read(hsStream *stream, hsResMgr *mgr);

1
Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeLeaver.cpp

@ -223,6 +223,7 @@ void plNCAgeLeaver::ExecNextOp () {
plAgeLoader::GetInstance()->UnloadAge(); // unload age
nc->ISendCameraReset(false/*leaving age*/); // reset camera
nc->IUnloadRemotePlayers(); // unload other players
nc->IUnloadNPCs(); // unload non-player clones
if (NetCommNeedToLoadAvatar())
am->UnLoadLocalPlayer();

53
Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp

@ -181,13 +181,9 @@ void plNetClientMgr::Shutdown()
plNetLinkingMgr::GetInstance()->LeaveAge(true);
// release existing remote players
int i;
for (i=0;i<RemotePlayerKeys().size();i++)
{
plKey k=RemotePlayerKeys()[i];
plAvatarMgr::GetInstance()->UnLoadRemotePlayer(k);
}
// release all avatar clones
IUnloadRemotePlayers();
IUnloadNPCs();
// Finally, pump the dispatch system so all the new refs get delivered.
plgDispatch::Dispatch()->MsgQueueProcess();
@ -197,7 +193,7 @@ void plNetClientMgr::Shutdown()
delete fMsgRecorder;
fMsgRecorder = nil;
}
for (i = 0; i < fMsgPlayers.size(); i++)
for (int i = 0; i < fMsgPlayers.size(); i++)
delete fMsgPlayers[i];
fMsgPlayers.clear();
@ -424,9 +420,19 @@ int plNetClientMgr::IPrepMsg(plNetMessage* msg)
//
void plNetClientMgr::IUnloadRemotePlayers()
{
for(int i=RemotePlayerKeys().size()-1;i>=0;i--)
plAvatarMgr::GetInstance()->UnLoadRemotePlayer(RemotePlayerKeys()[i]);
hsAssert(!RemotePlayerKeys().size(),"Still remote players left when linking out");
for (size_t i = fRemotePlayerKeys.size(); i > 0; --i)
plAvatarMgr::GetInstance()->UnLoadAvatar(fRemotePlayerKeys[i-1], true);
hsAssert(fRemotePlayerKeys.empty(), "Still remote players left when linking out");
}
//
// unload NPCs since we're leaving the age
//
void plNetClientMgr::IUnloadNPCs()
{
for (size_t i = fNPCKeys.size(); i > 0; --i)
plAvatarMgr::GetInstance()->UnLoadAvatar(fNPCKeys[i-1], false);
hsAssert(fNPCKeys.empty(), "Still npcs left when linking out");
}
//
@ -853,6 +859,31 @@ plSynchedObject* plNetClientMgr::GetLocalPlayer(hsBool forceLoad) const
plSynchedObject::ConvertNoRef(fLocalPlayerKey->ObjectIsLoaded()) : nil;
}
plSynchedObject* plNetClientMgr::GetNPC(uint32_t i) const
{
return fNPCKeys[i] ? plSynchedObject::ConvertNoRef(fNPCKeys[i]->ObjectIsLoaded()) : nil;
}
void plNetClientMgr::AddNPCKey(const plKey& npc)
{
// note: npc keys have little sanity checking...
hsAssert(npc, "adding nil npc key? naughty, naughty...");
fNPCKeys.push_back(npc);
}
bool plNetClientMgr::IsNPCKey(const plKey& npc, int* idx) const
{
if (npc)
{
plKeyVec::const_iterator it = std::find(fNPCKeys.begin(), fNPCKeys.end(), npc);
bool found = it != fNPCKeys.end();
if (idx)
*idx = found ? (it - fNPCKeys.begin()) : -1;
return found;
}
return false;
}
//
// return a ptr to a remote player
//

11
Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h

@ -153,7 +153,7 @@ private:
// cached char info
plKey fLocalPlayerKey;
plKeyVec fRemotePlayerKeys;
// plKeyVec fNPCKeys;
plKeyVec fNPCKeys;
class plNetClientMgrMsg * fDisableMsg;
@ -222,6 +222,7 @@ private:
void IRemoveCloneRoom();
void IUnloadRemotePlayers();
void IUnloadNPCs();
plKey ILoadClone(plLoadCloneMsg *cloneMsg);
@ -310,8 +311,14 @@ public:
// avatar vault actions
int UploadPlayerVault(uint32_t vaultFlags);
// npc clones
const plKeyVec& NPCKeys() const { return fNPCKeys; }
plSynchedObject* GetNPC(uint32_t i) const;
void AddNPCKey(const plKey& npc);
bool IsNPCKey(const plKey& npc, int* idx=nil) const;
// remote players
const std::vector<plKey>& RemotePlayerKeys() const { return fRemotePlayerKeys; }
const plKeyVec& RemotePlayerKeys() const { return fRemotePlayerKeys; }
plSynchedObject* GetRemotePlayer(int i) const;
void AddRemotePlayerKey(plKey p);
hsBool IsRemotePlayerKey(const plKey p, int* idx=nil);

19
Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgrLoad.cpp

@ -113,13 +113,16 @@ plKey plNetClientMgr::ILoadClone(plLoadCloneMsg *pCloneMsg)
// check if local or remote player before loading
plLoadAvatarMsg* loadAvMsg=plLoadAvatarMsg::ConvertNoRef(pCloneMsg);
if (loadAvMsg && loadAvMsg->GetIsPlayer())
if (loadAvMsg)
{
bool originating = ( pCloneMsg->GetOriginatingPlayerID() == this->GetPlayerID() );
if (originating)
fLocalPlayerKey = cloneKey;
else
AddRemotePlayerKey(cloneKey);
if (loadAvMsg->GetIsPlayer())
if (originating)
fLocalPlayerKey = cloneKey;
else
AddRemotePlayerKey(cloneKey);
else // hey, we got a quab or yeesha... or some other such devilry...
AddNPCKey(cloneKey);
}
plKey cloneNodeKey = hsgResMgr::ResMgr()->FindKey(kNetClientCloneRoom_KEY);
@ -139,6 +142,12 @@ plKey plNetClientMgr::ILoadClone(plLoadCloneMsg *pCloneMsg)
return cloneKey;
}
// need to drop our ref if it's an NPC
// remote players handled by plPlayerPageMsg--don't sweat that
plKeyVec::iterator it = std::find(fNPCKeys.begin(), fNPCKeys.end(), cloneKey);
if (it != fNPCKeys.end())
fNPCKeys.erase(it);
ICheckPendingStateLoad(hsTimer::GetSysSeconds());
plSynchEnabler p(false); // turn off dirty tracking while in this function

Loading…
Cancel
Save