Jump to content
The Dark Mod Forums

Creating a Robust Tram Script


Epifire

Recommended Posts

Well I'd talked a tad bit about starting a tram with my splines thread but I thought I'd post something a little more direct now. For you script junkies I hope this can be a fun little project to collaborate on! So here it goes. The tram I'm making for my mission will be a self powered mode of transportation that rides along a railway. It should basically be a small box car of sorts with a view panel that stretches around all sides. It should have a single door that is automatically closed while the tram is in motion. It should have three buttons for controlling it. Forward, backward and a brake. The directional levers should only be able to change direction when the brake has been applied (so no changing directions while in motion!).

 

The tram brake should probably be it's own script function so that it can be called by other modes of command (from end of path splines or triggered events). It also should have a single state that (when enabled) should make the controls inoperable. This function is only needed so that the tram may carry out it's last defined spline path without player interference. Lastly, I wanted to have a true/false active state available. This is just so that the tram can remain inactive until the story/objectives define otherwise.

 

I will be modeling a entirely new detail mesh from scratch, and I'm hoping that I can eventually get a pretty compact def file established for it. The end goal of mine is to have a nice tram that practically anyone can pick up and use after it ships in my FM.

 

Also for your convenient referencing I already grabbed the original script for the D3 tram and tried to gut most the features I was sure that we wouldn't be needing for this. Such as vertical and lateral movement functions. Hoping we can reuse some functions from it but I'm a script novice so I need some parental advice. :P

 

 

 

namespace map_commoutside {

	#define CMD_LIFT_NONE		0
	#define CMD_LIFT_UP			1
	#define CMD_LIFT_DOWN		2
	#define CMD_LIFT_FORWARD	3
	#define CMD_LIFT_RIGHT		4
	#define CMD_LIFT_LEFT		5
	#define CMD_LIFT_BACK		6
	#define CMD_LIFT_CALL1		7
	#define CMD_LIFT_CALL2		8
	#define CMD_LIFT_CALL3		9

	#define	LIFT_MOVE_SPEED		160
	#define	LIFT_TURN_TIME		2
	#define LIFT_UPDOWN_TIME	2

	// states for the lift
	float	lift_command;
	boolean	lift_plat_up;
	float	lift_dir;
	entity	lift_station;

	void	setup_lift();
	void	reset_guis();
	void	set_lift_location( entity position );
	float 	calc_lift_path_r( entity current_pos, entity previous, entity target, float depth );
	float 	get_lift_turn_dir( entity source, entity target );
	entity	check_lift_move_dir( entity current_pos, float dir );
	void	turn_lift_to_dir( float dir );
	void	move_lift_to( entity target );

	void 	cmd_lift_up();
	void 	cmd_lift_down();
	void 	cmd_lift_forward();
	void 	cmd_lift_right();
	void 	cmd_lift_left();
	void 	cmd_lift_back();
	void 	cmd_lift_call_1();
	void 	cmd_lift_call_2();
	void 	cmd_lift_call_3();
	void	lift_loop();

	void 	lift_up();
	void 	lift_down();
	void 	lift_forward();
	void 	lift_right();
	void 	lift_left();
	void 	lift_back();
	void 	lift_call_1();
	void 	lift_call_2();
	void 	lift_call_3();

	void 	callspot1_panel_up();
	void 	callspot1_panel_down();
	void 	callspot2_panel_up();
	void 	callspot2_panel_down();
	void 	callspot3_panel_up();
	void 	callspot3_panel_down();

	void 	lift_main();

