#charset "us-ascii"
#include "advlite.h"


/*
 *   VERSION 2022-09-03 15:48
 */

modify Thing
    iobjFor(PushTravelThrough)
    {
        preCond = [travelPermitted, touchObj]
    }
    
    iobjFor(PushTravelClimbUp)
    {
        preCond = [travelPermitted, touchObj]        
    }
    
    iobjFor(PushTravelClimbDown)
    {
        preCond = [travelPermitted, touchObj]        
    }
    
    beforeMovePushable(connector, dir)
    {
        /* make a note of our connector */
        local conn = connector;
         
        /* 
         *   If our connector is an UnlistedProxyConnector we need some special handling to identify
         *   the real connector we're going to use.
         */
        if(connector.ofKind(UnlistedProxyConnector))
        {
            /* Note the room we're in. */
            local loc = getOutermostRoom;
            
                    
            /* Get the direction prop our UnlistedProxyConnector is a proxy for. */
            local prop = connector.direction.dirProp;
            
            if(loc.propType(prop) == TypeObject)  
            {
                
                /* Get the connector that direction property points to. */
                conn = loc.(prop);                
                
                /* Make that connector the iobj of this action. */
                gIobj = conn; 
            }
            else
            {
                /*  Otherwise note the direction we're actually going to try to go in. */
                dir = connector.direction;
                
                /* If the connector isn't an object, we don't want to deal with it here. */
                conn = nil;
            }
        }
            
        /* 
         *   Next check that there's nothing that wants to disallow this travel.. If the
         *   travelPermitted preCond is present for this action on this object this should already
         *   have been done, but if game code has overridden that we need to carry out the
         *   beforeTravelNotifications now.
         */         
        if(conn && dataType(conn == TypeObject))
            conn.beforeTravelNotifications(self);        
        
        
        // I've commented these lines out as they're the ones causing the duplicated messages.
        
//        /* 
//         *   if there's no connector this way or it's a room, just describe our travel as push
//         *   travel to the appropriate direction.
//         */
//        if(connector == nil || connector.ofKind(Room))
//            sayPushTravel(dir);
       
        
        
        
        /*  If we have an indirect object, describe our PushTravel via it */
        if(gIobj)
            describePushTravel(gAction.viaMode);    
        
        /*  
         *   Otherwise we have a travel connector to travel through, report which direction we're
         *   pushing to.
         */
        else if(dataType(conn) == TypeObject && conn.ofKind(TravelConnector))
            sayPushTravel(dir);
        
        /*  
         *   Otherwise do nothing, because our 'connector' must be a string or method that explains
         *   why travel that way isn't possible.
         */
    }
        
    
    
    dobjFor(PushTravelDir)
    {
        preCond = [touchObj, travelPermitted]
        
        check()
        {
            /* set up a local variable to hold the connector we want to travel through. */
            local conn;
            
            /* note which room we're in */
            local loc = getOutermostRoom;
            
            /* Get the direction of travel from the command */
            local dirn = gCommand.verbProd.dirMatch.dir;
            
            if(loc.propType(dirn.dirProp) == TypeObject)
            {
                /* Note the connector object in the relevant direction */
                conn = loc.(dirn.dirProp);
                
                /* 
                 *   If our connector is an UnlistedProxyConnector we need to replace the direction
                 *   we're heading in with the one the UPC points to and our connector with the one
                 *   our new direction points to.
                 */
                if(conn.ofKind(UnlistedProxyConnector))
                {
                    /* get our real direction of travel. */
                    dirn = conn.direction; 
                    
                    /* get the connector in that direction, or nil if it's not an object */
                    conn = loc.propType(dirn.dirProp) == TypeObject ? loc.(dirn.dirProp) : nil;
                    
                }                                 
                
                /* If the connector we want to use is an object then check its travel barriers. */
                if(dataType(conn) == TypeObject)
                {                    
                    if(conn.checkTravelBarriers(self))
                        conn.checkTravelBarriers(gActor);
                }
            }
            
            
        }
    }
    
    /* Display a message saying we pushed the direct object in a particular direction. */
    sayPushTravel(dir)
    {
        DMsg(before push travel dir, '{I} push{es/ed} {the dobj} {1}. ',
                 dir.departureName);
    }
