/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // characterstate.cpp: // #include "characterstate.h" #include "animate.h" #include "scriptmaster.h" static const char *MoveControl_Names[] = { "none", // MOVECONTROL_NONE "user", // MOVECONTROL_USER "legs", // MOVECONTROL_LEGS "user_moveanim", // MOVECONTROL_USER_MOVEANIM "anim", // MOVECONTROL_ANIM "absolute", // MOVECONTROL_ABSOLUTE "hanging", // MOVECONTROL_HANGING "rope_grab", // MOVECONTROL_ROPE_GRAB "rope_release", // MOVECONTROL_ROPE_RELEASE "rope_move", // MOVECONTROL_ROPE_MOVE "pickupenemy", // MOVECONTROL_PICKUPENEMY "push", // MOVECONTROL_PUSH "climbwall", // MOVECONTROL_CLIMBWALL "useanim", // MOVECONTROL_USEANIM "crouch", // MOVECONTROL_CROUCH "loopuseanim", // MOVECONTROL_LOOPUSEANIM "useobject", // MOVECONTROL_USEOBJECT "coolobject", // MOVECONTROL_COOLOBJECT NULL }; static const char *Camera_Names[] = { "topdown", // CAMERA_TOPDOWN "behind", // CAMERA_BEHIND "front", // CAMERA_FRONT "side", // CAMERA_SIDE "behind_fixed", // CAMERA_BEHIND_FIXED "side_left", // CAMERA_SIDE_LEFT "side_right", // CAMERA_SIDE_RIGHT "behind_nopitch", // CAMERA_BEHIND_NOPITCH NULL }; Conditional::Conditional ( Condition &cond ) : condition( cond ) { result = false; previous_result = false; checked = false; } Conditional::Conditional() { result = false; previous_result = false; checked = false; } Expression::Expression() { } Expression::Expression ( Expression &exp ) { int i; value = exp.value; for( i = 1; i <= exp.conditions.NumObjects(); i++ ) { conditions.AddObject( exp.conditions.ObjectAt( i ) ); } } Expression::Expression ( Script &script, State &state ) { str token; condition_t condition; int start; value = script.GetToken( true ); if ( !script.TokenAvailable( false ) || Q_stricmp( script.GetToken( false ), ":" ) ) { gi.Error( ERR_DROP, "%s: Expecting ':' on line %d.\n", script.Filename(), script.GetLineNumber() ); } while( script.TokenAvailable( false ) ) { token = script.GetToken( true ); switch( token[ 0 ] ) { case '!' : condition.test = TC_ISFALSE; start = 1; break; case '+' : condition.test = TC_EDGETRUE; start = 1; break; case '-' : condition.test = TC_EDGEFALSE; start = 1; break; default : condition.test = TC_ISTRUE; start = 0; } if ( token.length() <= start ) { gi.Error( ERR_DROP, "%s: Illegal syntax '%s' on line %d.\n", script.Filename(), &token, script.GetLineNumber() ); condition.condition_index = 0; continue; } condition.condition_index = state.addCondition( &token[ start ], script ); if ( !condition.condition_index ) { gi.Error( ERR_DROP, "%s: Unknown condition '%s' on line %d.\n", script.Filename(), &token[ start ], script.GetLineNumber() ); } conditions.AddObject( condition ); } } bool Expression::getResult ( State &state, Entity &ent, Container *sent_conditionals ) { int i; condition_t *cond; Conditional *conditional; for( i = 1; i <= conditions.NumObjects(); i++ ) { cond = &conditions.ObjectAt( i ); conditional = sent_conditionals->ObjectAt( cond->condition_index ); if ( !conditional || !conditional->getResult( cond->test, ent ) ) { return false; } } return true; } void State::readNextState ( Script &script ) { nextState = script.GetToken( false ); } void State::readMoveType ( Script &script ) { str token; const char **name; int i; token = script.GetToken( false ); for( i = 0, name = MoveControl_Names; *name != NULL; name++, i++ ) { if ( !token.icmp( *name ) ) { break; } } if ( *name == NULL ) { gi.Error( ERR_DROP, "%s: Unknown movetype '%s' on line %d.\n", script.Filename(), token.c_str(), script.GetLineNumber() ); } else { movetype = ( movecontrol_t )i; } } qboolean State::setCameraType ( str ctype ) { const char **name; int i; for( i = 0, name = Camera_Names; *name != NULL; name++, i++ ) { if ( !ctype.icmp( *name ) ) { cameratype = ( cameratype_t )i; return qtrue; } } return qfalse; } void State::readCamera ( Script &script ) { str token; token = script.GetToken( false ); if ( !setCameraType( token ) ) { gi.Error( ERR_DROP, "%s: Unknown camera type '%s' on line %d.\n", script.Filename(), token.c_str(), script.GetLineNumber() ); } } void State::readLegs ( Script &script ) { str token; if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) { gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); } while( script.TokenAvailable( true ) ) { token = script.GetToken( true ); if ( !Q_stricmp( token.c_str(), "}" ) ) { break; } script.UnGetToken(); legAnims.AddObject( Expression( script, *this ) ); } } void State::readAction ( Script &script ) { str token; m_iActionAnimType = 0; if( script.TokenAvailable( false ) ) { token = script.GetToken( false ); if( token.icmp( "aim" ) ) { m_iActionAnimType = 1; } } if( !script.TokenAvailable( true ) ) { gi.Error( ERR_DROP, "%s: EOF, expected state body on line %d.\n", script.Filename(), script.GetLineNumber() ); } if( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) { gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); } while( script.TokenAvailable( true ) ) { token = script.GetToken( true ); if( !Q_stricmp( token.c_str(), "}" ) ) { return; } script.UnGetToken(); m_actionAnims.AddObject( Expression( script, *this ) ); } gi.Error( ERR_DROP, "%s: EOF, expected '}' on line %d.\n", script.Filename(), script.GetLineNumber() ); } void State::readBehavior ( Script &script ) { str token; if ( !script.TokenAvailable( true ) ) { gi.Error( ERR_DROP, "%s: Expecting behavior name on line %d.\n", script.Filename(), script.GetLineNumber() ); } behaviorName = script.GetToken( true ); if ( !getClass( behaviorName ) ) { gi.Error( ERR_DROP, "%s: Unknown behavior '%s' on line %d.\n", script.Filename(), behaviorName.c_str(), script.GetLineNumber() ); } // Read in the behavior arguments if there are any while ( script.TokenAvailable( false ) && script.AtString( false ) ) { token = script.GetToken( false ); addBehaviorParm( token ); } } void State::readTime ( Script &script ) { str token; if ( script.TokenAvailable( false ) && script.AtString( false ) ) { token = script.GetToken( false ); minTime = atof( token.c_str() ); } if ( script.TokenAvailable( false ) && script.AtString( false ) ) { token = script.GetToken( false ); maxTime = atof( token.c_str() ); } else { maxTime = minTime; } } void State::readStates ( Script &script ) { str token; if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) { gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); } while( script.TokenAvailable( true ) ) { token = script.GetToken( true ); if ( !Q_stricmp( token.c_str(), "}" ) ) { break; } script.UnGetToken(); states.AddObject( Expression( script, *this ) ); } } void State::ParseAndProcessCommand ( str command, Entity *target ) { int argc; const char *argv[ MAX_COMMANDS ]; char args[ MAX_COMMANDS ][ MAXTOKEN ]; Script script; Event *event; script.Parse( command, command.length(), "CommandString" ); argc = 0; while( script.TokenAvailable( false ) ) { if ( argc >= MAX_COMMANDS ) { gi.DPrintf( "State:ParseAndProcessCommand : Line exceeds %d command limit", MAX_COMMANDS ); script.SkipToEOL(); break; } strcpy( args[ argc ], script.GetToken( false ) ); argv[ argc ] = args[ argc ]; argc++; } assert( argc > 0 ); if ( argc <= 0 ) return; event = new Event( args[0] ); event->AddTokens( argc - 1, &argv[ 1 ] ); target->ProcessEvent( event ); } void State::ProcessEntryCommands ( Entity *target ) { int i,count; str command; assert( target ); if ( !target ) { return; } count = entryCommands.NumObjects(); for( i = 1; i <= count; i++ ) { ParseAndProcessCommand( entryCommands.ObjectAt( i ), target ); } } void State::ProcessExitCommands ( Entity *target ) { int i,count; str command; assert( target ); if ( !target ) { return; } count = exitCommands.NumObjects(); for( i = 1; i <= count; i++ ) { ParseAndProcessCommand( exitCommands.ObjectAt( i ), target ); } } void State::readCommands ( Script &script, Container &container ) { str token; str command; if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) { gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); } while( script.TokenAvailable( true ) ) { while( script.TokenAvailable( false ) ) { token = script.GetToken( true ); if ( !Q_stricmp( token.c_str(), "}" ) ) { goto out; } if ( token.length() ) { if ( strstr( token.c_str(), " " ) == NULL ) { command.append( token ); } else { command.append( "\"" ); command.append( token ); command.append( "\"" ); } } else { command.append( "\"\"" ); } command.append( " " ); } container.AddObject( command ); command = ""; } out: return; } State *State::Evaluate ( Entity &ent, Container *sent_conditionals ) { int i; Expression *exp; State *state; int index; for( i = 1; i <= condition_indexes.NumObjects(); i++ ) { index = condition_indexes.ObjectAt( i ); sent_conditionals->ObjectAt( index )->clearCheck(); //conditions.ObjectAt( i )->clearCheck(); } for( i = 1; i <= states.NumObjects(); i++ ) { exp = &states.ObjectAt( i ); if( exp->getResult( *this, ent, sent_conditionals ) ) { state = statemap.FindState( exp->getValue() ); return state; } } return NULL; } const char *State::getLegAnim ( Entity &ent, Container *sent_conditionals ) { int i; Expression *exp; int index; for( i = 1; i <= condition_indexes.NumObjects(); i++ ) { index = condition_indexes.ObjectAt( i ); sent_conditionals->ObjectAt( index )->clearCheck(); //conditions.ObjectAt( i )->clearCheck(); } for( i = 1; i <= legAnims.NumObjects(); i++ ) { exp = &legAnims.ObjectAt( i ); if( exp->getResult( *this, ent, sent_conditionals ) ) { return exp->getValue(); } } return ""; } const char *State::getActionAnim ( Entity &ent, Container *sent_conditionals, int *piAnimType ) { int i; Expression *exp; int index; for( i = 1; i <= condition_indexes.NumObjects(); i++ ) { index = condition_indexes.ObjectAt( i ); sent_conditionals->ObjectAt( index )->clearCheck(); } for( i = 1; i <= m_actionAnims.NumObjects(); i++ ) { exp = &m_actionAnims.ObjectAt( i ); if( exp->getResult( *this, ent, sent_conditionals ) ) { if( piAnimType ) { *piAnimType = m_iActionAnimType; } return exp->getValue(); } } if( piAnimType ) { *piAnimType = 0; } return ""; } const char *State::getBehaviorName ( void ) { return behaviorName.c_str(); } float State::getMinTime ( void ) { return minTime; } float State::getMaxTime ( void ) { return maxTime; } int State::addCondition ( const char *name, Script &script ) { Conditional *condition; Condition *cond; int index; str token; condition = NULL; cond = statemap.getCondition( name ); if ( !cond ) { return 0; } condition = new Conditional( *cond ); // Get the paramaters while ( script.TokenAvailable( false ) && script.AtString( false ) ) { token = script.GetToken( false ); condition->addParm( token ); } // only add a new conditional if a similar on doesn't exist index = statemap.findConditional( condition ); if ( index ) { // delete the one we just made delete condition; } else { index = statemap.addConditional( condition ); } condition_indexes.AddUniqueObject( index ); return index; } void State::CheckStates ( void ) { const char *value; int i; if ( !statemap.FindState( nextState.c_str() ) ) { gi.Error( ERR_DROP, "Unknown next state '%s' referenced in state '%s'.\n", nextState.c_str(), getName() ); } for( i = 1; i <= states.NumObjects(); i++ ) { value = states.ObjectAt( i ).getValue(); if ( !statemap.FindState( value ) ) { gi.Error( ERR_DROP, "Unknown state '%s' referenced in state '%s'.\n", value, getName() ); } } } void State::GetLegAnims ( Container *c ) { int i,j; qboolean addobj = true; for ( i=1; i<=legAnims.NumObjects(); i++ ) { const char *value = legAnims.ObjectAt( i ).getValue(); addobj = true; // Check to see if it's already in there for ( j=1; j<=c->NumObjects(); j++ ) { if ( !Q_stricmp( c->ObjectAt( j ), value ) ) { addobj = false; break; } } if ( addobj ) c->AddObject( value ); } } void State::GetActionAnims ( Container *c ) { int i,j; qboolean addobj = true; for ( i=1; i<=m_actionAnims.NumObjects(); i++ ) { const char *value = m_actionAnims.ObjectAt( i ).getValue(); addobj = true; // Check to see if it's already in there for ( j=1; j<=c->NumObjects(); j++ ) { if ( !Q_stricmp( c->ObjectAt( j ), value ) ) { addobj = false; break; } } if ( addobj ) c->AddObject( value ); } } State::State ( const char *statename, Script &script, StateMap &map ) : statemap( map ) { str cmd; name = statename; nextState = statename; movetype = DEFAULT_MOVETYPE; cameratype = DEFAULT_CAMERA; behaviorName = "idle"; minTime = -1.0; maxTime = -1.0; if( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) { gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); } while( script.TokenAvailable( true ) ) { cmd = script.GetToken( true ); if( !cmd.icmp( "nextstate" ) ) { readNextState( script ); } else if( !cmd.icmp( "movetype" ) ) { readMoveType( script ); } else if( !cmd.icmp( "camera" ) ) { readCamera( script ); } else if( !cmd.icmp( "legs" ) ) { readLegs( script ); } else if( !cmd.icmp( "action" ) ) { readAction( script ); } else if( !cmd.icmp( "behavior" ) ) { readBehavior( script ); } else if( !cmd.icmp( "time" ) ) { readTime( script ); } else if( !cmd.icmp( "states" ) ) { readStates( script ); } else if( !cmd.icmp( "entrycommands" ) ) { readCommands( script, entryCommands ); } else if( !cmd.icmp( "exitcommands" ) ) { readCommands( script, exitCommands ); } else if( !cmd.icmp( "}" ) ) { break; } else { gi.Error( ERR_DROP, "%s: Unknown command '%s' on line %d.\n", script.Filename(), cmd.c_str(), script.GetLineNumber() ); } } } StateMap::StateMap ( const char *file_name, Condition *conditions, Container *conditionals ) { str cmd; str statename; State *state; int i; Script script; assert( file_name ); filename = file_name; this->current_conditions = conditions; this->current_conditionals = conditionals; script.LoadFile( filename ); while( script.TokenAvailable( true ) ) { cmd = script.GetToken( true ); if ( !cmd.icmp( "state" ) ) { statename = script.GetToken( false ); if ( FindState( statename.c_str() ) ) { gi.Error( ERR_DROP, "%s: Duplicate definition of state '%s' on line %d.\n", filename.c_str(), statename.c_str(), script.GetLineNumber() ); } // parse the state even if we already have it defined state = new State( statename.c_str(), script, *this ); stateList.AddObject( state ); } else { gi.Error( ERR_DROP, "%s: Unknown command '%s' on line %d.\n", script.Filename(), cmd.c_str(), script.GetLineNumber() ); } } script.Close(); // Have all the states check themselves to see if they reference any non-existant states. for( i = 1; i <= stateList.NumObjects(); i++ ) { stateList.ObjectAt( i )->CheckStates(); } } StateMap::~StateMap() { int i,num; num = stateList.NumObjects(); for( i=num; i>0; i-- ) { delete stateList.ObjectAt( i ); } stateList.FreeObjectList(); } Condition *StateMap::getCondition ( const char *name ) { Condition *c; if ( current_conditions ) { for( c = current_conditions; c->name; c++ ) { if ( !strcmp( c->name, name ) ) { return c; } } } return NULL; } int StateMap::findConditional ( Conditional *condition ) { int i; int j; Conditional *c; bool found; // Check for the one special case where we don't want to merge the conditionals if ( strcmp( condition->getName(), "CHANCE" ) == 0 ) return 0; for( i = 1; i <= current_conditionals->NumObjects(); i++ ) { c = current_conditionals->ObjectAt( i ); if ( ( c->getName() == condition->getName() ) && ( c->numParms() == condition->numParms() ) ) { found = true; for( j = 1; j <= c->numParms(); j++ ) { if ( strcmp( c->getParm( j ), condition->getParm( j ) ) ) { found = false; break; } } if ( found ) { return i; } } } return 0; } int StateMap::addConditional ( Conditional *condition ) { int index; index = current_conditionals->AddObject( condition ); return index; } Conditional *StateMap::getConditional ( const char *name ) { int i; Conditional *c; Condition *condition; for( i = 1; i <= current_conditionals->NumObjects(); i++ ) { c = current_conditionals->ObjectAt( i ); if ( !strcmp( c->getName(), name ) ) { return c; } } condition = getCondition( name ); c = new Conditional( *condition ); current_conditionals->AddObject( c ); return c; } State *StateMap::FindState ( const char *name ) { int i; for( i = 1; i <= stateList.NumObjects(); i++ ) { if ( !strcmp( stateList.ObjectAt( i )->getName(), name ) ) { return stateList.ObjectAt( i ); } } return NULL; } // Caching statemaps struct cached_statemap_t { StateMap *statemap; Container *conditionals; }; Container cached_statemaps; StateMap *GetStatemap ( str filename, Condition *conditions, Container *conditionals, qboolean reload, qboolean cache_only ) { int i; int j; cached_statemap_t *cache = NULL; cached_statemap_t new_cache; qboolean found = false; Conditional *new_conditional; Conditional *old_conditional; Condition *cond; for( i = 1 ; i <= cached_statemaps.NumObjects() ; i++ ) { cache = &cached_statemaps.ObjectAt( i ); if ( strcmp( cache->statemap->Filename(), filename.c_str() ) == 0 ) { found = true; break; } } if ( found && reload ) { delete cache->statemap; delete cache->conditionals; cache->conditionals = new Container; cache->statemap = new StateMap( filename, conditions, cache->conditionals ); } if ( !found ) { new_cache.conditionals = new Container; new_cache.statemap = new StateMap( filename, conditions, new_cache.conditionals ); cached_statemaps.AddObject( new_cache ); cache = &new_cache; } // Copy conditionals over if ( !cache_only ) { for( i = 1 ; i <= cache->conditionals->NumObjects() ; i++ ) { old_conditional = cache->conditionals->ObjectAt( i ); cond = cache->statemap->getCondition( old_conditional->condition.name ); new_conditional = new Conditional( *cond ); for( j = 1 ; j <= old_conditional->parmList.NumObjects() ; j++ ) { new_conditional->parmList.AddObject( old_conditional->parmList.ObjectAt( j ) ); } conditionals->AddObject( new_conditional ); } } return cache->statemap; } void CacheStatemap ( str filename, Condition *conditions ) { GetStatemap( filename, conditions, NULL, false, true ); } void StateMap::GetAllAnims ( Container *c ) { int i; for( i = 1; i <= stateList.NumObjects(); i++ ) { stateList.ObjectAt( i )->GetLegAnims( c ); stateList.ObjectAt( i )->GetActionAnims( c ); } } void ClearCachedStatemaps ( void ) { int i,j,num2; cached_statemap_t *cache; num2 = cached_statemaps.NumObjects(); for( i=num2 ; i>0; i-- ) { cache = &cached_statemaps.ObjectAt( i ); delete cache->statemap; int num = cache->conditionals->NumObjects(); for ( j=num; j>0; j-- ) { Conditional *cond = cache->conditionals->ObjectAt( j ); delete cond; } delete cache->conditionals; } cached_statemaps.FreeObjectList(); }