	////////////////////////////////////////////////////
	//
	//	Setup binds and times etc...
	//
	////////////////////////////////////////////////////
	void setup_lift() {
		// initial variable assignments
		lift_command = CMD_LIFT_NONE;
		lift_plat_up = true;

		lift_station = $lift_bottom.getTarget( 0 );
		if ( !lift_station ) {
			sys.warning( "Entity 'lift_bottom' doesn't have a target." );
		}
		set_lift_location( lift_station );

		lift_dir = 2;

		// Lift binds, times, and speeds
		$lift_leg_11.bind($lift_bottom);
		$lift_light1.bind($lift_bottom);
		
		$lift_axle1.bind($lift_leg_11);
		$lift_axle2.bind($lift_leg_11);
		$lift_leg_12.bind($lift_axle2);
		$lift_axle4.bind($lift_leg_12);
		$lift_axle6.bind($lift_leg_12);
		$lift_leg_13.bind($lift_axle6);
		$lift_axle7.bind($lift_leg_13);

		$lift_leg_23.bind($lift_axle7);
		$lift_axle5.bind($lift_leg_23);
		$lift_leg_22.bind($lift_axle5);
		$lift_axle3.bind($lift_leg_22);
		$lift_leg_21.bind($lift_axle3);

		$lift_connection_top_move.bind($lift_leg_13);
		$lift_connection_top_static.bind($lift_leg_23);
		$lift_connection_bottom_move.bind($lift_leg_21);

		$lift_cage.bind($lift_connection_top_static);

		$lift_leg_11.time( LIFT_UPDOWN_TIME );
		$lift_leg_12.time( LIFT_UPDOWN_TIME );
		$lift_leg_13.time( LIFT_UPDOWN_TIME );
		$lift_connection_top_move.time( LIFT_UPDOWN_TIME );
		$lift_leg_21.time( LIFT_UPDOWN_TIME );
		$lift_connection_bottom_move.time( LIFT_UPDOWN_TIME );
		$lift_leg_22.time( LIFT_UPDOWN_TIME );
		$lift_leg_23.time( LIFT_UPDOWN_TIME );
		$lift_connection_top_static.time( LIFT_UPDOWN_TIME );

		$lift_leg_11.accelTime( .25 );
		$lift_leg_12.accelTime( .25 );
		$lift_leg_13.accelTime( .25 );
		$lift_connection_top_move.accelTime( .25 );
		$lift_leg_21.accelTime( .25 );
		$lift_connection_bottom_move.accelTime( .25);
		$lift_leg_22.accelTime( .25 );
		$lift_leg_23.accelTime( .25 );
		$lift_connection_top_static.accelTime( .25 );

		$lift_leg_11.decelTime( .25 );
		$lift_leg_12.decelTime( .25 );
		$lift_leg_13.decelTime( .25 );
		$lift_connection_top_move.decelTime( .25 );
		$lift_leg_21.decelTime( .25 );
		$lift_connection_bottom_move.decelTime( .25 );
		$lift_leg_22.decelTime( .25 );
		$lift_leg_23.decelTime( .25 );
		$lift_connection_top_static.decelTime( .25 );

		// Call spots panel stuff
		$callspot1_conpan_gui.bind($callspot1_conpan);
		$callspot1_conpan.time(.25);
		$callspot1_conpan.rotateOnce( '63.43 0 0' );
		$callspot1_conpan_trigger_down.disable();

		$callspot2_conpan_gui.bind($callspot2_conpan);
		$callspot2_conpan.time(.25);
		$callspot2_conpan.rotateOnce( '0 0 -63.43' );
		$callspot2_conpan_trigger_down.disable();

		$callspot3_conpan_gui.bind($callspot3_conpan);
		$callspot3_conpan.time(.25);
		$callspot3_conpan.rotateOnce( '0 0 -63.43' );
		$callspot3_conpan_trigger_down.disable();

		$lift_light1_mover.rotateOnce( '90 0 0' );
	}

	////////////////////////////////////////////////////
	//
	//	Do a depth-first search of the "track" to find a path to the target entity.
	//  each path entity targets 1 or more other path entities.  Each path entity it
	//  targets must also target it, so the links are forward and back.  There must be
	//  no circular links and the longest possible path must never be more than the
	//  script stack can handle (around 62 path entities would be the limit, but possibly
	//  fewer).  This could be modified to handle circular links, and be made non-recursive
	//  so it's not dependent on the stack size, and possibly with a bit of effort could find
	//  the shortest path if multiple paths exist, but for our purposes, this does the job.
	//
	////////////////////////////////////////////////////
	float calc_lift_path_r( entity current_pos, entity previous, entity target, float depth ) {
		float num;
		float i;
		entity next;
		float  path_length;

		// have we reached the target?
		if ( current_pos == target ) {
            return depth;
		}

		// check each of the targets on this entity
		num = current_pos.numTargets();
		for( i = 0; i < num; i++ ) {
			next = current_pos.getTarget( i );
			if ( next == previous ) {
				// don't go back the way we came
				continue;
			}

			// check if a path exists through this target
			path_length = calc_lift_path_r( next, current_pos, target, depth + 1 );
			if ( path_length ) {
				// found the target, so as we recurse backwards, set the name of the next path entity to go to.
				// when we're done, going through the "path*" keys in order will give the order of entities
				// to follow to get to the target.
				$lift_bottom.setKey( "path" + depth, next.getName() );
				return path_length;
			}
		}

		// no path from this entity
		return 0;
	}