;

modify TravelConnector
    iobjFor(PushTravelThrough)
    {
        preCond = [travelPermitted]
        verify() 
        {  
            
        }
        
        check() { checkPushTravel(); }       
    }
    
    /* 
     *   The appropriate PushTravelAction for pushing something something
     *   through a TravelConnector.
     */
    PushTravelVia = PushTravelThrough
    
    /*  
     *   If the actor doing the traveling is the player character, display the
     *   travelDesc. Note that although this might normally be a simple
     *   description of the travel, the travelDesc method could also be used to
     *   carry out any other side-effects of the travel via this connector.
     *
     *
     */
    noteTraversal(actor)
    {
        if(actor == gPlayerChar && !(gAction.isPushTravelAction && suppressTravelDescForPushTravel))
        {                
            travelDesc;
            "<.p>";
        }
    }
    
    /* 
     *   If we display a message for pushing something via us, we probably don't also want the
     *   travelDesc describing the actor's travel. Game code can override if both messages are
     *   wanted when push-travelling.
     */
    suppressTravelDescForPushTravel = true
    
;

/*  
 *   PreCondition that calls before travel notifications (such as beforeTravel() on every object in
 *   scope) so these can rule out travel/push travel before it's attempted.
 */
travelPermitted: PreCondition
    checkPreCondition(obj, allowImplicit) 
    { 
      
        obj.beforeTravelNotifications(gAction.isPushTravelAction ? gDobj : gActor);
        
        return true;
    }
    
    preCondOrder = 50
;


modify PushTravelThrough
    viaMode = Through
    
    isPushTravelAction = true
    
    addExtraScopeItems(role)
    {
        /* 
         *   If our indirect object is a TravelConnector it may not be a physical object that would
         *   normally be considered in scope, so we need to add it to our scope list.
         */        
        if (curIobj && curIobj.ofKind(TravelConnector))
            scopeList = scopeList.appendUnique(valToList(curIobj));
        
        /* 
         *   Append the extra scope items defined on this Room to the action's
         *   scope list.
         */
        inherited(role);
    }
;

modify PushTravelEnter
    viaMode = Into
    
    isPushTravelAction = true
;

modify PushTravelGetOutOf
    viaMode = OutOf
    
    isPushTravelAction = true
;

modify PushTravelClimbUp
    viaMode = Up
    
    isPushTravelAction = true
;

modify PushTravelClimbDown
    viaMode = Down
    
    isPushTravelAction = true
;

