Page 1 of 2

Jumping on Exploboxes

Posted: Fri Aug 14, 2009 12:18 am
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.

Posted: Fri Aug 14, 2009 1:38 am
by Team Xlink
Nice Tutorial!

Finally, I can take the fun route!

Thank you!

Posted: Fri Aug 14, 2009 4:09 pm
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.

Posted: Fri Aug 14, 2009 7:06 pm
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)?

Posted: Sat Aug 15, 2009 2:52 pm
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

Re: Jumping on Exploboxes

Posted: Sun Aug 16, 2009 8:59 pm
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...

Re: Jumping on Exploboxes

Posted: Sun Aug 16, 2009 10:24 pm
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).

Posted: Thu Aug 20, 2009 9:01 pm
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:

Posted: Fri Aug 21, 2009 5:40 pm
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 ;) )

Posted: Fri Aug 21, 2009 6:22 pm
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.

Posted: Sat Aug 22, 2009 2:59 am
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]

Posted: Sat Dec 05, 2009 9:05 pm
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.

Posted: Fri Nov 19, 2010 4:21 pm
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.

Posted: Fri Nov 19, 2010 7:30 pm
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.

Posted: Sat Jun 11, 2011 3:56 am
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.