! physics.h
!-----
! Version 1.2  /by\ Mike Tulloch /in\ Feb '05 
! Thanks to Dan Shiovitz for paving the way, 
! and thanks to Sam Hulick for making it all possible. 
!
! Changes:
! 1) Updated routines for Inform 6.30  
! 2) Added PHY_SUGGEST constant, which allows you to turn 
! suggestions if a container is too full/heavy on or off.
! 3) Updated comments
!
! Set the PHY_SUGGEST constant before you include "Physics.h". 
! Set it to 1 or higher to allow suggestions; set it to 0 to disallow them. 
!
! Questions, bugs, praise, email me at poster@aurora.cotse.net. :)
!-----
! Version 1.1
! by Sam Hulick <shulick@indiana.edu>

! This is a slight revision (not by much) of the old physics.inf example
! I released quite awhile ago.  Please read through these comments here
! so you know exactly how it works.

! P.S.: I give  Graham Nelson complete permission to add this into
! his next library release.

! When creating a game using physics.h, you must define a new player
! object so it includes a 'max_space 100', in it, preferably near the 
! 'capacity 100'.
!
! Do *NOT* use the ChangePlayer() routine, as that is intended only
! for changing the player during play. 
!
! Do define a player object somewhere before Initialise() like this:
!	Object newplayer
!		with
!		max_weight 100,  !-- Or whatever you like
!		max_space	100, !-- Or whatever you like
!		description " ";  !-- Put description here if you like
!
! Then after Initialise(), include the following lines:
! player = newplayer;
! give player transparent concealed animate proper;
!
! location=first_room_of_game; !-- whatever it is
!
! Note that max_weight and max_space do NOT work on an object-by-object
! basis, but by weight #'s.  That's the whole point of physics.h.
! Inform's capacity handling routines aren't very detailed, in that you
! could put three large rocks in a bag, and the bag will be full, but
! if you put three small pebbles in the bag, it is strangely full as
! well.  This file will remedy that problem.  When you define objects,
! give it a 'weight' and 'size', i.e.
!
!   Object skull "human skull"
!    with  name "human" "skull",
!          weight 8,
!          size 6,
!          ...
!
! Just figure a decent weight/size scale for your game, and think of
! what size or weight you would give it.  I would give a shovel a
! weight of 10 or 13, perhaps, and a size of 15-20, maybe.  It's
! just estimating.  I give very small objects (pebbles, M&M's, etc.)
! a weight and size of 0, usually, because they're so small.  Or
! you could up-scale your system, making M&M's size/weight both 1,
! then your shovel would more likely have a weight of 30, since you've
! up-scaled your values to compensate for the M&M's.  There are no
! rights or wrongs: it's your game.  I'm just providing the medium
! to control these weights and sizes.

! Now, on to more technical things.  max_weight and max_space are
! used for containers and supporters.  They can be values or
! routines (maybe for magical containers that expand).  I think
! that is self-explanatory.

! too_big and too_heavy are a bit more complicated.  They can simply
! be a string to print to the user when an object is too heavy or
! big to carry, or it can be a routine if you wish.  But on a supporter
! or container, it really MUST be a routine.  When too_heavy or too_big
! is called, 'action' contains either ##Take, ##PutOn, or ##Insert.
! Let's give an example:

! Object mcont "magic container"
!  with  description "You can put things on or in it, ooh.",
!        name "magic" "container",
!        size 5,
!        weight 10,
!        max_weight 30,
!        max_size 30,
!        too_heavy [;
!           switch (action)
!           {
!              ##Take: "The container contains too many heavy things \
!                       for you to handle it.";
!              ##PutOn: "However magical it is, it can't support the \
!                        weight of that.";
!              ##Insert: "That object is too heavy, the magical \
!                         container would rip trying to carry that.";
!           }
!        ], 
!        .....
!
! And too_big works the same way, only it deals with the fact that
! the container can't handle the size of an object.  If  these things
! are unclear to you, you can skim through this file and read the
! source code.  If you do not provide a too_heavy/too_big routine
! or string for an object, default messages will be printed, and
! you don't have to handle examining the 'action' variable.  But
! you will most likely want to handle it yourself, since the default
! messages are.. well.. rather bland.

!-- Also, the default messages refer to "you", and not to the object, 
!-- So you must redefine them for containers and supporters. 

! P.S.: Don't forget to REPLACE AttemptToTakeObject, InsertSub, and
! PutOnSub and include this file after parser and verblib.


Property max_weight alias capacity;    ! max weight something can hold
Property max_space;                    ! max space.....
Property weight;   ! can be a routine.  so can max_weight/max_space
Property size;     ! same for this one
Property too_heavy "That's too heavy for you to carry right now.";
Property too_big "That's too big for you to carry right now.";

! can the carrier take on the weight of obj?  true/false return value
[ OkWeight carrier obj w;
   w = ValueOrRun(obj, weight);
   if ((WeightCarried(carrier) + w) > ValueOrRun(carrier, max_weight)) rfalse;
   rtrue;
];

! can the carrier take on the size of obj?
[ OkSize carrier obj s;
   s = ValueOrRun(obj, size);
   if ((SpaceCarried(carrier) + s) > ValueOrRun(carrier, max_space)) rfalse;
   rtrue;
];

[ WeightCarried obj o total;
   objectloop (o in obj)
   {
      total = total + ValueOrRun(o, weight);
      if (child(o) ~= 0)
         total = total + WeightCarried(o);
   }
   return total;
];

[ SpaceCarried obj o total;
   objectloop (o in obj)
      total = total + ValueOrRun(o, size);
   return total;
];

!! The reason SpaceCarried() isn't recursive is because it merely doesn't
! have to be.  WeightCarried() must be recursive, because if you put a
! huge heavy steel ball into a light sack and carry the sack, this doesn't
! mean the sack relieves your weight.  However, if you can manage to fit a
! very large object into a small sack, then your troubles are reduced. (a
! good example would be a huge Nerf(TM) beachball that is tough to carry
! around, but if you find a small, light box, you can open the box, stuff
! the Nerf(TM) ball into it, and close the box.  The weight carried is
! still the box plus the ball, but now your space carried has reduced.

[ AdviseMoveIt obj;
   print "Try moving some things off ", (the) obj; ".";
];

[ AdvisePullIt obj;
   print "Try removing some things from ", (the) obj; ".";
];

!-- Updated in version 1.2
!-- Dropped in AttemptToTakeObject for RTakeSub
[ AttemptToTakeObject item     ancestor after_recipient i j k a rval;
    ! Try to transfer the given item to the player: return false
    ! if successful, true if unsuccessful, printing a suitable message
    ! in the latter case.
    ! People cannot ordinarily be taken.
    if (item == player) return L__M(##Take, 2);
    if (item has animate) return L__M(##Take, 3, item);

    ancestor = CommonAncestor(player, item);

    if (ancestor == 0) {
        i = ObjectScopedBySomething(item);
        if (i ~= 0) ancestor = CommonAncestor(player, i);
    }

    ! Is the player indirectly inside the item?
    if (ancestor == item) return L__M(##Take, 4, item);

    ! Does the player already directly contain the item?
    if (item in player) return L__M(##Take, 5, item);

    ! Can the player touch the item, or is there (e.g.) a closed container
    ! in the way?
    if (ObjectIsUntouchable(item, false, true)) return;

    ! The item is now known to be accessible.

    ! Consult the immediate possessor of the item, if it's in a container
    ! which the player is not in.

    i = parent(item);
    if (i ~= ancestor && (i has container || i has supporter)) {
        after_recipient = i;
        k = action; action = ##LetGo;
        if (RunRoutines(i, before) ~= 0) { action = k; rtrue; }
        action=k;
    }

    if (item has scenery) return L__M(##Take, 10, item);
    if (item has static)  return L__M(##Take, 11, item);

    ! The item is now known to be available for taking.  Is the player
    ! carrying too much?  If so, possibly juggle items into the rucksack
    ! to make room.

    k = 0; objectloop (j in player) if (j hasnt worn) k++;

	!-- 1.2 Is the item too heavy to be carried?
	 if (OkWeight(player, item) == 0)
    {
       a = action;
       action = ##Take;
       rval = PrintOrRun(item, too_heavy, 0);
       action = a;
       return rval;
    } 

	!-- 1.2 just the original code here follows for this if. 
	!-- Is object too large for player to carry? 
	if (OkSize(player, noun) == 0)
  	{
      if (SACK_OBJECT~=0)
      {   if (parent(SACK_OBJECT)~=player)
              return ObjTooBig(noun);
          j=0;
          objectloop (k in player) 
              if (k~=SACK_OBJECT && k hasnt worn && k hasnt light) j=k;

          if (j~=0)
          {   L__M(##Take,13,j);
              keep_silent = 1; <Insert j SACK_OBJECT>; keep_silent = 0;
              if (j notin SACK_OBJECT) rtrue;
          }
          else return ObjTooBig(noun);
      }     
      else return ObjTooBig(noun);
  }


    if (k >= ValueOrRun(player, capacity)) {
        if (SACK_OBJECT ~= 0) {
            if (parent(SACK_OBJECT) ~= player)
                return L__M(##Take, 12);
            j = 0;
            objectloop (k in player)
                if (k ~= SACK_OBJECT && k hasnt worn && k hasnt light) j = k;

            if (j ~= 0) {
                L__M(##Take, 13, j);
                keep_silent = 1; <Insert j SACK_OBJECT>; keep_silent = 0;
                if (j notin SACK_OBJECT) rtrue;
            }
            else return L__M(##Take, 12);
        }
        else return L__M(##Take, 12);
    }

    ! Transfer the item.

    move item to player;

    ! Send "after" message to the object letting go of the item, if any.

    if (after_recipient ~= 0) {
        k = action; action = ##LetGo;
        if (RunRoutines(after_recipient, after) ~= 0) { action = k; rtrue; }
        action=k;
    }
    rfalse;
];


[ ObjTooBig obj a rval;
   a = action;
   action = ##Take;
   rval = PrintOrRun(obj, too_big, 0);
   action = a;
   return rval;
];

!-- 1.2 : Added a to routine and added tests for PHY_SUGGEST 
[ PutOnSub ancestor a;
    receive_action = ##PutOn;
    if (second == d_obj || player in second) <<Drop noun>>;
    if (parent(noun) == second) return L__M(##Drop,1,noun);
    if (parent(noun) ~= player) return L__M(##PutOn, 1, noun);

    ancestor = CommonAncestor(noun, second);
    if (ancestor == noun) return L__M(##PutOn, 2, noun);
    if (ObjectIsUntouchable(second)) return;

    if (second ~= ancestor) {
        action = ##Receive;
        if (RunRoutines(second, before) ~= 0) { action = ##PutOn; return; }
        action = ##PutOn;
    }
    if (second hasnt supporter) return L__M(##PutOn, 3, second);
    if (ancestor == player) return L__M(##PutOn, 4);
    if (noun has worn) {
        L__M(##PutOn, 5, noun); 
		<Disrobe noun>; 
		if (noun has worn) return;
    }

	!-- Is item small enough to fit on second?
	!-- Original routine
	if (OkSize(second, noun) == 0)
  {
     a = action;
     action = ##PutOn;
     if (RunRoutines(second, too_big) == 0)
     {
        ! default message
        print_ret (The) noun, " won't fit on ", (the) second, ".";
     }
     action = a;
     if ((ValueOrRun(noun, size) <= ValueOrRun(second, max_space)) && PHY_SUGGEST)
        return AdviseMoveIt(second);
     rtrue;
  }

	!-- Is item light enough to put on the second?
	!-- Original routine. 
  if (OkWeight(second, noun) == 0)
  {
     a = action;
     action = ##PutOn;
     if (RunRoutines(second, too_heavy) == 0)
     {
        print_ret "You feel ", (the) second, " begin to give as you start to put ",
              (the) noun, " on it.  Maybe this isn't such a good idea.";
     }
     action = a;
     if ((ValueOrRun(noun, weight) <= ValueOrRun(second, max_weight)) && PHY_SUGGEST)
        return AdviseMoveIt(second);
     rtrue;
  }

    if (children(second) >= ValueOrRun(second, capacity))
        return L__M(##PutOn, 6, second);

    move noun to second;

    if (AfterRoutines() == 1) return;

    if (second ~= ancestor) {
        action = ##Receive;
        if (RunRoutines(second, after) ~= 0) { action = ##PutOn; return; }
        action = ##PutOn;
    }
    if (keep_silent == 1) return;
    if (multiflag == 1) return L__M(##PutOn, 7);
    L__M(##PutOn, 8, noun);
];


!-- 1.2: Added a to the routine, and added PHY_SUGGEST tests.
!-- all else is unchanged. 
[ InsertSub ancestor a;
    receive_action = ##Insert;
    if (second == d_obj || player in second) <<Drop noun>>;
    if (parent(noun) == second) return L__M(##Drop,1,noun);
    if (parent(noun) ~= player) return L__M(##Insert, 1, noun);

    ancestor = CommonAncestor(noun, second);
    if (ancestor == noun) return L__M(##Insert, 5, noun);
    if (ObjectIsUntouchable(second)) return;

    if (second ~= ancestor) {
        action = ##Receive;
        if (RunRoutines(second,before) ~= 0) { action = ##Insert; rtrue; }
        action = ##Insert;
        if (second has container && second hasnt open)
            return L__M(##Insert, 3, second);
    }
    if (second hasnt container) return L__M(##Insert, 2, second);

    if (noun has worn) {
        L__M(##Insert, 6, noun); <Disrobe noun>; if (noun has worn) return;
    }

    if (children(second) >= ValueOrRun(second, capacity))
        return L__M(##Insert, 7, second);
	
	if (OkSize(second, noun) == 0)
  		{
     	a = action;
     	action = ##Insert;
     	if (RunRoutines(second, too_big) == 0)
     		{
        	print_ret "There's not enough room inside.";
     		}
     	action = a;
     	if ((ValueOrRun(noun, size) <= ValueOrRun(second, max_space)) && PHY_SUGGEST)
        return AdvisePullIt(second);
     	rtrue;
  		}
  
	if (OkWeight(second, noun) == 0)
	  	{
     	a = action;
     	action = ##Insert;
     	if (RunRoutines(second, too_heavy) == 0)
     		{
        	print_ret (The) second, " won't hold anymore weight.";
     		}
     	action = a;
     	if ((ValueOrRun(noun, weight) <= ValueOrRun(second, max_weight)) && PHY_SUGGEST)
		return AdvisePullIt(second);
     	rtrue;
  		}

    move noun to second;

    if (AfterRoutines() == 1) rtrue;

    if (second ~= ancestor) {
        action = ##Receive;
        if (RunRoutines(second, after) ~= 0) { action = ##Insert; rtrue; }
        action = ##Insert;
    }
    if (keep_silent == 1) rtrue;
    if (multiflag == 1) return L__M(##Insert, 8, noun);
    L__M(##Insert, 9, noun);
];