modify PushTravelDir
    isPushTravelAction = true
    
    execAction(cmd)
    {
        local conn;
        
        /* Note whether travel is allowed. This can be adjusted by the dobj */
        travelAllowed = nil;
        
        /* Get the direction of travel from the command */
        direction = cmd.verbProd.dirMatch.dir;
        
        /* Note the actor's location. */
        local loc = gActor.getOutermostRoom; 
        
        /* Note whether we meed the lighting conditions to permit travel */
        local illum = loc.allowDarkTravel || loc.isIlluminated;
        
        /* 
         *   first find out what our direction might take us to; if it's an object that defines the
         *   PushTravlVia property, change the action to PushTravelVia that connector
         */
        
    retry:
        /* 
         *   See if the direction we're due to go in points to an object and if so process it
         *   accordingly.
         */
        if(loc.propType(direction.dirProp) == TypeObject)
        {
            /* Note the connector object in the relevant direction */
            conn = loc.(direction.dirProp); 
            
            /* 
             *   If this connector is an UnlistedProxy Connector we need to carry out the rest of
             *   the processing on whatever it's a proxy for.
             */
            if(conn.ofKind(UnlistedProxyConnector))
            {
                /* Reset our direction to that of the UnlistedProxyConnector. */
                direction = conn.direction;
                
                /* Start over again with our new direction. */
                goto retry;                  
            }
            
            /*  
             *   If the connector object defines a PushTravelVia action, then replace the current
             *   action with that PushTravelVia action (e.g. PushTravelGoThrough or
             *   PushTravelClimbUp).
             */
            if(conn.PushTravelVia)
                replaceAction(conn.PushTravelVia, gDobj, conn);
            
            /* 
             *   Maybe conn looks like an object but is actually an anonymous or dynamic function,
             *   in which case we can try to execute it.
             */
            if(dataTypeXlat(conn) == TypeFuncPtr)
            {
                try
                {
                    conn();                    
                }
                catch (Exception ex)
                {
                    "Problem with function object attached to the <<direction.name>> property of
                    <<loc.name>>.<.p>";
                    
                    ex.displayException();
                }
            }
            else        
                
            /* 
             *   if we reach this point, there must be something fishy going on, since we really
             *   shouldn't have any other kind of object attached to a direction property, so
             *   display a message saying so.
             */                
                "<b>ERROR!</b> Illegal object <<conn>> attached to the <<direction.name>> property 
                of <<loc.name>>. ";
            
        }
        /* 
         *   If our direction isn't attached to an object, it must be to a method or string that's
         *   going to display a message explaining why we can't travel.
         *
         *   (In due course this can be wrapped up into a separate method that could also be used on
         *   TravelAction).
         */
        else
        {
                switch (loc.propType(direction.dirProp))
                {
                    /* 
                     *   If there's nothing there, simply display the appropriate message explaining
                     *   that travel that way isn't possible.
                     */
                case TypeNil:
                    if(illum && gActor == gPlayerChar)
                        loc.cannotGoThatWay(direction);
                    else if(gActor == gPlayerChar)
                        loc.cannotGoThatWayInDark(direction);            
                    break;
                    
                    
                    /* 
                     *   If the direction property points to a double-quoted method or a string,
                     *   then provided the illumination is right, we display the string or execute
                     *   the method. Otherwise show the message saying we can't travel that way in
                     *   the dark.
                     */            
                case TypeDString:
                case TypeCode:                
                    if(illum)
                    {
                        /* 
                         *   Call the before travel notifications on every object that's in scope
                         *   for the actor. Since we don't have a connector object to pass to the
                         *   beforeTravel notifications, we use the direction object instead.
                         */
                        Q.scopeList(gActor).toList.forEach({x: x.beforeTravel(gActor,
                            direction)});
                        
                        
                        /*  
                         *   If going this way would take us to a known destination that's a Room
                         *   (so that executing the travel should take the actor out of his/her
                         *   current room) notify the current room that the actor is about to
                         *   depart.
                         */                
                        local dest;
                        
                        if(loc.propType(direction.dirProp) == TypeCode)                
                            dest = libGlobal.extraDestInfo[[loc, direction]];
                        else
                            dest = nil;
                        
                        if(dest && dest.ofKind(Room))
                            loc.notifyDeparture(gActor, dest);
                        
                        /*  
                         *   Then execute the method or display the double-quoted string.
                         */
                        loc.(direction.dirProp);
                        
                        /* 
                         *   If we've just executed a method, it may have moved the actor to a new
                         *   location, so if the actor is the player character note where the method
                         *   took the actor to so that the pathfinder can find a route via this
                         *   exit.
                         */
                        if(gActor == gPlayerChar)
                            libGlobal.addExtraDestInfo(loc, direction,
                                gActor.getOutermostRoom);
                    }
                    else if(gActor == gPlayerChar)
                        loc.cannotGoThatWayInDark(direction);
                    break;
                    
                    /* 
                     *   If the direction property points to a single-quoted string, simply display
                     *   the string if the illumination is sufficient, otherwise display the message
                     *   saying we can't go that way in the dark. If the actor isn't the player
                     *   character, do nothing.
                     */
                case TypeSString:
                    if(gActor == gPlayerChar)
                    {
                        conn = loc.(direction.dirProp);
                        if(illum)
                        {
                            say(conn);
                            libGlobal.addExtraDestInfo(loc, direction,
                                gActor.getOutermostRoom); 
                        }
                        else
                            loc.cannotGoThatWayInDark(direction);
                    }    
                    break;
                    
                }        
            }
        }
    
        
        
        /* 
//         *   Carry out the inherited handling, including calling dobjFor(Travel)
//         *   on the dobj
//         */
//        inherited(cmd);
//        
//        /* Proceed to carry out the travel if the dobj allows it */
//        if(travelAllowed)
//        {
//            /* Note the old location, which is the actor's current room. */ 
////           local loc = gActor.getOutermostRoom; 
//            
//           /*  
//            *   If the relevant direction property of the actor's current room
//            *   points to an object, then try pushing the dobj via that object
//            *   (e.g. up the stairs or through the door).
//            */ 
//           if(loc.propType(direction.dirProp) == TypeObject)
//           {
////                /* Note the connector object in the relevant direction */
////                conn = loc.(direction.dirProp);
////                
////                /*  
////                 *   If the connector object defines a PushTravelVia action,
////                 *   then replace the current action with that PushTravelVia
////                 *   action (e.g. PushTravelGoThrough or PushTravelClimbUp).
////                 */
////                if(conn.PushTravelVia)
////                    replaceAction(conn.PushTravelVia, gDobj, conn);
//                               
//                /* 
//                 *   Otherwise, if the travel barriers would not allow the dobj
//                 *   to pass or the actor to pass, stop the action here.
//                 */
////                if(!conn.checkTravelBarriers(curDobj) 
////                   || !conn.checkTravelBarriers(gActor))
////                {                    
////                    return;
////                }
//                
//            }
//            
//            /* 
//             *   If the direction property isn't attached to an object, the
//             *   chances are that travel won't be allowed, so in that case we
//             *   don't want to display a message about attempting to push the
//             *   direct object anywhere, but if it is, then the chances are that
//             *   travel is possible (unless it's already been ruled out) so we
//             *   display a suitable message.
//             */
//            if(loc.propType(direction.dirProp) == TypeObject)
//                gDobj.beforeMovePushable(conn, direction);
//            
//            /* 
//             *   Temporarily set the isHidden property of the direct
//             *   object to true so we don't see it listed in its old location if
//             *   there's a sight path to it there from the actor's new location.
//             */
//            
//            local wasHidden;
//            try
//            {
//                wasHidden = gDobj.propType(&isHidden) is in (TypeCode, TypeFuncPtr) ?
//                    gDobj.getMethod(&isHidden) : isHidden;
//                
//                gDobj.isHidden = true;
//                
//                /* 
//                 *   Carry out the standard handling of TravelAction to move the
//                 *   actor in the appropriate direction
//                 */ 
//                delegated TravelAction(cmd);
//            }
//            finally
//            {
//                if(dataTypeXlat(wasHidden) is in (TypeCode, TypeFuncPtr))
//                    gDobj.setMethod(&isHidden, wasHidden);
//                else
//                    gDobj.isHidden = wasHidden;
//            }
//            
//            
//            /* 
//             *   If the actor has moved to a new location, move the dobj to that
//             *   location and report what's happened.
//             */
//            if(loc != gActor.getOutermostRoom)
//            {
//                curDobj.moveInto(gActor.getOutermostRoom);
//                curDobj.describeMovePushable(conn, gActor.getOutermostRoom);
//            }
//        }
//    }
;