	////////////////////////////////////////////////////
	//
	//	movement test
	//
	////////////////////////////////////////////////////
	entity check_lift_move_dir( entity current_pos, float dir ) {
		float	num;
		float	i;
		float	check_dir;
		entity	check_ent;

		num = current_pos.numTargets();
		for( i = 0; i < num; i++ ) {
			check_ent = current_pos.getTarget( i );
			check_dir = get_lift_turn_dir( current_pos, check_ent );
			if ( check_dir == dir ) {
				return check_ent;
			}
		}
		return current_pos;
	}

	////////////////////////////////////////////////////
	//
	//	move lift to a specific spot
	//
	////////////////////////////////////////////////////
	void move_lift_to( entity target ) {
		float 	path_length;
		float 	i;
		float 	dir;
		float 	next_dir;
		string 	ent_name;
		entity 	path_ent;
		entity 	next_ent;

		if ( lift_station == target ) {
			// lower the platform if necessary
			if ( lift_plat_up ) {
				cmd_lift_down();
			}
			return;
		}

		// create a path to the target
		path_length = calc_lift_path_r( lift_station, lift_station, target, 0 );
		if ( !path_length ) {
			string currentname = lift_station.getName();
			string targetname = target.getName();
			sys.warning( "entity 'lift_bottom' can't find path from '" + currentname + "' to '" + targetname + "'" );
			return;
		}

		// lower the platform if necessary
		if ( lift_plat_up ) {
			cmd_lift_down();
		}

		// get the first point on the path
		ent_name = $lift_bottom.getKey( "path0" );
		next_ent = sys.getEntity( ent_name );
		if ( !next_ent ) {
			sys.warning( "entity 'lift_bottom' can't find path entity '" + ent_name + "' to get to '" + target.getName() + "'" );
			return;
		}
		dir = get_lift_turn_dir( lift_station, next_ent );

		// move to each point on the path
		for( i = 0; i < path_length; i++ ) {
			path_ent = next_ent;

			// get the next path entity, unless we're at the last one
			if ( i < path_length - 1 ) {
				ent_name = $lift_bottom.getKey( "path" + ( i + 1 ) );
				next_ent = sys.getEntity( ent_name );
				if ( !next_ent ) {
					sys.warning( "entity 'lift_bottom' can't find path entity '" + ent_name + "' to get to '" + target.getName() + "'" );
					return;
				}
			}

			// turn towards the new direction if it's different and accelerate
			if ( dir != lift_dir ) {
				turn_lift_to_dir( dir );
				$lift_bottom.accelTime( .5 );
			} else if ( i != 0 ) {
				$lift_bottom.accelTime( 0 );
			} else {
				// always accelerate on the first path entity
				$lift_bottom.accelTime( .5 );
			}

			// decelerate on the last path entity or if we have to turn
			if ( i < path_length - 1 ) {
				dir = get_lift_turn_dir( path_ent, next_ent );
				if ( dir != lift_dir ) {
					$lift_bottom.decelTime( .5 );
				} else {
					$lift_bottom.decelTime( 0 );
				}
			} else {
				$lift_bottom.decelTime( .5 );
			}
			
			// move to the next path entity
			sys.onSignal( SIG_BLOCKED, $lift_bottom, "map_commoutside::lift_move_blocked" );
			$lift_bottom.speed( LIFT_MOVE_SPEED );
		
			$lift_bottom.moveTo( path_ent );
			sys.waitFor( $lift_bottom );
			sys.clearSignalThread( SIG_BLOCKED, $lift_bottom );

			// update our location on the guis
			set_lift_location( path_ent );
		}
	}

