123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "T3D/components/game/stateMachine.h"
- StateMachine::StateMachine()
- {
- mStateStartTime = -1;
- mStateTime = 0;
- mStartingState = "";
- mCurCreateState = NULL;
- }
- StateMachine::~StateMachine()
- {
- }
- void StateMachine::loadStateMachineFile()
- {
- if (!mXMLReader)
- {
- SimXMLDocument *xmlrdr = new SimXMLDocument();
- xmlrdr->registerObject();
- mXMLReader = xmlrdr;
- }
- bool hasStartState = false;
- if (!dStrIsEmpty(mStateMachineFile))
- {
- //use our xml reader to parse the file!
- SimXMLDocument *reader = mXMLReader.getObject();
- if (!reader->loadFile(mStateMachineFile))
- Con::errorf("Could not load state machine file: &s", mStateMachineFile);
- if (!reader->pushFirstChildElement("StateMachine"))
- return;
- //find our starting state
- if (reader->pushFirstChildElement("StartingState"))
- {
- mStartingState = reader->getData();
- reader->popElement();
- hasStartState = true;
- }
- readStates();
- }
- if (hasStartState)
- mCurrentState = getStateByName(mStartingState);
- mStateStartTime = -1;
- mStateTime = 0;
- }
- void StateMachine::readStates()
- {
- SimXMLDocument *reader = mXMLReader.getObject();
- //iterate through our states now!
- if (reader->pushFirstChildElement("State"))
- {
- //get our first state
- State firstState;
- readStateName(&firstState, reader);
- readStateScriptFunction(&firstState, reader);
- readTransitions(firstState);
- mStates.push_back(firstState);
- //now, iterate the siblings
- while (reader->nextSiblingElement("State"))
- {
- State newState;
- readStateName(&newState, reader);
- readStateScriptFunction(&newState, reader);
- readTransitions(newState);
- mStates.push_back(newState);
- }
- }
- }
- void StateMachine::readTransitions(State ¤tState)
- {
- SimXMLDocument *reader = mXMLReader.getObject();
- //iterate through our states now!
- if (reader->pushFirstChildElement("Transition"))
- {
- //get our first state
- StateTransition firstTransition;
- readTransitonTarget(&firstTransition, reader);
- readConditions(firstTransition);
- currentState.mTransitions.push_back(firstTransition);
- //now, iterate the siblings
- while (reader->nextSiblingElement("Transition"))
- {
- StateTransition newTransition;
- readTransitonTarget(&newTransition, reader);
- readConditions(newTransition);
- currentState.mTransitions.push_back(newTransition);
- }
- reader->popElement();
- }
- }
- void StateMachine::readConditions(StateTransition ¤tTransition)
- {
- SimXMLDocument *reader = mXMLReader.getObject();
- //iterate through our states now!
- if (reader->pushFirstChildElement("Rule"))
- {
- //get our first state
- StateTransition::Condition firstCondition;
- StateField firstField;
- bool fieldRead = false;
-
- readFieldName(&firstField, reader);
- firstCondition.field = firstField;
- readFieldComparitor(&firstCondition, reader);
- readFieldValue(&firstCondition.field, reader);
- currentTransition.mTransitionRules.push_back(firstCondition);
- //now, iterate the siblings
- while (reader->nextSiblingElement("Transition"))
- {
- StateTransition::Condition newCondition;
- StateField newField;
- readFieldName(&newField, reader);
- newCondition.field = newField;
- readFieldComparitor(&newCondition, reader);
- readFieldValue(&newCondition.field, reader);
- currentTransition.mTransitionRules.push_back(newCondition);
- }
- reader->popElement();
- }
- }
- S32 StateMachine::parseComparitor(const char* comparitorName)
- {
- S32 targetType = -1;
- if (!dStrcmp("GreaterThan", comparitorName))
- targetType = StateMachine::StateTransition::Condition::GeaterThan;
- else if (!dStrcmp("GreaterOrEqual", comparitorName))
- targetType = StateMachine::StateTransition::Condition::GreaterOrEqual;
- else if (!dStrcmp("LessThan", comparitorName))
- targetType = StateMachine::StateTransition::Condition::LessThan;
- else if (!dStrcmp("LessOrEqual", comparitorName))
- targetType = StateMachine::StateTransition::Condition::LessOrEqual;
- else if (!dStrcmp("Equals", comparitorName))
- targetType = StateMachine::StateTransition::Condition::Equals;
- else if (!dStrcmp("True", comparitorName))
- targetType = StateMachine::StateTransition::Condition::True;
- else if (!dStrcmp("False", comparitorName))
- targetType = StateMachine::StateTransition::Condition::False;
- else if (!dStrcmp("Negative", comparitorName))
- targetType = StateMachine::StateTransition::Condition::Negative;
- else if (!dStrcmp("Positive", comparitorName))
- targetType = StateMachine::StateTransition::Condition::Positive;
- else if (!dStrcmp("DoesNotEqual", comparitorName))
- targetType = StateMachine::StateTransition::Condition::DoesNotEqual;
- return targetType;
- }
- void StateMachine::update()
- {
- //we always check if there's a timout transition, as that's the most generic transition possible.
- F32 curTime = Sim::getCurrentTime();
- if (mStateStartTime == -1)
- mStateStartTime = curTime;
- mStateTime = curTime - mStateStartTime;
- char buffer[64];
- dSprintf(buffer, sizeof(buffer), "%g", mStateTime);
- checkTransitions("stateTime", buffer);
- }
- void StateMachine::checkTransitions(const char* slotName, const char* newValue)
- {
- //because we use our current state's fields as dynamic fields on the instance
- //we'll want to catch any fields being set so we can treat changes as transition triggers if
- //any of the transitions on this state call for it
- //One example would be in order to implement burst fire on a weapon state machine.
- //The behavior instance has a dynamic variable set up like: GunStateMachine.burstShotCount = 0;
- //We also have a transition in our fire state, as: GunStateMachine.addTransition("FireState", "burstShotCount", "DoneShooting", 3);
- //What that does is for our fire state, we check the dynamicField burstShotCount if it's equal or greater than 3. If it is, we perform the transition.
- //As state fields are handled as dynamicFields for the instance, regular dynamicFields are processed as well as state fields. So we can use the regular
- //dynamic fields for our transitions, to act as 'global' variables that are state-agnostic. Alternately, we can use state-specific fields, such as a transition
- //like this:
- //GunStateMachine.addTransition("IdleState", "Fidget", "Timeout", ">=", 5000);
- //That uses the the timeout field, which is reset each time the state changes, and so state-specific, to see if it's been 5 seconds. If it has been, we transition
- //to our fidget state
- //so, lets check our current transitions
- //now that we have the type, check our transitions!
- for (U32 t = 0; t < mCurrentState.mTransitions.size(); t++)
- {
- //if (!dStrcmp(mCurrentState.mTransitions[t]., slotName))
- {
- //found a transition looking for this variable, so do work
- //first, figure out what data type thie field is
- //S32 type = getVariableType(newValue);
- bool fail = false;
- bool match = false;
- S32 ruleCount = mCurrentState.mTransitions[t].mTransitionRules.size();
- for (U32 r = 0; r < ruleCount; r++)
- {
- const char* fieldName = mCurrentState.mTransitions[t].mTransitionRules[r].field.name;
- if (!dStrcmp(fieldName, slotName))
- {
- match = true;
- //now, check the value with the comparitor and see if we do the transition.
- if (!passComparitorCheck(newValue, mCurrentState.mTransitions[t].mTransitionRules[r]))
- {
- fail = true;
- break;
- }
- }
- }
- //If we do have a transition rule for this field, and we didn't fail on the condition, go ahead and switch states
- if (match && !fail)
- {
- setState(mCurrentState.mTransitions[t].mStateTarget);
- return;
- }
- }
- }
- }
- bool StateMachine::passComparitorCheck(const char* var, StateTransition::Condition transitionRule)
- {
- F32 num = dAtof(var);
- switch (transitionRule.field.fieldType)
- {
- case StateField::Type::VectorType:
- switch (transitionRule.triggerComparitor)
- {
- case StateTransition::Condition::Equals:
- case StateTransition::Condition::GeaterThan:
- case StateTransition::Condition::GreaterOrEqual:
- case StateTransition::Condition::LessThan:
- case StateTransition::Condition::LessOrEqual:
- case StateTransition::Condition::DoesNotEqual:
- //do
- break;
- default:
- return false;
- };
- case StateField::Type::StringType:
- switch (transitionRule.triggerComparitor)
- {
- case StateTransition::Condition::Equals:
- if (!dStrcmp(var, transitionRule.field.triggerStringVal))
- return true;
- else
- return false;
- case StateTransition::Condition::DoesNotEqual:
- if (dStrcmp(var, transitionRule.field.triggerStringVal))
- return true;
- else
- return false;
- default:
- return false;
- };
- case StateField::Type::BooleanType:
- switch (transitionRule.triggerComparitor)
- {
- case StateTransition::Condition::TriggerValueTarget::True:
- if (dAtob(var))
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::False:
- if (dAtob(var))
- return false;
- else
- return true;
- default:
- return false;
- };
- case StateField::Type::NumberType:
- switch (transitionRule.triggerComparitor)
- {
- case StateTransition::Condition::TriggerValueTarget::Equals:
- if (num == transitionRule.field.triggerNumVal)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::GeaterThan:
- if (num > transitionRule.field.triggerNumVal)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::GreaterOrEqual:
- if (num >= transitionRule.field.triggerNumVal)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::LessThan:
- if (num < transitionRule.field.triggerNumVal)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::LessOrEqual:
- if (num <= transitionRule.field.triggerNumVal)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::DoesNotEqual:
- if (num != transitionRule.field.triggerNumVal)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::Positive:
- if (num > 0)
- return true;
- else
- return false;
- case StateTransition::Condition::TriggerValueTarget::Negative:
- if (num < 0)
- return true;
- else
- return false;
- default:
- return false;
- };
- default:
- return false;
- };
- }
- void StateMachine::setState(const char* stateName, bool clearFields)
- {
- State oldState = mCurrentState;
- StringTableEntry sName = StringTable->insert(stateName);
- for (U32 i = 0; i < mStates.size(); i++)
- {
- //if(!dStrcmp(mStates[i]->stateName, stateName))
- if (!dStrcmp(mStates[i].stateName,sName))
- {
- mCurrentState = mStates[i];
- mStateStartTime = Sim::getCurrentTime();
- onStateChanged.trigger(this, i);
- return;
- }
- }
- }
- const char* StateMachine::getStateByIndex(S32 index)
- {
- if (index >= 0 && mStates.size() > index)
- return mStates[index].stateName;
- else
- return "";
- }
- StateMachine::State& StateMachine::getStateByName(const char* name)
- {
- StringTableEntry stateName = StringTable->insert(name);
- for (U32 i = 0; i < mStates.size(); i++)
- {
- if (!dStrcmp(stateName, mStates[i].stateName))
- return mStates[i];
- }
- }
- S32 StateMachine::findFieldByName(const char* name)
- {
- for (U32 i = 0; i < mFields.size(); i++)
- {
- if (!dStrcmp(mFields[i].name, name))
- return i;
- }
- return -1;
- }
|