Forum

func_train, one point at a time?

Discuss programming in the QuakeC language.

Moderator: InsideQC Admins

func_train, one point at a time?

Postby dayfive » Thu Feb 28, 2008 2:21 am

hi,

is there presently any way in the plain generic quake engine to instead of having a trigger_once or trigger_multiple start a func_train in motion and not be able to stop it instead have some kind of trigger once to make the train simply increment one point along its path?
User avatar
dayfive
 
Posts: 77
Joined: Fri Nov 10, 2006 9:48 pm

Postby frag.machine » Thu Feb 28, 2008 4:14 am

This can be handled in the QuakeC side, and I think mods like Quoth already do it.
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
User avatar
frag.machine
 
Posts: 2090
Joined: Sat Nov 25, 2006 1:49 pm

Postby Willem » Thu Feb 28, 2008 10:49 am

I'm pretty sure this code does what you're asking. It's a func_train adaptation that will stop at any path_corner that has a wait of -1. When you trigger it again, it will move to the next one in the path.

If this doesn't work, I apologize, it may have rotted since I last used it.

You'll have to add some variables to def.qc like "isMoving". I hope this at least gets you on the right track.

Or use Quoth. :P


Code: Select all
// ================================================================
// FUNC_MOVER
// ================================================================

// Forward declarations

void() func_mover_next;
void() func_mover_use;
void() func_mover_wait;

void() func_mover_blocked =
{
   if( time < self.attack_finished )
   {
      return;
   }
   
   self.attack_finished = time + 0.5;
   
   T_Damage (other, self, self, self.dmg);
};

// Triggered.

void() func_mover_use =
{
   // Ignore triggers if we are already moving
   
   if( self.isMoving )
   {
      return;
   }
   
   func_mover_next();
};

void() func_mover_wait =
{
   sound( self, CHAN_VOICE, self.noise, 1, ATTN_NORM );

   if( self.wait )
   {
      self.nextthink = self.ltime + self.wait;
   }
   else
   {
      self.nextthink = self.ltime + 0.01;
   }
   
   self.isMoving = FALSE;
   self.think = func_mover_next;
};

void() func_mover_next =
{
   local entity targ;

   targ = find( world, targetname, self.target );
   self.target = targ.target;
   
   if( !self.target )
   {
      objerror( "mover_next: no next target" );
   }
      
   if( targ.wait )
   {
      self.wait = targ.wait;
   }
   else
   {
      self.wait = 0;
   }
   
   self.isMoving = TRUE;   
   sound( self, CHAN_VOICE, self.noise1, 1, ATTN_NORM );
   SUB_CalcMove( targ.origin - self.mins, self.speed, func_mover_wait );
};

void() func_mover_find =
{
   local entity targ;

   // Find the first path_corner we are targeting
   targ = find( world, targetname, self.target );
   self.target = targ.target;
   
   // Move to the target
   setorigin( self, targ.origin - self.mins );
   
   // If this mover isn't being targetted, make it start moving immediately
   if( !self.targetname )
   {   
      // not triggered, so start immediately
      self.nextthink = self.ltime + 0.1;
      self.think = func_mover_next;
   }
};

/*QUAKED func_mover (.8 .5 .0) ?
A mover is a brush entity that can move to path_corners.

This works a lot like a func_train except that each time the mover stops, it can be triggered again.
Set up a series of path_corners with a wait of -1 to create a mover that moves and stops at waypoints.

"sounds =
0) None
1) Train
2) Base
3) Medieval
*/
void() func_mover =
{   
   if( !self.speed )
   {
      self.speed = 100;
   }
      
   if (!self.target)
   {
      objerror ("func_mover without a target");
   }
      
   if( !self.dmg )
   {
      self.dmg = 2;
   }
      
   self.isMoving = FALSE;

   self.cnt = 1;
   self.solid = SOLID_BSP;
   self.movetype = MOVETYPE_PUSH;
   self.blocked = func_mover_blocked;
   self.use = func_mover_use;
   self.classname = "mover";
   
   if (self.sounds == 0)
   {
      precache_sound ("misc/null.wav");
      self.noise = ("misc/null.wav");
      precache_sound ("misc/null.wav");
      self.noise1 = ("misc/null.wav");
   }
   else if (self.sounds == 1)
   {
      precache_sound ("plats/train2.wav");
      self.noise = ("plats/train2.wav");
      precache_sound ("plats/train1.wav");
      self.noise1 = ("plats/train1.wav");
   }
   else if (self.sounds == 2)
   {
      precache_sound ("plats/plat2.wav");
      self.noise = "plats/plat2.wav";
      precache_sound ("plats/plat1.wav");
      self.noise1 = "plats/plat1.wav";
   }
   else if (self.sounds == 3)
   {
      precache_sound ("plats/medplat2.wav");
      self.noise = "plats/medplat2.wav";
      precache_sound ("plats/medplat1.wav");
      self.noise1 = "plats/medplat1.wav";
   }

   setmodel( self, self.model );
   setsize( self, self.mins, self.maxs );
   setorigin( self, self.origin );
   
   // Start movers on the second frame, to make sure their targets have had a chance to spawn
   self.nextthink = self.ltime + 0.1;
   self.think = func_mover_find;
};
Willem
 
Posts: 73
Joined: Wed Jan 23, 2008 10:58 am

Postby Dr. Shadowborg » Thu Feb 28, 2008 4:28 pm

See Rogue's Dissolution of Eternity source as well.
User avatar
Dr. Shadowborg
InsideQC Staff
 
Posts: 1110
Joined: Sat Oct 16, 2004 3:34 pm

Postby leileilol » Thu Feb 28, 2008 6:33 pm

p0x's Extras has this too
i should not be here
leileilol
 
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Postby OneManClan » Tue Apr 13, 2010 8:12 am