	////////////////////////////////////////////////////
	//
	//	move lift forward
	//
	////////////////////////////////////////////////////
	void cmd_lift_forward() {
		entity path_ent;

		path_ent = check_lift_move_dir( lift_station, lift_dir );
		if ( path_ent == lift_station ) {
			return;
		}

		if ( lift_plat_up ) {
			cmd_lift_down();
		}

		$lift_cage_control.disable();

		sys.onSignal( SIG_BLOCKED, $lift_bottom, "map_commoutside::lift_move_blocked" );
		$lift_bottom.accelTime( .5 );
		$lift_bottom.decelTime( .5 );
		$lift_bottom.speed( LIFT_MOVE_SPEED );
		$lift_bottom.moveTo( path_ent );
		sys.waitFor( $lift_bottom );
		sys.clearSignalThread( SIG_BLOCKED, $lift_bottom );
		set_lift_location( path_ent );
		$lift_cage_control.enable();
	}

	////////////////////////////////////////////////////
	//
	//	move lift back
	//
	////////////////////////////////////////////////////
	void cmd_lift_back() {
		entity path_ent;

		path_ent = check_lift_move_dir( lift_station, ( lift_dir + 2 ) & 3 );
		if ( path_ent == lift_station ) {
			return;
		}

		if ( lift_plat_up ) {
			cmd_lift_down();
		}

		$lift_cage_control.disable();

		sys.onSignal( SIG_BLOCKED, $lift_bottom, "map_commoutside::lift_move_blocked" );
		$lift_bottom.accelTime( .5 );
		$lift_bottom.decelTime( .5 );
		$lift_bottom.speed( LIFT_MOVE_SPEED );
		$lift_bottom.moveTo( path_ent );
		sys.waitFor( $lift_bottom );
		set_lift_location( path_ent );
		$lift_cage_control.enable();
	}

	////////////////////////////////////////////////////
	//
	//	call lift to call spot #1
	//	From anywhere in the grid the lift will find
	//	it's way to call spot #1
	//
	////////////////////////////////////////////////////
	void cmd_lift_call_1() {
		move_lift_to( $lift_spot_13 );
		turn_lift_to_dir( 0 );
	}

	////////////////////////////////////////////////////
	//
	//	call lift to call spot #2
	//	From anywhere in the grid the lift will find
	//	it's way to call spot #2
	//
	////////////////////////////////////////////////////
	void cmd_lift_call_2() {
		move_lift_to( $lift_spot_31 );
		turn_lift_to_dir( 1 );
	}

	////////////////////////////////////////////////////
	//
	//	call lift to call spot #3
	//	From anywhere in the grid the lift will find
	//	it's way to call spot #3
	//
	////////////////////////////////////////////////////
	void cmd_lift_call_3() {
		move_lift_to( $lift_spot_83 );
		turn_lift_to_dir( 1 );
	}

	////////////////////////////////////////////////////
	//
	//	command processing loop
	//
	////////////////////////////////////////////////////
	void lift_loop() {
		while( 1 ) {
			lift_command = CMD_LIFT_NONE;
			while( lift_command == CMD_LIFT_NONE ) {
				sys.waitFrame();
			}

			if ( lift_command == CMD_LIFT_UP ) {
				cmd_lift_up();
			} else if ( lift_command == CMD_LIFT_DOWN ) {
				cmd_lift_down();
			} else if ( lift_command == CMD_LIFT_FORWARD ) {
				cmd_lift_forward();
			} else if ( lift_command == CMD_LIFT_RIGHT ) {
				cmd_lift_right();
			} else if ( lift_command == CMD_LIFT_LEFT ) {
				cmd_lift_left();
			} else if ( lift_command == CMD_LIFT_BACK ) {
				cmd_lift_back();
			} else if ( lift_command == CMD_LIFT_CALL1 ) {
				cmd_lift_call_1();
			} else if ( lift_command == CMD_LIFT_CALL2 ) {
				cmd_lift_call_2();
			} else if ( lift_command == CMD_LIFT_CALL3 ) {
				cmd_lift_call_3();
			}
		}
	}

