Jumping on Exploboxes

Post tutorials on how to do certain tasks within game or engine code here.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Jumping on Exploboxes

Post by mh »

EDIT - put begin and end comments around the relevant code changes so that you can more easily see the bits that you need to add to your engine if you don't want to just copy/paste whole functions

This came to light after everyone started playing the Bloody Slipgates mod. There is a place where one of the red keys is hidden where there are 3 routes to it. Route 1 involves a fairly fiendish ninja jump (captured for posterity in my demo), route 2 involves a clip brush in a place where it doesn't seem obvious, and route 3 involves jumping off a small explobox. Now, most if not all engines aside from DP don't support jumping off exploboxes, so route 2 was added at the last minute in order to provide a way (I think the mod author may not have realised that route 1 was possible here).

Here I'm going to show you how to implement the ability to jump off exploboxes in Quake. Before I start though there's a big disclaimer to make.

BIG DISCLAIMER

This is a gameplay changing tutorial. It might make some areas in some maps that were previously inaccessible easier to get at, so I consider it a "cheating" change. It's all server-side though so it won't affect MP games, but just be aware that if you implement it you may spoil your enjoyment of some maps or mods.

I've only tested it on Bloody Slipgates, and only in the relevant area. I've also ensured that it doesn't interfere with other regular brush entities.

Some mods may do things with exploboxes that break as a result of this code. You have been warned.


Now, open sv_phys.c and look for the SV_Physics function. Replace it with this:

Code: Select all

void SV_Physics (void)
{
	int		i;
	edict_t	*ent;

// let the progs know that a new frame has started
	pr_global_struct->self = EDICT_TO_PROG(sv.edicts);
	pr_global_struct->other = EDICT_TO_PROG(sv.edicts);
	pr_global_struct->time = sv.time;
	PR_ExecuteProgram (pr_global_struct->StartFrame);

//SV_CheckAllEnts ();

//
// treat each object in turn
//
	ent = sv.edicts;
	for (i=0 ; i<sv.num_edicts ; i++, ent = NEXT_EDICT(ent))
	{
		// jumping on exploboxes begin
		qboolean modelhack = false;
		// jumping on exploboxes end

		if (ent->free)
			continue;

		// jumping on exploboxes begin
		// don't do the world edict
		if (ent->v.touch == 0 && i > 0)
		{
			model_t *mod = sv.models[(int) ent->v.modelindex];

			if (mod)
			{
				if (mod->type == mod_brush && mod->name[0] != '*')
				{
					// we have a brushmodel that (1) doesn't have a touch function, and (2)
					// is an instanced BSP model, so we switch the solid and movetype
					ent->v.solid = SOLID_BSP;
					ent->v.movetype = MOVETYPE_PUSH;

					// flag that we're hacking the model
					modelhack = true;
				}
			}
		}
		// jumping on exploboxes end

		if (pr_global_struct->force_retouch)
		{
			SV_LinkEdict (ent, true);	// force retouch even for stationary
		}

		if (i > 0 && i <= svs.maxclients)
			SV_Physics_Client (ent, i);
		else if (ent->v.movetype == MOVETYPE_PUSH)
			// jumping on exploboxes begin
			SV_Physics_Pusher (ent, modelhack);
			// jumping on exploboxes end
		else if (ent->v.movetype == MOVETYPE_NONE)
			SV_Physics_None (ent);
#ifdef QUAKE2
		else if (ent->v.movetype == MOVETYPE_FOLLOW)
			SV_Physics_Follow (ent);
#endif
		else if (ent->v.movetype == MOVETYPE_NOCLIP)
			SV_Physics_Noclip (ent);
		else if (ent->v.movetype == MOVETYPE_STEP)
			SV_Physics_Step (ent);
		else if (ent->v.movetype == MOVETYPE_TOSS 
		|| ent->v.movetype == MOVETYPE_BOUNCE
#ifdef QUAKE2
		|| ent->v.movetype == MOVETYPE_BOUNCEMISSILE
#endif
		|| ent->v.movetype == MOVETYPE_FLY
		|| ent->v.movetype == MOVETYPE_FLYMISSILE)
			SV_Physics_Toss (ent);
		else
			Sys_Error ("SV_Physics: bad movetype %i", (int)ent->v.movetype);			
	}
	
	if (pr_global_struct->force_retouch)
		pr_global_struct->force_retouch--;	

	sv.time += host_frametime;
}
The cause of not being able to jump off an explobox is because of the movetype value that they have (MOVETYPE_NONE) so here we detect if we have an explobox and switch the movetype. We also need to switch the solid value because MOVETYPE_PUSH requires SOLID_BSP.