Can this function be used to make a (TF2 style) payload map for Quake1? ie to make the 'cart' stop and start along a fixed path?

ie
If there are Blue players near the cart it moves (slowly)along its path. If there are no Blue players near the cart for (eg) 20 secs, it slowly moves backwards. The cart gives health and ammo to the Blue team.


Possible?


OneManClan
OneManClan
 
Posts: 243
Joined: Sat Feb 28, 2009 2:38 pm

Postby c0burn » Tue Apr 13, 2010 9:20 am

Of course it's possible.
c0burn
 
Posts: 208
Joined: Fri Nov 05, 2004 12:48 pm
Location: Liverpool, England

Postby OneManClan » Tue Aug 09, 2011 8:03 am

Dear Gurus,

I've started work on a TF2 style 'payload' adaptation for Q1, and have been studying Willem's func_mover concept but I'm still confused as to exactly *how* it works. If anyone can answer my 5 questions (in the comments below) then that would be great! I've put 'OMC' before my comments, and added 'Willem' to his original ones.

thanks,

OneManClan
Note: This is an excerpt from the complete Willem's func_mover which he posted above.
Code: Select all
// OMC the 'func_mover' is sitting there waiting to be triggered (IIUC)
void() func_mover_wait =
{
   sound( self, CHAN_VOICE, self.noise, 1, ATTN_NORM );


   if( self.wait )
   {
   /* OMC Q1: defs.qc says that  .ltime is something that FUNC_PLATS use instead of time, but how do they work, and where is self.ltime set? */
      self.nextthink = self.ltime + self.wait;
   }
   else
   {
      self.nextthink = self.ltime + 0.01;
   }
   
   self.isMoving = FALSE;
   self.think = func_mover_next;
};


void() func_mover_next =
{
   local entity targ;

   targ = find( world, targetname, self.target );
   self.target = targ.target;
   

   // OMC Q2: Waypoint ents start with 'wait = -1', so why/when would targ ever not be -1?
   if( targ.wait )
   {

      self.wait = targ.wait;
   }
   else
   {
      self.wait = 0;
   }
   
   self.isMoving = TRUE;   
   sound( self, CHAN_VOICE, self.noise1, 1, ATTN_NORM );

   // OMC Q3: This function does not run every frame, so how does the func_mover move?
   SUB_CalcMove( targ.origin - self.mins, self.speed, func_mover_wait );
};


// OMC I think this only runs once, when the map is loaded
void() func_mover_find =
{
   local entity targ;

   // Willem Find the first path_corner we are targeting
   targ = find( world, targetname, self.target );
   self.target = targ.target;
   
   // Willem Move to the target
   // OMC Q4: IIUC self.mins is used in 'setsize' - what is it doing here?
   setorigin( self, targ.origin - self.mins );
   
   // If this mover isn't being targetted, make it start moving immediately
   if( !self.targetname )
   {
        // OMC Q5. Why would it move it if doesnt have a self.targetname?
      // not triggered, so start immediately
      self.nextthink = self.ltime + 0.1;
      self.think = func_mover_next;
   }
};
OneManClan
 
Posts: 243
Joined: Sat Feb 28, 2009 2:38 pm

Postby Spike » Tue Aug 09, 2011 3:10 pm

A1: the engine increases self.ltime by as much as time increases, *except* if the object was blocked by something, so thinks will be delayed if the object was blocked. This is special behavior only for movetype_push.

A2: negative wait generally means 'infinite'. looks like that value isn't supported. certainly the extra logic to test if its zero is useless.

A3: movetype_push objects move by setting velocity, and should not be moved using setorigins, other than to attempt to enforce a safe starting point for a move.

A4: inline bsp objects tend to have a default origin of '0 0 0'. Their positioning is typically based upon their mins coord rather than their center.
That is, a 64*64*64 cube object centered '256 256 256' will have origin/mins/max of '0 0 0'/'224 224 224'/'288 288 288', once a setmodel has been used on it, anyway.
Waypoints are positioned at the absmin point that the object would have if it was waiting at that waypoint.
Or in other words, pusher.origin + pusher.mins == waypoint.origin
Rearrange to calculate pusher.origin relative to pusher.mins and the waypoint.origin.

A5: objects without a targetname cannot be targetted. such objects thus tend to automatically begin moving, because they're useless otherwise - you might as well have used func_wall.
Spike
 
Posts: 2892
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Postby OneManClan » Tue Aug 09, 2011 6:34 pm

Thanks for the great info Spike. Much to think about. Meanwhile re:

Spike wrote:A2: negative wait generally means 'infinite'. looks like that value isn't supported. certainly the extra logic to test if its zero is useless.


I had another look at the code, and just realised that 'if()' returns TRUE, if given a negative number!!! ie:
Code: Select all
local float foo;
foo = -1.0;

if(foo)
dprint("This prints!!\n");


In other words:
Code: Select all
if( self.wait ) // this is TRUE if self.wait == -1

   {
      self.nextthink = self.ltime + self.wait;
      // we just gave .nextthink a negative value, which (apparantly) is 'valid'! IIUC, it just means that the .think will run when (and if) the .nextthink value is ever changed

   }


Ugh, so many hours wasted being (needlessly) confused! Still, at least this bit makes sense now.

Thanks again Spike.
OneManClan
 
Posts: 243
Joined: Sat Feb 28, 2009 2:38 pm

Postby Spike » Wed Aug 10, 2011 12:44 pm

'truth' is any value which is not 0. sadly in QC that also includes -0, which can result in some confusion...
Spike
 
Posts: 2892
Joined: Fri Nov 05, 2004 3:12 am
Location: UK


Return to QuakeC Programming

Who is online

Users browsing this forum: No registered users and 1 guest