	////////////////////////////////////////////////////
	//
	//	MAIN
	//
	////////////////////////////////////////////////////
	void lift_main() {
		
		setup_lift();

		// turn lift to the left so it can illuminate the 2nd call station area
		cmd_lift_left();
		lift_loop();
	}
} // namespace

 

 

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

The lifts in D3 moved in straight lines. As far as I remember you want your tram to move in curves. So up-down and left-right will also include rotation.

 

Personally I wouldn't use splines for such an undertaking bould would rather rely on a fixed set of tracks which hold the information needed for correct movement. This would allow to use a script to provide the correct setup, instead of the mapper having to setup all the links, targets etc. which would be quite messy with bigger rail systems and not very error prone. In addition a tram has a considerable length, so the center of the tram will not be following the center of the rails when driving through curves for example.

 

Regarding the tram it would be you should think about whether the player can see the tram moving from the outside or not. If not, the wheels don't need to be seperate models. However, in terms of reusability it might make sense to make them seperate models anyway. What is important though is what kind of axe system you are going to use. I'm assuming you would go for a two axle vehicle, so one axle at the front and one at the back?! In that case you should make sure that the origin of the tram model is exactly in the center of both axles, preferable at the same height where the wheels touch the rails. This would easen the math for the rotation. If you are aiming for a tram that has two axles both at the front and the back, things will get a bit more messy.

 

Just my two cents, though.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to comment
Share on other sites

Splines have to be used because the regular mover paths don't take pitch into account. For angles and such it requires that it uses a spline, otherwise I'm stuck working on a level plane.

 

To be fair most the areas setup for the tram are just single paths without any optional turns or routes. The big thing I want to make sure is bulletproof is that it can stop and continue or change directions with ease. Right now I don't seem to have an effective means of stopping without it starting the spline path over again.

 

Unlike the door, the wheel movers aren't essential to start with but I would like to add them later when the core components are in. I'm really just wondering how to get all of this setup.

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

In that regard I might add that for splines you should make sure that the radii of the curves should be sufficiently bigger then the length of the tram, so that the offset that is caused by them can be ignored. Otherwise you will have a hard time getting them layed out correctly.

 

 


Splines have to be used because the regular mover paths don't take pitch into account

I've never said anything about regular movers and am not sure what you mean by that. I've meant that the script posted will probably not work as it is intented for a completely different kind of situation.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to comment
Share on other sites

In that regard I might add that for splines you should make sure that the radii of the curves should be sufficiently bigger then the length of the tram, so that the offset that is caused by them can be ignored. Otherwise you will have a hard time getting them layed out correctly.

 

 

I've never said anything about regular movers and am not sure what you mean by that. I've meant that the script posted will probably not work as it is intented for a completely different kind of situation.

 

I meant just the regular method being defined by path nodes. For the spline itself I can imagine there will be a lot of map testing involved to get the desired positioning down. Just in my own side tests I realized just how sharp it will make turns. One thing was bothering me when I had made a static brush bucket to test with. When turning or moving at an angle, the player somehow could block the mover from the inside. I'm hoping that just had to do with using brushwork, so hopefully using a model should fix that.

 

So to start with, how would we go about setting up these three main buttons?

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

I was more thinking about a func_mover. An entity whichs movements are4 controlled via scripting.

 

The same applies to the buttons. You could for example use the state_change_callback or work with the triggers they cause (I prefer the latter). If you take a look at the numberwheel lock in Builder Roads you can see how the script works (although this version is a bit buggy).

 

 


For the spline itself I can imagine there will be a lot of map testing involved to get the desired positioning down.

That's why I suggested to use a different approach. IIRC in Doom3 they were never used for movers, but for camera movement only (ingame cutscenes). If the railworks become sufficiently large, it will be a hard thing to avoid errors.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to comment
Share on other sites

Sounds fun, wish I knew what a spline was in this context.

I have an eclectic YouTube channel making videos on a variety of games. Come and have look here:

https://www.youtube.com/c/NeonsStyleHD

 

Dark Mod Missions: Briarwood Manor - available here or in game

http://forums.thedarkmod.com/topic/18980-fan-mission-briarwood-manor-by-neonsstyle-first-mission-6082017-update-16/

 

 

Link to comment
Share on other sites