The code to detect if it's an explobox can work with any instanced BSP model. First it detects if the model has a touch function; if it does we assume that it''s meant to be interacted with in some way, and so we don't switch it. Then we ensure that we've got an instanced BSP model. Note also the change to the call to SV_Physics_Pusher in here. I'll talk about that one after the next block of code.

Next, we replace SV_Physics_Pusher itself with this:

Code: Select all

void SV_Physics_Pusher (edict_t *ent, qboolean modelhack)
{
	float	thinktime;
	float	oldltime;
	float	movetime;

	oldltime = ent->v.ltime;
	
	thinktime = ent->v.nextthink;
	if (thinktime < ent->v.ltime + host_frametime)
	{
		movetime = thinktime - ent->v.ltime;
		if (movetime < 0)
			movetime = 0;
	}
	else
		movetime = host_frametime;

	if (movetime)
	{
#ifdef QUAKE2
		if (ent->v.avelocity[0] || ent->v.avelocity[1] || ent->v.avelocity[2])
			SV_PushRotate (ent, movetime);
		else
#endif
			SV_PushMove (ent, movetime);	// advances ent->v.ltime if not blocked
	}

	// jumping on exploboxes begin
	if (modelhack)
	{
		// run regular thinking
		SV_RunThink (ent);
	}
	else if (thinktime > oldltime && thinktime <= ent->v.ltime)
	// jumping on exploboxes end
	{
		ent->v.nextthink = 0;
		pr_global_struct->time = sv.time;
		pr_global_struct->self = EDICT_TO_PROG(ent);
		pr_global_struct->other = EDICT_TO_PROG(sv.edicts);
		PR_ExecuteProgram (ent->v.think);
		if (ent->free)
			return;
	}

}
All that we're doing here is detecting if the model was hacked above and sending it through the think function for a MOVETYPE_NONE model if so.

This code as it stands will potentially give you a "SOLID_BSP with a non-BSP model" error so the last thing to do is fix that. I'm going to do a quick and dirty fix rather than something more robust, so open world.c and replace SV_HullForEntity with this:

Code: Select all

hull_t *SV_HullForEntity (edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset)
{
	model_t		*model;
	vec3_t		size;
	vec3_t		hullmins, hullmaxs;
	hull_t		*hull;

// decide which clipping hull to use, based on the size
	if (ent->v.solid == SOLID_BSP)
	{	// explicit hulls in the BSP model
		if (ent->v.movetype != MOVETYPE_PUSH)
			Sys_Error ("SOLID_BSP without MOVETYPE_PUSH");

		model = sv.models[ (int)ent->v.modelindex ];

		// jumping on exploboxes begin
		if (!model || model->type != mod_brush)
			goto dont_crash;
		// jumping on exploboxes end

		VectorSubtract (maxs, mins, size);
		if (size[0] < 3)
			hull = &model->hulls[0];
		else if (size[0] <= 32)
			hull = &model->hulls[1];
		else
			hull = &model->hulls[2];

// calculate an offset value to center the origin
		VectorSubtract (hull->clip_mins, mins, offset);
		VectorAdd (offset, ent->v.origin, offset);
	}
	else
	{	// create a temp hull from bounding box sizes
dont_crash:
		VectorSubtract (ent->v.mins, maxs, hullmins);
		VectorSubtract (ent->v.maxs, mins, hullmaxs);
		hull = SV_HullForBox (hullmins, hullmaxs);
		
		VectorCopy (ent->v.origin, offset);
	}


	return hull;
}
This happens because the entities model changes from a brush to a sprite when it explodes, so all we do here is jump to the non-SOLID_BSP hull code instead of throwing a Sys_Error.

Now, this code is fairly hacky and there are better ways of doing the whole thing; one possible solution would be to store the original values of solid and movetype in the edict_t struct and switch between them and the new ones as required. I did it this way so that I could present the tutorial in a manner that's easier to follow, but I wouldn't claim is the right way, or even a good way. But if you want to jump off exploboxes it'll let you do it. Any improvements are entirely up to you.
Last edited by mh on Sat Aug 15, 2009 2:56 pm, edited 1 time in total.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

