| /* Copyright (c) 2001 by Kevin Forchione. All Rights Reserved. */ | |
| /* | |
| * TADS ADV.T/STD.T LIBRARY EXTENSION | |
| * TRACKACTOR.T | |
| * Version 3.0 | |
| * | |
| * THE TRAVEL GRAPH | |
| * | |
| * The travel graph is a multi-graph of weighted directed | |
| * edges, that may contain self-loops. To simplify the automation | |
| * process only travel properties that resolve to property types | |
| * of _object_ are used to build adjacent vertex and adjacent edge | |
| * lists. | |
| * | |
| * Multiple edges between a given pair of vertices are flattened | |
| * out by resolving the traversal between the two vertices as | |
| * passable if any of the edges is found to be passable, and | |
| * impassable otherwise. This allows for simple paths to be | |
| * computed by dijkstra's shortest paths algorithm. | |
| * | |
| * Weighting of edges between vertices is based upon the | |
| * actor's ability to pass all of the obstacles that may | |
| * lie along the given edge. Impassable edges are given a | |
| * weight of INFINITY, while edges for which passage is | |
| * possible are give a weight of 1. The edge weights are | |
| * maintained through all path computations for a given | |
| * station (see below), and re-adjudicated only when the | |
| * next station is being considered. | |
| * | |
| * If a passable edge has been found for the two vertices | |
| * then the adjacent vertex itself is examined to determine | |
| * if it is accessible to the actor. Inaccessible vertices | |
| * are temporarily removed from path computations. After a | |
| * new path has been computed the bias against the | |
| * inaccessible vertex is removed. | |
| * | |
| * STATIONS | |
| * | |
| * TrackActor class allows the author to set stations as | |
| * points of travelling preference along the graph. Stations | |
| * are used to form the destinations of simple paths computed | |
| * by dijkstra's algorithm. | |
| * | |
| * When a path to a station has been found to be impassable, | |
| * the trackActorDaemon() will remove the impassable vertex | |
| * or edge from its new path computation and attempt to | |
| * traverse this new path. This strategy is continued until | |
| * either a path to the station has been successfully | |
| * traversed; or all possible paths to the station are | |
| * exhausted. If all paths have been exhausted the station | |
| * is abandonned and we proceed to calculate a path from | |
| * the actor's location to the next station in the list. | |
| * | |
| * TRAVERSAL STYLE | |
| * | |
| * In this way, stations are run in sequence as they appear | |
| * in the actor's station list until the end of the list is | |
| * reached. At that point the traversal style used to set | |
| * the station list will determine what happens next. The | |
| * pattern of this sequence can be any of three traversal | |
| * styles: | |
| * | |
| * pathTraversal: the station list is processed once only. | |
| * cycleTraversal: the station list is processed once, returning | |
| * the actor to the first station when the end has | |
| * been reached. | |
| * continuousTraversal: the station list is processed as a continuous | |
| * loop from beginning to end. | |
| * | |
| * EXAMPLE | |
| * | |
| * setStation( [loc1, loc2, loc3], continuousTraversal ); | |
| * | |
| * The above statement tells TrackActor to set its station | |
| * list to [loc1, loc2, loc3] and its traversal style to | |
| * continuousTraversal. The actor's destination will be | |
| * set to loc1and a path computed from the actor's location | |
| * to loc1. | |
| * | |
| * The actor will then follow the computed path (track list) | |
| * until it completes it or finds an edge or vertex it cannot | |
| * pass. If this is the case then a new path to its destination | |
| * is computed, eliminating the obstructing edge or vertex from | |
| * the calculation. | |
| * | |
| * After the actor has arrived at loc1 (or it has been | |
| * determined that this no paths to the destination exist) | |
| * a new path starting from the actor's location to loc2 | |
| * will be computed; the process repeated for loc3 and | |
| * the whole cycle starts over again by the actor returning | |
| * from loc3 to loc1. | |
| * | |
| *---------------------------------------------------------------------- | |
| * REQUIREMENTS | |
| * | |
| * + HTML TADS 2.2.6 or later | |
| * + Should be #included after ADV.T and STD.T | |
| * + Requires dijkstra.t | |
| * | |
| *---------------------------------------------------------------------- | |
| * IMPORTANT LIBRARY INTERFACE AND MODIFICATION | |
| * | |
| * + Overrides preinit() | |
| * | |
| *---------------------------------------------------------------------- | |
| * COPYRIGHT NOTICE | |
| * | |
| * You may modify and use this file in any way you want, provided that | |
| * if you redistribute modified copies of this file in source form, the | |
| * copies must include the original copyright notice (including this | |
| * paragraph), and must be clearly marked as modified from the original | |
| * version. | |
| * | |
| *------------------------------------------------------------------------------ | |
| * REVISION HISTORY | |
| * | |
| * 28-Jan-01: Creation. | |
| * 06-Feb-01: Major restructuring! | |
| */ | |
| #ifndef _TRACK_ACTOR_T_ | |
| #define _TRACK_ACTOR_T_ | |
| #include <dijkstra.t> | |
| #pragma C+ | |
| sayDirName: function; | |
| randomElement: function; | |
| TrackActor: object | |
| isActive = nil | |
| traversalStyle = nil | |
| destination = nil | |
| movementCount = 0 | |
| stationPos = 1 | |
| trackPos = 1 | |
| stationList = [] | |
| trackList = [] | |
| failedVertexList = [] | |
| failedEdgeList = [] | |
| failedStationList = [] | |
| /* | |
| * Stations are vertices within the game world multi-graph which | |
| * form the destinations for the tracks the actor must traverse. | |
| * This method sets the actor in motion by setting its station | |
| * list, calculating the track list for the first station, and | |
| * notifying the trackActorDaemon. | |
| */ | |
| setStation( vertexList, type ) = { | |
| self.stationList = []; | |
| self.traversalStyle = type; | |
| if (length( vertexList ) == 1 | |
| && self.traversalStyle != pathTraversal ) | |
| self.stationList += self.location; | |
| self.stationList += vertexList; | |
| self.stationPos = 1; | |
| self.stationListCycles = 0; | |
| povStack.push(self); | |
| self.isActive = true; | |
| self.failedVertexList = []; | |
| self.failedEdgeList = []; | |
| self.setPath( self.location, self.stationList[ self.stationPos ] ); | |
| povStack.pop; | |
| } | |
| /* | |
| * Creates the track list for the destination. Track lists are | |
| * computed using dijkstra's shortest paths algorithm. | |
| */ | |
| setPath( start, dest ) = { | |
| self.trackPos = 1; | |
| self.destination = dest; | |
| self.trackList = Path.shortestPathVertices(start, dest); | |
| } | |
| trackActorDaemon = { | |
| local cur, nxt, edge, povSave; | |
| if ( !self.isActive ) return; | |
| /* set the point of view to this actor */ | |
| povStack.push(self); | |
| /* Reset movement count */ | |
| self.movementCount = 0; | |
| while( self.hasMore ) | |
| { | |
| /* Get the actor's current and next locations */ | |
| cur = self.location; | |
| nxt = self.trackList[ self.trackPos ]; | |
| /* | |
| * Retrieve a passable edge for the current and next | |
| * vertices. | |
| */ | |
| edge = self.getEdge( cur, nxt ); | |
| if ( edge != nil ) | |
| { | |
| if ( self.canMoveToVertex( nxt ) ) | |
| { | |
| /* | |
| * We remove the nxt vertice from the failed | |
| * station list, if it has been added from an | |
| * earlier iteration. | |
| */ | |
| if ( nxt == self.destination ) | |
| self.failedStationList -= nxt; | |
| self.traverseEdge( edge ); | |
| break; | |
| } | |
| /* | |
| * If this vertex is temporarily blocked to the actor we | |
| * need to recalculate the path, eliminating this vertex | |
| * from our calculations -- temporarily. | |
| */ | |
| if ( nxt != self.destination ) | |
| { | |
| self.failedVertexList += nxt; | |
| self.setPath( cur, self.destination ); | |
| self.handleInvalidVertex( nxt ); | |
| continue; | |
| } | |
| } | |
| /* | |
| * The actor's current location is the next location in the | |
| * track list. We simply increment the track position and start | |
| * the process over. This is checked after edge and vertice | |
| * validation to allow for graph self-loops. | |
| */ | |
| if ( cur == nxt ) | |
| continue; | |
| /* | |
| * This route is blocked to the actor. We need to recalculate | |
| * the path, eliminating the failed edge list from our | |
| * calculations. | |
| */ | |
| self.setPath( cur, self.destination ); | |
| if ( nxt == self.destination | |
| && length( self.trackList ) == 0 ) | |
| { | |
| local tmp = [ nxt ]; | |
| self.failedStationList += (tmp - self.failedStationList); | |
| } | |
| self.handleNoValidEdges( cur, nxt ); | |
| } | |
| /* Reset the pov */ | |
| povStack.pop; | |
| } | |
| hasMore = { | |
| ++self.trackPos; | |
| ++self.movementCount; | |
| if ( length(self.stationList - self.failedStationList) == 0) | |
| { | |
| /* | |
| * Dijkstra didn't compute a path for any of the stations, | |
| * so shut the actor down. | |
| */ | |
| self.handleNoValidStations; | |
| return nil; | |
| } | |
| /* | |
| * When we reach the end of the track list we check to see if | |
| * we need to stop or go to the next station. | |
| */ | |
| if ( length(self.trackList) < self.trackPos ) | |
| { | |
| /* Ask traversal style is we should stop moving */ | |
| if ( self.traversalStyle.trackListCompleted(self, | |
| self.stationListCycles) ) | |
| return nil; | |
| /* increment the station position */ | |
| ++self.stationPos; | |
| /* | |
| * If we've reached the end of the station list we need to | |
| * determine if we should stop or recycle through the list. | |
| */ | |
| if ( length(self.stationList) < self.stationPos ) | |
| { | |
| /* Ask traversal style is we should stop moving */ | |
| if ( self.traversalStyle.stationListCompleted(self) ) | |
| return nil; | |
| /* | |
| * Increment the number of times we've been thru the | |
| * station cycle list. | |
| */ | |
| ++self.stationListCycles; | |
| /* set the station position back to 1 */ | |
| self.stationPos = 1; | |
| } | |
| /* Reset the failed vertex and edge lists */ | |
| self.failedVertexList = []; | |
| self.failedEdgeList = []; | |
| /* Compute the track list for the new station */ | |
| self.setPath( self.location, self.stationList[ | |
| self.stationPos ] ); | |
| } | |
| return true; | |
| } | |
| getEdge( cur, nxt ) = { | |
| local i, len, edge, obsList; | |
| local edgeList = [], obstacleEdgeList = []; | |
| /* | |
| * Create an edge list containing each edge object that matches | |
| * the actor's current and next locations. Build a separate | |
| * list from the edge list that contains all edges that have | |
| * obstacles. | |
| */ | |
| len = length( cur.adjacentEdgeList ); | |
| for ( i = 1; i <= len; ++i ) | |
| { | |
| edge = cur.adjacentEdgeList[ i ]; | |
| if ( edge.vertexFrom == cur && edge.vertexTo == nxt ) | |
| { | |
| edgeList += edge; | |
| if ( length( edge.obstacleList ) > 0 ) | |
| { | |
| obstacleEdgeList += edge; | |
| } | |
| } | |
| } | |
| /* remove all edges with obstacles from the edge list */ | |
| edgeList -= obstacleEdgeList; | |
| /* Set edge to nil */ | |
| edge = nil; | |
| if ( length( edgeList ) > 0 ) | |
| { | |
| /* we have at least 1 passable edge */ | |
| edge = randomElement( edgeList ); | |
| return edge; | |
| } | |
| /* | |
| * All edges have obstacles. Check each edge to see if the | |
| * actor can pass all of the obstacles for the given edge. If | |
| * the actor can pass all of the obstacles then travel to the | |
| * first obstacle. | |
| */ | |
| len = length( obstacleEdgeList ); | |
| for ( i = 1; i <= len; ++i ) | |
| { | |
| obsList = obstacleEdgeList[ i ].obstacleList; | |
| if ( self.canPassEdge( obsList ) ) | |
| { | |
| edge = obstacleEdgeList[ i ]; | |
| return edge; | |
| } | |
| } | |
| /* Add the obstacle edge list to the failed edge list */ | |
| self.failedEdgeList += obstacleEdgeList; | |
| return nil; | |
| } | |
| /* | |
| * Test whether the actor can pass all of the obstacles for a | |
| * given edge. Returns true if the actor can; otherwise returns | |
| * nil. | |
| */ | |
| canPassEdge( obstacleList ) = { | |
| local i, len, o; | |
| len = length( obstacleList ); | |
| for ( i = 1; i <= len; ++i ) | |
| { | |
| o = obstacleList[i]; | |
| if ( !self.canPassObstacle( o ) ) | |
| { | |
| /* we can't pass the obstacle */ | |
| return nil; | |
| } | |
| } | |
| /* No obstacle has stopped passage */ | |
| return true; | |
| } | |
| /* | |
| * Test whether the actor can pass the specified obstacles for | |
| * a given edge. The method should return true if passage is | |
| * allowed; nil otherwise. The default simply tests to see | |
| * if the obstacle is closed and locked or not auto open. | |
| */ | |
| canPassObstacle( o ) = { | |
| if ( !o.isopen | |
| && ( o.islocked || o.noAutoOpen ) ) | |
| { | |
| /* we can't pass the obstacle */ | |
| return nil; | |
| } | |
| /* The obstacle has not stopped passage */ | |
| return true; | |
| } | |
| /* | |
| * This method is used to determine if the vertex | |
| * is, for some reason, off-limits to the actor. | |
| */ | |
| canMoveToVertex( v ) = { | |
| return true; | |
| } | |
| isFirstPass = { return (self.movementCount == 1); } | |
| // move to a new location, notifying player of coming and going | |
| traverseEdge( edge ) = { | |
| local old_loc_visible; | |
| local new_loc_visible; | |
| local room, obs; | |
| obs = car(edge.obstacleList); | |
| if ( obs ) | |
| room = obs; | |
| else | |
| room = edge.vertexTo; | |
| /* if it's an obstacle, travel to the obstacle instead */ | |
| while( room != nil && room.isobstacle ) | |
| room = room.destination; | |
| /* do nothing if going nowhere */ | |
| if ( room == nil ) | |
| return; | |
| /* note whether the actor was previously visible */ | |
| old_loc_visible = (self.location != nil | |
| && parserGetMe().isVisible(self.location)); | |
| /* note whether the actor will be visible in the new location */ | |
| new_loc_visible = parserGetMe().isVisible(room); | |
| /* | |
| * if I'm leaving the player's location, and I was previously | |
| * visible, and I'm not going to be visible after the move, and | |
| * the player's location isn't dark, show the "leaving" message | |
| */ | |
| if (parserGetMe().location != nil | |
| && parserGetMe().location.islit | |
| && old_loc_visible | |
| && !new_loc_visible) | |
| self.sayLeaving( edge.dirPtr ); | |
| /* move to my new location */ | |
| self.moveInto( room ); | |
| /* | |
| * if I'm visible to the player at the new location, and I | |
| * wasn't previously visible, and it's not dark, show the | |
| * "arriving" message | |
| */ | |
| if (parserGetMe().location != nil | |
| && parserGetMe().location.islit | |
| && new_loc_visible | |
| && !old_loc_visible) | |
| self.sayArriving( edge.dirPtr ); | |
| } | |
| // sayLeaving and sayArriving announce the actor's departure and arrival | |
| // in the same room as the player. | |
| sayLeaving( dirPtr ) = { | |
| self.location.dispParagraph; | |
| "\^<<self.thedesc>> leaves"; | |
| switch( dirPtr ) | |
| { | |
| case &up: | |
| case &down: | |
| case &in: | |
| case &out: | |
| " the area. "; | |
| break; | |
| default: | |
| " to the <<sayDirName( dirPtr )>>. "; | |
| } | |
| } | |
| sayArriving( dirPtr ) = { | |
| self.location.dispParagraph; | |
| "\^<<self.thedesc>> enters "; | |
| switch( dirPtr ) | |
| { | |
| case &up: | |
| case &down: | |
| case &in: | |
| case &out: | |
| " the area. "; | |
| break; | |
| default: | |
| " from the <<sayDirName( dirPtr )>>. "; | |
| } | |
| } | |
| // Hooks for special transition handling | |
| /* | |
| * This method is called when no valid edges have been found and | |
| * after a new path has been calculated weighting the edges between | |
| * cur and nxt at INFINITY. | |
| */ | |
| handleNoValidEdges( cur, nxt ) = {} | |
| /* | |
| * This method is called when the next vertex is found to be | |
| * impassable to the actor and after a new path has been calculated | |
| * by removing the vertex from dijkstra's considerations. By | |
| * default we remove the vertex from the failed vertex list. | |
| */ | |
| handleInvalidVertex( nxt ) = { | |
| self.failedVertexList -= nxt; | |
| } | |
| /* | |
| * This method is called when all of the stations in the actor's | |
| * station list have been added to the failed station list. This | |
| * means that the actor has failed to find paths for all of the | |
| * stations. By default we stop the actor's attempts to move. | |
| */ | |
| handleNoValidStations = { | |
| self.isActive = nil; | |
| } | |
| /* | |
| * This method is called when the traversal style is cycleTraversal | |
| * and the cycle has been completed. By default we stop the actor's | |
| * attempts to move. | |
| */ | |
| handleCompletedCycle = { | |
| self.isActive = nil; | |
| } | |
| /* | |
| * This method is called when the traversal style is pathTraversal | |
| * and the path has been completed. By default we stop the actor's | |
| * attempts to move. | |
| */ | |
| handleCompletedPath = { | |
| self.isActive = nil; | |
| } | |
| ; | |
| /* | |
| * TraversalStyle indicates whether movement is to stop | |
| * at the points when the end of the track list or station | |
| * list has been reached. | |
| */ | |
| class TraversalStyle: object | |
| trackListCompleted(actor, stationListCycles) = { return nil; } | |
| stationListCompleted(actor) = { return nil; } | |
| ; | |
| pathTraversal: TraversalStyle | |
| /* movement stops when we reach the end of the station list */ | |
| stationListCompleted(actor) = { | |
| actor.handleCompletedPath; | |
| return true; | |
| } | |
| ; | |
| cycleTraversal: TraversalStyle | |
| /* movement stops when we've cycled around the station list once */ | |
| trackListCompleted(actor, stationListCycles) = { | |
| if ( stationListCycles > 0 ) | |
| { | |
| actor.handleCompletedCycle; | |
| return true; | |
| } | |
| return nil; | |
| } | |
| ; | |
| continuousTraversal: TraversalStyle | |
| ; | |
| class Edge: object | |
| vertexFrom = nil | |
| vertexTo = nil | |
| dirPtr = nil | |
| obstacleList = [] | |
| instantiate( vf, vt, d, o ) = { | |
| local e = new Edge; | |
| e.vertexFrom = vf; | |
| e.vertexTo = vt; | |
| e.dirPtr = d; | |
| e.obstacleList = o; | |
| return e; | |
| } | |
| ; | |
| /* | |
| * Say the direction name for the direction pointer. | |
| */ | |
| sayDirName: function( dirPtr ) | |
| { | |
| switch( dirPtr ) | |
| { | |
| case &north: | |
| "north"; | |
| break; | |
| case &ne: | |
| "northeast"; | |
| break; | |
| case &nw: | |
| "northwest"; | |
| break; | |
| case &south: | |
| "south"; | |
| break; | |
| case &se: | |
| "southeast"; | |
| break; | |
| case &sw: | |
| "southwest"; | |
| break; | |
| case &east: | |
| "east"; | |
| break; | |
| case &west: | |
| "west"; | |
| break; | |
| case &up: | |
| "up"; | |
| break; | |
| case &down: | |
| "down"; | |
| break; | |
| case &in: | |
| "in"; | |
| break; | |
| case &out: | |
| "out"; | |
| break; | |
| } | |
| } | |
| /* | |
| * Returns an element of the list selected at random. | |
| */ | |
| randomElement: function( lst ) { | |
| local r, len = length( lst ); | |
| if ( len == 1 ) | |
| r = 1; | |
| else | |
| r = _rand( len ); | |
| return lst[ r ]; | |
| } | |
| povStack: object | |
| povList = [] | |
| currentPov = nil | |
| push( val ) = { | |
| local tmpList = self.povList; | |
| self.currentPov = val; | |
| self.povList = [ val ] + tmpList; | |
| } | |
| pop = { | |
| local cp, val; | |
| val = car(self.povList); | |
| if ( val == nil ) { | |
| val = parserGetMe(); | |
| self.currentPov = val; | |
| } | |
| else | |
| { | |
| self.povList = cdr(self.povList); | |
| cp = car(self.povList); | |
| if ( cp == nil ) | |
| self.currentPov = parserGetMe(); | |
| else | |
| self.currentPov = cp; | |
| } | |
| return val; | |
| } | |
| ; | |
| /* | |
| * Modify Path to weight edges that are found to be unpassable to the | |
| * track actor. If we find a corresponding edge for v & w we return a | |
| * value that will compute to INFINITY for comparison of the edge | |
| * between the two vertices. If we don't find an edge in the track | |
| * actor's failed edge list then we return 1. | |
| */ | |
| modify Path | |
| computeEdge( v, w ) = { | |
| local i, len, e; | |
| local edgeList = []; | |
| edgeList = povStack.currentPov.failedEdgeList; | |
| len = length( edgeList ); | |
| for ( i = 1; i <= len; ++i ) | |
| { | |
| e = edgeList[ i ]; | |
| if ( v.vertex == e.vertexFrom && w.vertex == e.vertexTo ) | |
| return INFINITY; | |
| } | |
| return 1; | |
| } | |
| ; | |
| modify room | |
| adjacentVertexList = [] | |
| adjacentEdgeList = [] | |
| setAdjacentVertices = { | |
| local i, dPtr, w, len, e, obsList; | |
| local dirList = [ &north, &south, &east, &west, &ne, &nw, &se, | |
| &sw, &up, &down, &in, &out ]; | |
| len = length( dirList ); | |
| for ( i = 1; i <= len; ++i ) | |
| { | |
| if ( proptype( self, dirList[ i ] ) == DTY_OBJECT ) | |
| { | |
| dPtr = dirList[i]; | |
| w = self.(dPtr); | |
| /* build a list of all obstacles */ | |
| obsList = []; | |
| while( w != nil && isclass(w, obstacle) ) | |
| { | |
| obsList += w; | |
| w = w.doordest; | |
| } | |
| e = Edge.instantiate(self, w, dPtr, obsList); | |
| self.adjacentEdgeList += e; | |
| /* Ensure that the adjacent vertex list is unique */ | |
| w = [ w ]; | |
| self.adjacentVertexList += (w - self.adjacentVertexList); | |
| } | |
| } | |
| } | |
| getAdjacentVertices = { | |
| return (self.adjacentVertexList - | |
| povStack.currentPov.failedVertexList); | |
| } | |
| ; | |
| initTravelGraph: function { | |
| local o; | |
| o = firstobj(room); | |
| while( o != nil ) | |
| { | |
| o.setAdjacentVertices; | |
| o = nextobj( o, room ); | |
| } | |
| povStack.push(parserGetMe()); | |
| } | |
| replace preinit: function | |
| { | |
| local o; | |
| global.lamplist = []; | |
| o = firstobj(); | |
| while(o != nil) | |
| { | |
| if (o.islamp) | |
| global.lamplist = global.lamplist + o; | |
| o = nextobj(o); | |
| } | |
| initSearch(); | |
| initTravelGraph(); | |
| } | |
| #pragma C- | |
| #endif /* _TRACK_ACTOR_T_ */ | |
Xet Storage Details
- Size:
- 24.6 kB
- Xet hash:
- 851edff9b94e929d950abe09d8b3305d4197aa531c5ac9d88802554f52dfd009
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.