I was more thinking about a func_mover. An entity whichs movements are4 controlled via scripting.

 

The same applies to the buttons. You could for example use the state_change_callback or work with the triggers they cause (I prefer the latter). If you take a look at the numberwheel lock in Builder Roads you can see how the script works (although this version is a bit buggy).

 

Currently it's using func_mover_amodel with a state_change_callback on a button. The problem at current, is it doesn't pick up where it left off. It just restarts the spline path rather than resuming course.

 

Baring in mind that the tram doesn't deviate from a single spline path in any given situation, should it really be that difficult to implement? Maybe I'm assuming the script can do more than I'm seeing, or that it simply can't handle complex commands for splines. I was under the impression you could define start and end point entities similar to conventional multistate movers. The only difference being how it converges between those two points being a spline rather than a straight line.

 

 

Sounds fun, wish I knew what a spline was in this context.

Proof of concept that a one way mover does indeed work.

 

https://gyazo.com/964a7b7b660b41c25a882a9ff3320f23

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

 


Baring in mind that the tram doesn't deviate from a single spline path in any given situation, should it really be that difficult to implement?

It is not neccessarely hard, but there are a few things to consider. What I don't get is why you insist on using splines?! They are not meant for that purpose, as said.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to comment
Share on other sites

It is not neccessarely hard, but there are a few things to consider. What I don't get is why you insist on using splines?! They are not meant for that purpose, as said.

 

Because as far as I know of it's the only pathing method that can incorporate pitch control. The track and rail areas needed to be traversed have a lot of vertical height differences. If the normal method could do that easily, I'd just as well chuck splines. From what everyone was telling me though, splines are the only way to accomplish that.

 

What would be the few things to consider?

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

What would be the few things to consider?

For example the ratio of the distance between the front and the rear axis and the minimum radius of your curves.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to comment
Share on other sites

For example the ratio of the distance between the front and the rear axis and the minimum radius of your curves.

 

As long as the rails aren't bizarre the curves should be able to be tweaked to match pretty easily. My only real concern was the unpredictable amount of roll axis tilt that occurs on hard turns along an incline. I had tried running $mover_object.disableSplineAngles(); but that seems to disable all axis of rotation. Pitch and yaw are the only two axis that the spline seems to handle in a reasonable fashion. However if there's not a lot of control options there, I can work around the problem of intense roll if I keep inclines and turns separated.

 

Right now though, I'm just trying to get some usable functions for the buttons fleshed out. That's the main running theme that the tram must have to function. After that I'd look at an automatic door and wheels, since those are more a last step. I was really wanting to smartly include the tram for accessing, what would otherwise be out of reach areas. So that's why the stop and start buttons are so important. The key to their functionality is that it can't change directions on a dime. Example: if moving forward the reverse direction lever would check to see if stopped before being able to move to the opposite end of the track. Maybe insert a metallic grinding sound affect on a false attempt.

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

Do we have (or can we implement) a three-state-lever? In this case you could have "forward" for example to the left. From this position, the only option is "stop"in the middle. From here, it goes on to "backwards" to the right. And the same in the other directon. Thus, you would not need three buttons and the direction check is automatically implemented. The only downside I could see is, that the lever has to cycle through all states, as it is not possible to determine in which direction the lever should go by a simple frob. So, if you want to go forward, stop and continue forward, you would have to go backwards first.

Another option for a forced stop could be frobability. I.e. when the tram moves only the "stop" button is frobable, when it stands still, the other two buttons are frobable. May be even simpler than the lever, now that I think about it...

Link to comment
Share on other sites

Do we have (or can we implement) a three-state-lever? In this case you could have "forward" for example to the left. From this position, the only option is "stop"in the middle. From here, it goes on to "backwards" to the right. And the same in the other directon. Thus, you would not need three buttons and the direction check is automatically implemented. The only downside I could see is, that the lever has to cycle through all states, as it is not possible to determine in which direction the lever should go by a simple frob. So, if you want to go forward, stop and continue forward, you would have to go backwards first.

Another option for a forced stop could be frobability. I.e. when the tram moves only the "stop" button is frobable, when it stands still, the other two buttons are frobable. May be even simpler than the lever, now that I think about it...

 