modify Door
    dobjFor(GoThrough)
    {
        preCond = [travelPermitted, touchObj, objOpen]
    }
    
    iobjFor(PushTravelThrough)
    {
        preCond = [travelPermitted, touchObj, objOpen]
    }
;

modify Passage
    dobjFor(GoThrough)
    {
        preCond = [travelPermitted, touchObj]
    }
    
    iobjFor(PushTravelThrough)
    {
        preCond = [travelPermitted, touchObj]
    }    
;


/* These modifications aren't needed yet but are included for possible future use. */
modify UnlistedProxyConnector
    
    proxyForProp = direction.dirProp
    
    /* 
     *   The loc parameter should contain the room in which this UnlistedProxyConnector is used, but
     *   calling code will need to supply it.
     */
    proxyForConnector(loc)
    {
        local ptype = loc.propType(proxyForProp);
        
        return ptype == TypeObject ? loc.(proxyForProp) : ptype;       
            
    }    
    
    /* 
     *   Return the actual destination, if any, an actor will arrive at by traversing the connector
     *   we're a proxy for from origin.
     */
    getDestination(origin)
    {
        local conn = proxyForConnector(origin);
        
        return dataTypeXlat(conn) == TypeObject && conn.ofKind(TravelConnector) 
            ? conn.getDestination(origin) : nil;
        
    }
;
 