Nice Tutorial!

Finally, I can take the fun route!

Thank you!
Downsider
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Post by Downsider »

Good job coming up with the fix, but I hate seeing tutorials that just say, "Replace this function with this function because I said so".

At the very least, you could compare the two functions to show the differences, so that way you could work out what's actually going on. Some people are working with different code bases that have changed certain aspects of the engine and can't simply replace the entire function. Others also want to understand what's going on, too, especially since a lot of these additions are very hacky and aren't understood immediately unless your knowledge of Quake is rather deep.
c0burn
Posts: 208
Joined: Fri Nov 05, 2004 12:48 pm
Location: Liverpool, England
Contact:

Post by c0burn »

Can this be fixed in QC the same way the URQP fixes sliding when you jump on a monster? (touch function that fiddles with FL_ONGROUND)?
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Downsider wrote:Good job coming up with the fix, but I hate seeing tutorials that just say, "Replace this function with this function because I said so".

At the very least, you could compare the two functions to show the differences, so that way you could work out what's actually going on. Some people are working with different code bases that have changed certain aspects of the engine and can't simply replace the entire function. Others also want to understand what's going on, too, especially since a lot of these additions are very hacky and aren't understood immediately unless your knowledge of Quake is rather deep.
I have commented the relevant code areas as well as provided some explanation text, but I can amend the tutorial and put // begin and // end comments around the modified parts. In the specific case of this one, the physics code is something that I think not too many people modify, but I suppose you're right all the same.
c0burn wrote:Can this be fixed in QC the same way the URQP fixes sliding when you jump on a monster? (touch function that fiddles with FL_ONGROUND)?
Should be possible, yes. I'm not too familiar with QC but it's all fields that are exported through ent->v so they should also be accessible from QC. In fact I would be of the opinion that QC is the correct place to do it. :D
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Lardarse
Posts: 266
Joined: Sat Nov 05, 2005 1:58 pm
Location: Bristol, UK

Re: Jumping on Exploboxes

Post by Lardarse »

mh wrote:This came to light after everyone started playing the Bloody Slipgates mod. There is a place where one of the red keys is hidden where there are 3 routes to it. Route 1 involves a fairly fiendish ninja jump (captured for posterity in my demo), route 2 involves a clip brush in a place where it doesn't seem obvious, and route 3 involves jumping off a small explobox. Now, most if not all engines aside from DP don't support jumping off exploboxes, so route 2 was added at the last minute in order to provide a way (I think the mod author may not have realised that route 1 was possible here).
I couldn't do it. negke tried to do it in his first run demo and couldn't do it either...

There is actually a 4th method: Look at the box, jump, shoot the box.

I would like a QC solution, though...
Roaming status: Testing and documentation
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Jumping on Exploboxes

Post by mh »

Lardarse wrote:I couldn't do it. negke tried to do it in his first run demo and couldn't do it either...
Well if I could do it, it can't be that hard. :lol:
Lardarse wrote:I would like a QC solution, though...
Obviously the ideal way to go; one of our resident QC hackers would have to help you with that, however. I'm still highly dubious about ever having posted this thing; it's just Wrong and Bad in so many ways (cheating, edict behaviour modification, physics hacking, etc).
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Downsider
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Post by Downsider »

Kurok had a fix for this from version 0.3 -> 0.4, if I remember correctly. Ask MDave, although for all I know he could have changed the exploboxes into brushes or something (Is this possible?). Or maybe I'm totally off my hat and it never was fixed, or it's a different problem entirely! :lol:
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

c0burn wrote:Can this be fixed in QC the same way the URQP fixes sliding when you jump on a monster? (touch function that fiddles with FL_ONGROUND)?
Maybe if touching an explosion box, switch it's movetype to MOVETYPE_STEP, and keeping track of that entity, so if not touching it, switch it back to MOVETYPE_NONE... i'm still looking for an explosion box in normal Quake maps that i can stand on.. e3m1? I think as c0burn mentioned in player_touch setting the box ONGROUND would keep u from slip-sliding around while on it, fiddling with the movetype should allow you to jump off it... I'll have to play with this as it might breakif u blow it up while standing on it :|

hmm weird. I havent looked in misc.qc in AT LEAST 6 years...
here's what i found in my CAx mod...

Code: Select all