I hadn't thought of a three stage switch before. It looks like these all check an order, so couldn't the forward/backward modes toggle back to the brake if these values were adjusted? That essentially would be what we need. Visually it would make sense too because you couldn't put it in a different gear without tripping the brake first. For the brake I'm very curious as to how that could work. Everything I've heard so far points to the fact that it needs a solid defined point of origin to end a script on. Whereas I just want to be able to halt it on the fly.

 

 

This is the current little bit of script the spline mover type requires for one way direction right now.

void spline_mover()
{
	$mover_object.time(30); 		 //how long object take to move along spline
	$mover_object.accelTime(2); 		 //time mover takes to accelerate to full speed range 0.0 to not more than time to travel spline
	$mover_object.decelTime(0.5);		 //time mover takes to decelerate to a stop range as above
	//$mover_object.disableSplineAngles(); 	 //disables spline angles
	$mover_object.startSpline($spline_path); //starts mover moving along spline
	sys.waitFor($mover_object); 	         //wait for func_mover to finish moving along spline before doing anything else
}

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

I hadn't thought of a three stage switch before. It looks like these all check an order, so couldn't the forward/backward modes toggle back to the brake if these values were adjusted? That essentially would be what we need.

I think, this is what I meant. With a three stage switch, you would automatically switch from forward or backward to the stop position. The problem with the order I meant, was that you can not simply stop and continue in the same direction that way, because you can only frob the switch, but you can not give a direction in which the switch schould move. Unless, you want to introduce a GUI, when you activate the lever in the stop position, that asks, which direction you want to go...

 

Regarding the break, I alos have no idea how to implement it on a spline. If it is possible to read out the current direction, in which the mover moves, as a vector, it may be possible to define a new end point relative to the current position. In a simple way something like "Breakpoint = $Mover.getOrigin() + 10*directionVector" (this is not correct syntax; I am not very familiar with scripting) and then set the current destination to this Breakpoint.

  • Like 1
Link to comment
Share on other sites

Regarding the break, I alos have no idea how to implement it on a spline. If it is possible to read out the current direction, in which the mover moves, as a vector, it may be possible to define a new end point relative to the current position. In a simple way something like "Breakpoint = $Mover.getOrigin() + 10*directionVector" (this is not correct syntax; I am not very familiar with scripting) and then set the current destination to this Breakpoint.

 

Honestly the syntax is what I'm having the most difficulty with right now. I don't fully understand what commands are all available to utilize in a format the game will understand. Usually there's some sort of fallback archive I can find that documents available functions and brief explanations (much like the mtr documentation) but alas I can find none. If all else fails I may very well just have to settle for a single trip button loop. In this scenario we'd just have one throw switch in the control panel that would take you all the way to your destination. Upon arrival the button would toggle over to another script activation, so when triggered again you'd just hop on an identical path that's just the same path but reversed.

 

If there was some sort of .startSplineReverse command that'd make that whole bit easier. Just have it check for position and kick in the reversal script if the tram is at end of the spline. Again I don't even know if that function exists so I'm flying blind. At worst I'd just make the duplicate path running backwards, but I'm really trying to save that option as a last resort if there's no other option.

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

For the (few and very basic) things I have done regarding scripting, I have used a combination of the scripting tutorial on the Wiki, that unfortunately is not finished, but at least gives a basic understanding, of how scripting works, and a list of available script commands on the Wiki. The latter is vast and it is not easy to find what you need, but the search function of your browser helps in this regard.

  • Like 1
Link to comment
Share on other sites

Awwww yis! That list is exactly what I was needing. Already dug out a great find from that. Turns out you can disable just the roll factor in a spline mover if you just add .removeInitialSplineAngles (instead of disableSplineAngles). That's one annoying thing fixed. Not seeing a lot else relating to movers thus far. I think I'll try the basic two way setup first, and see what could be possible to add after that. There is a stop command relating directly to splines, but I have my doubts as to whether it will just restart the path if enabled again. I'll have to try and bind some buttons to my test mover and see if I can actually continue a path.

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

Alright so I'm just taking this one on the simple route, as I don't wanna cut it but I don't see any practical means of resuming (or reversing) a spline mover. I'm content with a two button call system but I wanna make it visually informative as well as fool proof.

 

Current problems are...

 

Track reversal: This I may have covered but not thoroughly tested for a fix. The problem lies in the fact that when switching to the identical spline (ran the other way) is that the mover will flip to face it. This is an inherit problem with how the spline angles are implemented. I think it can however be overcome by toggling in a identical tram model that has inverted forward axis. The result should be instantaneous without a noticeable hitch. Even if it is slightly more work.

 

Player Still Dies a Lot: So it's not exactly unheard of but on the same note as the last problem; when switching directions, the, "flip" tends to kill the player for some reason. This may be given that I've been testing with, "4posterbed.lwo" and you can't fully stand in it. Possibly just more headroom is needed when I make the actual mesh so that there is standing room.

 

Button Active States: One track is ran on a forward button and the other track is ran on the backward switch. These buttons need to have two conditions to work flawlessly. Firstly they can only trigger once (when idle at the spline start). They cannot be re-activated during transit other wise it will teleport to start as well as kill the player. I think if done right, either button will trigger once and will reset the other button when the the tram has come to a stop (possibly fired from sys.waitFor in said button script?). Done correctly it should be able to flipflop between the two modes whenever you reach the other platform.

 

Call Buttons: This should be extremely similar if not even using the same scripts derived for the aforementioned button functions. The only condition here is that the call button must not be able to call the tram until it has reached the opposite platform.

 

If I can iron out some example lines for the last two problems here, I think that'll be my biggest worry out of the way.

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

RE track reversal. Have you tried negative speed values?

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to comment
Share on other sites

Yeah it didn't seem to have any affect adding a negative spawnarg to the mover. It just created a crash if I tried to input them via script.

Modeler galore & co-authors literally everything

 

 

Link to comment
Share on other sites

Track reversal: This I may have covered but not thoroughly tested for a fix. The problem lies in the fact that when switching to the identical spline (ran the other way) is that the mover will flip to face it. This is an inherit problem with how the spline angles are implemented. I think it can however be overcome by toggling in a identical tram model that has inverted forward axis. The result should be instantaneous without a noticeable hitch. Even if it is slightly more work.

 

Player Still Dies a Lot: So it's not exactly unheard of but on the same note as the last problem; when switching directions, the, "flip" tends to kill the player for some reason. This may be given that I've been testing with, "4posterbed.lwo" and you can't fully stand in it. Possibly just more headroom is needed when I make the actual mesh so that there is standing room.

I think this is strange, if you don't move a spline anymore. When you take a look at elevators, they do not flip, when the movement is reversed. So, if you just use a simple func_mover (as I think you do, regarding your first comment) there should be no switch, unless I am missing something here.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recent Status Updates

    • OrbWeaver

      Does anyone actually use the Normalise button in the Surface inspector? Even after looking at the code I'm not quite sure what it's for.
      · 2 replies
    • Ansome

      Turns out my 15th anniversary mission idea has already been done once or twice before! I've been beaten to the punch once again, but I suppose that's to be expected when there's over 170 FMs out there, eh? I'm not complaining though, I love learning new tricks and taking inspiration from past FMs. Best of luck on your own fan missions!
      · 4 replies
    • The Black Arrow

      I wanna play Doom 3, but fhDoom has much better features than dhewm3, yet fhDoom is old, outdated and probably not supported. Damn!
      Makes me think that TDM engine for Doom 3 itself would actually be perfect.
      · 6 replies
    • Petike the Taffer

      Maybe a bit of advice ? In the FM series I'm preparing, the two main characters have the given names Toby and Agnes (it's the protagonist and deuteragonist, respectively), I've been toying with the idea of giving them family names as well, since many of the FM series have named protagonists who have surnames. Toby's from a family who were usually farriers, though he eventually wound up working as a cobbler (this serves as a daylight "front" for his night time thieving). Would it make sense if the man's popularly accepted family name was Farrier ? It's an existing, though less common English surname, and it directly refers to the profession practiced by his relatives. Your suggestions ?
      · 9 replies
    • nbohr1more

      Looks like the "Reverse April Fools" releases were too well hidden. Darkfate still hasn't acknowledge all the new releases. Did you play any of the new April Fools missions?
      · 5 replies
×
×
  • Create New...