void() misc_explobox2 =
{
	remove(self);
	return;

	local float	oldz;
	
	self.solid = SOLID_BBOX;
	//self.movetype = MOVETYPE_NONE;
	self.movetype = MOVETYPE_PUSH;
	precache_model2 ("maps/b_exbox2.bsp");
	setmodel (self, "maps/b_exbox2.bsp");
	precache_sound ("weapons/r_exp3.wav");
	self.health = 20;
	self.th_die = barrel_explode;
	self.takedamage = DAMAGE_AIM;

	self.origin_z = self.origin_z + 2;
	oldz = self.origin_z;
	droptofloor(0,0);
	if (oldz - self.origin_z > 250)
	{
		dprint ("item fell out of level at ");
		dprint (vtos(self.origin));
		dprint ("\n");
		remove(self);
	}
};
I dont remember switching that movetype before nor do i know the result as in CA i clear this entity out. When I get home I'll have to test this as I cant compile under this OS (i have a 16gb thumbdrive with my quake stuff that i take along with me ;) )
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

You'll need to switch to SOLID_BSP as well, but I do believe that is all that's required. Just hacked it into a vanilla ID QC and it worked fine.

There's a box you can test it on in e4m1 by the way, in the room with the lasers.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Wazat
Posts: 771
Joined: Fri Oct 15, 2004 9:50 pm
Location: Middle 'o the desert, USA

Post by Wazat »

Assuming my memories weren't planted by conspiring hamsters (haaamsteeers!), I'm reasonably certain I had walking & jumping off of non-BSP objects working perfectly in one of my mods. Thus I was able to run around on monsters' heads and jump off.

Again, if memory serves me, it was a simple feat of tracelining down in playerprethink, testing if the impacted non-bsp object was worthy of your footsteps (most things are: monsters, explode boxes, maybe even triggers), and setting the player's FL_ONGROUND flag. Once the flag was set, the player could walk as though he was on a SOLID_BSP object.

But I still suspect conspiring hamsters planting false memories to keep me under control.


It can be fun abusing the power of QC like this to get great results. I'm still proud of the semi-solid-corpses-without-an-engine-mod hack I put in Ace of Nails. And Conquest's pause-the-world-without-pausing feature.[/shamelessbrag]
When my computer inevitably explodes and kills me, my cat inherits everything I own. He may be the only one capable of continuing my work.
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

mh wrote:
Next, we replace SV_Physics_Pusher itself with this:

Code: Select all

void SV_Physics_Pusher (edict_t *ent, qboolean modelhack)
{
	float	thinktime;
	float	oldltime;
	float	movetime;
}
A friend of mine was having trouble with that and I told him about this so I thought maybe other people were having trouble.

The part where you add the qboolean modelhack wasn't commented and he didn't notice it so maybe a comment there might help others as well.

EDIT: I am confused. How exactly is it supposed to work?

I tried taking that way but it didn't change anything at all.

I think I ama misunderstanding how to use it.
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

Thread necromancy, but:

Can anyone imagine a good reason not to do this (disable the sliding on monsters/exploboxes), ie a specific case where this would break existing content?

DP simply does it and I can't remember anyone complaining.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

The entity movetype needs to be changed to MOVETYPE_PUSH, irrespective of what it was before. This will break because it may not go through the proper physics, so it's really only appropriate for entities that don't get any physics in the first place.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Post by taniwha »

I like the concept of this patch, but the implementation seemed rather hacky: in my books, any tweak like this that ripples through the code requiring other tweaks is automatically suspect.

Thus, I took a really good look at the code. Whether or not the player can jump is controlled by FL_ONGROUND, so I looked at the code that sets FL_ONGROUND. The only relevant condition was the "ground" entity's solid flag being SOLID_BSP. I suspect mh's comments about MOVETYPE_NONE being the problem and SOLID_BSP being required were just a matter of getting things mixed up after investigation and hacking (quite reasonable: I do the same). Only two places that set FL_ONGROUND are conditional on the entity's solid type.

So... what I did was I disabled the SOLID_BSP checks in SV_WalkMove and SV_FlyMove: jumping on explode boxes worked perfectly.

Maybe rather than "if (ent is SOLID_BSP)", an if (ent can support jump)" test would be better (using a function for the shared code). This would allow for cvar control and sophisticated entity checks.
Leave others their otherness.
http://quakeforge.net/
Post Reply