Inside3D!
     

Upcoming Tutorial: The Seamless Multi-Map World
Goto page 1, 2  Next
 
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Engine Programming
View previous topic :: View next topic  
Author Message
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri May 28, 2010 2:14 am    Post subject: Upcoming Tutorial: The Seamless Multi-Map World Reply with quote

Thanks to some QuakeC guidance to Lardarse, I have successfully made an engine and QuakeC modification that allows seamless travel between maps leveraging the #ifdef Quake2 engine code and some mild finessing.

The way this works at least in my implementation is that you can do a trigger_changelevel indicating "mymap.spawn1" or "mymap.spawn2" with the "no intermission" flag set and travel between maps in the state that you left them.

So you could build a little universe made of several maps and travel amongst them without skipping a beat.

From a strict Quake perspective (which isn't what I am seeking with this), one drawback is that "save games" are rather meaningless because the progress of a single map just doesn't suffice to describe your current environment. But my interests in this aren't strict traditional Quake and since this is highly locked to the engine modifications required which require an altered progsdef, well ... for me this is just fine but this isn't standard engine friendly at all.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Back to top
View user's profile Send private message
Downsider



Joined: 16 Sep 2008
Posts: 477

PostPosted: Fri May 28, 2010 3:38 am    Post subject: Reply with quote

So more or less what Half-Life does? I say Half-Life because Half-Life certainly illustrates it better.

Entitys that travel across map when they're within a certain range of the change level trigger, more or less? Sounds neat, and honestly I've always pondered how such an effect was achieved. Cool
Back to top
View user's profile Send private message
Sajt



Joined: 16 Oct 2004
Posts: 1026

PostPosted: Fri May 28, 2010 4:16 am    Post subject: Reply with quote

You should mention the term "hub system" so old-timers know right away that you are not talking about something else. By the way, this feature is also possible in pure QuakeC, which I did in 2004 or so using FRIK_FILE (it also supported savegames, since each map had its own save file in a folder reserved for the save "slot").
_________________
F. A. Špork, an enlightened nobleman and a great patron of art, had a stately Baroque spa complex built on the banks of the River Labe.
Back to top
View user's profile Send private message
Downsider



Joined: 16 Sep 2008
Posts: 477

PostPosted: Fri May 28, 2010 4:36 am    Post subject: Reply with quote

FRIK_FILE isn't exactly pure QC.

If an engine dev is willing to add FRIK_FILE, why not add this as a proper, easier to use system?
Back to top
View user's profile Send private message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Fri May 28, 2010 6:59 am    Post subject: Reply with quote

I can understand why map size is limited clientside, but logically serverside the map dimensions are infinite. Why cant the server order and group together maps into 1 logical map?

would be interesting to see all the id1 maps stacked in the tightest configuration making the smallest dm map group.
Back to top
View user's profile Send private message
gnounc



Joined: 06 Apr 2009
Posts: 120

PostPosted: Fri May 28, 2010 8:54 am    Post subject: Reply with quote

This has been talked about for a long time.
I think its pretty slick, I'd like to see it in-game
Back to top
View user's profile Send private message
mh



Joined: 12 Jan 2008
Posts: 909

PostPosted: Fri May 28, 2010 8:59 am    Post subject: Reply with quote

r00k wrote:
I can understand why map size is limited clientside, but logically serverside the map dimensions are infinite. Why cant the server order and group together maps into 1 logical map?

would be interesting to see all the id1 maps stacked in the tightest configuration making the smallest dm map group.

There are other limits aside from dimensions. In particular the 65536 surfaces limit is likely to be hit early, and there's nothing anyone can do about that as indexes to them are stored in the BSP as unsigned shorts.

I wonder though how far it would be possible to go with grouping each of the ID1 episodes into one map.
_________________
DirectQ Engine - New release 1.8.666a, 9th August 2010
MHQuake Blog (General)
Direct3D 8 Quake Engines
Back to top
View user's profile Send private message Visit poster's website
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri May 28, 2010 9:29 am    Post subject: Reply with quote

Downsider wrote:
Entitys that travel across map when they're within a certain range of the change level trigger, more or less? Sounds neat, and honestly I've always pondered how such an effect was achieved. Cool


Actually no, communication failure on my part. Non-player entities can't make the trip across levels.

Sajt wrote:
You should mention the term "hub system" so old-timers know right away that you are not talking about something else. By the way, this feature is also possible in pure QuakeC, which I did in 2004 or so using FRIK_FILE (it also supported savegames, since each map had its own save file in a folder reserved for the save "slot").


Aw, hell. Didn't think of FRIK_FILE.

However, thinking about it a bit more I am not absolutely certain FRIK_FILE could do "it all" at least in a non-DP engine. Hmmmm.

I didn't say hub system because I was thinking more Half-Life or Q2 and well ... I never thought of those as hub systems.

Downsider wrote:
FRIK_FILE isn't exactly pure QC.

If an engine dev is willing to add FRIK_FILE, why not add this as a proper, easier to use system?


Meanwhile ... Downsider comes up with an insightful point that I don't think anything else would think of.

Scores:

Baker -2 (double fail)
Sajt +1
Downsider +5

Anyhow ... tutorial still will be coming ... I'm not sure the FRIK_FILE only method would work as well. I can't remember why at the moment though or if I am even right.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Back to top
View user's profile Send private message
Sajt



Joined: 16 Oct 2004
Posts: 1026

PostPosted: Fri May 28, 2010 10:43 am    Post subject: Reply with quote

Okay, I went back and looked at my code for saving. It does work, but it was sort of ugly code-wise, and couldn't hook onto Quake's save/load commands of course, using impulses instead...

Like Quake2, a save slot is a folder. It contains one global file containing the player's fields and which map he is currently on. This file can also be used across transitions for storing more info than fits in the QC parms. Then the save folder also contains an additional file for each map so far visited, each containing something resembling an entity dump. Like Quake2, there is an extra folder called "current" which holds the state for the current game you're playing. (This has the unfortunate side-effect of not allowing two copies of the singleplayer game to run at once, but who would want to do that? It could be hacked around, anyway...)

I also had changelevel transitions with "landmarks" a la Half-Life. That is, each changelevel trigger has a corresponding one on the other level, allowing back and forth travel. Your position relative to a linked "landmark" entity is saved and re-added to the corresponding landmark on the other side. This, along with a small and unobtrusive loading graphic, gives a "seamless" feel to the level transitions.

Yes, I had to manually write functions to save specified fields to files and read them back again. I'm sure that these days among the ridiculous DP extensions are some reflection-like functions that would let you iterate through entity fields just like the engine could, making the QC for this a lot cleaner and more maintainable.

As for coop, it would probably be complicated whether you implemented a hub system in QC or in the engine. I never even let myself think about that...

(By the way, if you wanted to see my bad juvenile code for this, look at this file and others in the repository. It's even more primitive than I remembered, but it was working in simple test maps.)

Downsider wrote:
FRIK_FILE isn't exactly pure QC.

If an engine dev is willing to add FRIK_FILE, why not add this as a proper, easier to use system?


FRIK_FILE is usually the very first extension a new engine will support. It might take ten more years to get a reasonably wide base of support for a new complicated feature like engine-side hubs. I used to be able to live outside the passage of time (waiting untold eons for CSQC, which would appear just after I finally "retired"), but that doesn't seem to work for me anymore!

But ignore that. Your point is totally valid. For the QC method to be elegant enough not to provoke vomiting, further QC extensions would be required, which would probably be even more complicated than a full engine-side hub implementation.

Anyway, someone who would actually go through the effort of making a single-player hub-based campaign would probably not mind limiting players to a select few engines. And it would probably be relatively simple to implement in the engine, anyway... Right Baker?

Baker wrote:
I didn't say hub system because I was thinking more Half-Life or Q2 and well ... I never thought of those as hub systems.


Actually, I hold those games, along with maybe ye olde Hexen, to be the very definition of a hub system. But I'm no authority on this subject.

edit: Fixed a split infinitive! Aargh!
_________________
F. A. Špork, an enlightened nobleman and a great patron of art, had a stately Baroque spa complex built on the banks of the River Labe.
Back to top
View user's profile Send private message
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri May 28, 2010 11:08 am    Post subject: Reply with quote

Sajt wrote:
Anyway, someone who would actually go through the effort of making a single-player hub-based campaign would probably not mind limiting players to a select few engines. And it would probably be relatively simple to implement in the engine, anyway... Right Baker?


Very easy.

I spent maybe 30 minutes enabling the appropriate Q2 engine code already included in the Q1 source release, maybe another 30 minutes opening some Q2 maps in a text editor to look at the entities, 30 minutes on the map, 30 minutes realizing the engine-code "as is" didn't deal properly with deaths and 2 hours pondering some QC issues (for instance "why are trigger_changelevels removing themselves". grrr. Was a real problem).

Yeah, you hit on the "each savegame must be a folder" limitation. I haven't dealt with that yet, not that really is hard to deal with.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Back to top
View user's profile Send private message
frag.machine



Joined: 25 Nov 2006
Posts: 728

PostPosted: Fri May 28, 2010 12:43 pm    Post subject: Reply with quote

mh wrote:
r00k wrote:
I can understand why map size is limited clientside, but logically serverside the map dimensions are infinite. Why cant the server order and group together maps into 1 logical map?

would be interesting to see all the id1 maps stacked in the tightest configuration making the smallest dm map group.

There are other limits aside from dimensions. In particular the 65536 surfaces limit is likely to be hit early, and there's nothing anyone can do about that as indexes to them are stored in the BSP as unsigned shorts.

I wonder though how far it would be possible to go with grouping each of the ID1 episodes into one map.


I already did this for episode 1. Smile

EDIT: Regarding proper ways to store all info from a persistent world, here my $0.02: I don't think FRIK_FILE is adequate to such task. We are talking about something that behaves pretty much in the same way most mmorpgs (storing everything for every entity found by the player, all the events that occured, etc), and the solution adopted in all of these examples was to use a RDBMS for that. IMHO, SQLite is the logical choice for such project: it's a fairly complete SQL embedded server in a single .c file, and would be easy to add a couple bult-ins to allow execute SQL commands from QuakeC.
_________________
frag.machine - Q2K4 Project
http://fragmachine.quakedev.com/
Back to top
View user's profile Send private message Visit poster's website
Spike



Joined: 05 Nov 2004
Posts: 944
Location: UK

PostPosted: Fri May 28, 2010 1:34 pm    Post subject: Reply with quote

you could use pakfiles instead of directories. But then what's the difference between a pak and a zip, and a compressed 'folder' and a regular directory, as far as file navigation is concerned.
Saved games need multiple maps, but you don't need to save every part of the current saved game data, nor the player.
The currently known maps (other than the actual current) can be stored as temp files anywhere on the hard drive (and probably in the disk cache). Just copy them to the saved game when saving. You don't need to keep the current map as valid.
FTE's saved games use directories. Current maps actually go in the gamedir, which is kinda bad. Q2 requires fopenable file names for each, and it helps to keep hexen2 mechanisms consistant.

Landmarks are useful for single player, but if you want coop support, you will likely need to teleport all other players to it, without spawnfragging. Like a regular coop map change. If you want to stick to the quake theme, you'd be using portals/slipgates to change maps anyway. :)
I suppose for coop, you could 'teleport' them, and transfer the triggering player through the landmark. Imho, this is the responsibility of the gamecode rather than the engine, the engine needs to provide only the name of the landmark. Offset from the landmark would be copied over with the player, so it works with mutliple players.
Copying non-player entities over is somewhat pointless, and complicated by precaches.
Copying player entities only really needs the parms for the most part, though megahealth/powerups will reset. Hexen2+FTE copy the entire player entity over, all fields. The QC gets a callback with an argument specifying the time difference between the two maps, and the timer fields are adjusted in QC (lots of +=, reflection can't do this as its just a float).
One limitation of both h2+q2 is that in order to change the state of a door/bridge/etc, you need to explicitly travel from one map to the one that has the door. This limitation is perhaps not too serious, but consider the reactions of NPCs to deaths in another part of the hub. An example from quake is the runes.

One thing I still want to do is to have a game server running multiple maps at once, with the players switching between the maps at will. This resolves uglyness with hubs+coop, as well as provides 'load' balancing with small maps in multiplayer (20 players on a dual map = 10 instances of the same map). But yeah, its not pretty.

FRIK_FILE cannot provide support for hubs with engine-based saved games, at least other than quicksave. You can write your own saved game functionality though, which shouldn't be hard if the level cache is saved cleanly. You would need to use serverflags (rune persistance stuff) to flag if it was a new unit or an continuation of a hub. Yes, you would have to hard code the list of fields to save. Add each field ref to an array (frikqcc or fteqcc ones), and save/load those ones depending on the field type. Shouldn't be too ugly.

Regarding limiting engines: If your extension is transparent enough, supporting engines will just work, and non-supporting engines will still work, they'll just respawn everything over map changes. With the use of serverflags in the mod, this need not be fatal, so long as the various doors are opened as required, ala prydon.
For reference, FTE exposes 1 extra argument to the changelevel builtin, and 1 extra global string, named spawnspot. Add the extra argument, it'll save the old map, and spawnspot will be cleared. Call it without the extra argument (or empty), and it'll flush all the old maps and do a regular map change. So QC changes are just: a) select player_start (and coops) such that targetname matches spawnspot, and b) make trigger_changelevel pass self.target as an optional second argument to changelevel. You won't have landmarks, but it'll be able to act like hexen2. Engines+mods that support it will use a hub system, engines that don't will respawn all ents. To use landmarks, you would need to transfer the player's offset from the changelevel/landmark trigger, and spawn them offset from the new one, using the spawnspot to specify which landmark to use.
Transfering all player fields is logically a separate extension, and will work without hubs.
_________________
What's a signature?
Back to top
View user's profile Send private message Visit poster's website
Spirit



Joined: 20 Nov 2004
Posts: 476

PostPosted: Fri May 28, 2010 3:08 pm    Post subject: Reply with quote

frag.machine wrote:
EDIT: Regarding proper ways to store all info from a persistent world, here my $0.02: I don't think FRIK_FILE is adequate to such task. We are talking about something that behaves pretty much in the same way most mmorpgs (storing everything for every entity found by the player, all the events that occured, etc), and the solution adopted in all of these examples was to use a RDBMS for that. IMHO, SQLite is the logical choice for such project: it's a fairly complete SQL embedded server in a single .c file, and would be easy to add a couple bult-ins to allow execute SQL commands from QuakeC.

Database access was recently suggested by quin when I asked for new bounty ideas. The problem is that a) I cannot estimate the amount of work and b) I do not really see fantastic use cases that I personally would like or care about (apart from very simple stuff).
_________________
Quake Maps
Back to top
View user's profile Send private message Visit poster's website
frag.machine



Joined: 25 Nov 2006
Posts: 728

PostPosted: Fri May 28, 2010 3:57 pm    Post subject: Reply with quote

Spirit wrote:
frag.machine wrote:
EDIT: Regarding proper ways to store all info from a persistent world, here my $0.02: I don't think FRIK_FILE is adequate to such task. We are talking about something that behaves pretty much in the same way most mmorpgs (storing everything for every entity found by the player, all the events that occured, etc), and the solution adopted in all of these examples was to use a RDBMS for that. IMHO, SQLite is the logical choice for such project: it's a fairly complete SQL embedded server in a single .c file, and would be easy to add a couple bult-ins to allow execute SQL commands from QuakeC.

Database access was recently suggested by quin when I asked for new bounty ideas. The problem is that a) I cannot estimate the amount of work and b) I do not really see fantastic use cases that I personally would like or care about (apart from very simple stuff).


a) is actually quite doable. SQLite can be embedded to almost any ANSI-C program without great effort. The bigger problem would be actually to define the built-ins in QuakeC, but even this can be worked around. For more info, this is SQLite's site.

Regarding b) I can foresee a good number of possibilities: besides the idea of seamless worlds, one could create quest-oriented mods, where all the related data - which monsters, their locations, npc dialogs and behavior, etc - could be defined in terms of SQL load scripts. Imagine Prydon Gate supporting new quests just by database downloads (another nifty feature to add to an engine, BTW).
_________________
frag.machine - Q2K4 Project
http://fragmachine.quakedev.com/
Back to top
View user's profile Send private message Visit poster's website
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri May 28, 2010 4:01 pm    Post subject: Reply with quote

Databases ... bleh. Sounds like a cross-platform killer. Combined with FRIK_FILE and adding some extra persistent fields, you'd really have quite enough room for a mountain-sized heap of creativity with minimal effort.

One opinion. And maybe wrong and narrowminded.

PART I: Engine Tutorial

1. Take stock GLQuake. Much of this will involve enabling QUAKE2 build code. Source: http://forums.inside3d.com/viewtopic.php?t=1281 (free MS VC+ Express) or http://www.quakedev.com/files/quake1/q1source.zip (MS Visual Studio 6)

2. hostcmd.c - Change all instances of #ifdef QUAKE2 to "#if 666" to enable except line #615 which I don't see as important. This makes it so upon entering and exiting a level that the state of the level is written to file or read from a file.

3. pr_cmds.c - Make PF_changelevel support a "startspot" for being able to enter a map at different locations. Find this code ...

Code:
/*
==============
PF_changelevel
==============
*/
void PF_changelevel (void)
{
#ifdef QUAKE2
   char   *s1, *s2;

   if (svs.changelevel_issued)
      return;
   svs.changelevel_issued = true;

   s1 = G_STRING(OFS_PARM0);
   s2 = G_STRING(OFS_PARM1);

   if ((int)pr_global_struct->serverflags & (SFL_NEW_UNIT | SFL_NEW_EPISODE))
      Cbuf_AddText (va("changelevel %s %s\n",s1, s2));
   else
      Cbuf_AddText (va("changelevel2 %s %s\n",s1, s2));
#else
   char   *s;

// make sure we don't issue two changelevels
   if (svs.changelevel_issued)
      return;
   svs.changelevel_issued = true;
   
   s = G_STRING(OFS_PARM0);
   Cbuf_AddText (va("changelevel %s\n",s));
#endif
}


and replace with this code.

Code:
/*
==============
PF_changelevel
==============
*/
void PF_changelevel (void)
{
#if 666
   char   *s, *s1, *s2;

   if (svs.changelevel_issued)
      return;
   svs.changelevel_issued = true;

#if 0
   s1 = G_STRING(OFS_PARM0);
   s2 = G_STRING(OFS_PARM1);
#endif

#if 1
   s = G_STRING(OFS_PARM0);

   COM_StripExtension (s, s1);
   s2 = COM_FileExtension (s);
#endif

   if ((int)pr_global_struct->serverflags & (SFL_NEW_UNIT | SFL_NEW_EPISODE)) {
      Cbuf_AddText (va("changelevel %s %s\n",s1, s2));
      
   }
   else {
      Cbuf_AddText (va("changelevel2 %s %s\n",s1, s2));
   }
#else
   char   *s;

// make sure we don't issue two changelevels
   if (svs.changelevel_issued)
      return;
   svs.changelevel_issued = true;

   s = G_STRING(OFS_PARM0);
   Cbuf_AddText (va("changelevel %s\n",s));
#endif
}


3. progdefs.h - Change #ifdef QUAKE2 to #if 666

4. server.h - Change all instances of #ifdef QUAKE2 to #if 666. We are taking the whole Q2'd deal.

5. sv_main.c - On lines 1044, 1090, 1171 change #ifdef QUAKE2 to #if 666 to enable.

6. Open hostcmd.c AGAIN and add the yellow because when you die it needs to restore the state at the beginning of the level as when you arrived instead of the normal restoring the map to as if you just got there ...

Quote:
#if 666
strcpy(startspot, sv.startspot);

// try to restore the new level
if (LoadGamestate (mapname, startspot))

SV_SpawnServer (mapname, startspot);
else
SV_SpawnServer (mapname, NULL);


#else
SV_SpawnServer (mapname);
#endif


PART II: QuakeC Tutorial

1. Replace defs.qc with this one to mirror the Q2 build global variables. It is also important for the CRC check.

The fields added are startspot, null, basevelocity, drawPercent, gravity, mass, light_level, items2, pitch_speed, dmg, dmgtime, air_finished, pain_finished, radsuit_finished, speed.

Code:

/*
==============================================================================

         SOURCE FOR GLOBALVARS_T C STRUCTURE

==============================================================================
*/

//
// system globals
//
entity      self;
entity      other;
entity      world;
float      time;
float      frametime;

float      force_retouch;      // force all entities to touch triggers
                        // next frame.  this is needed because
                        // non-moving things don't normally scan
                        // for triggers, and when a trigger is
                        // created (like a teleport trigger), it
                        // needs to catch everything.
                        // decremented each frame, so set to 2
                        // to guarantee everything is touched
string      mapname;
string      startspot;

float      deathmatch;
float      coop;
float      teamplay;

float      serverflags;      // propagated from level to level, used to
                        // keep track of completed episodes

float      total_secrets;
float      total_monsters;

float      found_secrets;      // number of secrets found
float      killed_monsters;   // number of monsters killed


// spawnparms are used to encode information about clients across server
// level changes
float      parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16;

//
// global variables set by built in functions
//   
vector      v_forward, v_up, v_right;   // set by makevectors()
   
// set by traceline / tracebox
float      trace_allsolid;
float      trace_startsolid;
float      trace_fraction;
vector      trace_endpos;
vector      trace_plane_normal;
float      trace_plane_dist;
entity      trace_ent;
float      trace_inopen;
float      trace_inwater;

entity      msg_entity;            // destination of single entity writes
string      null;

//
// required prog functions
//
void()       main;                  // only for testing

void()      StartFrame;

void()       PlayerPreThink;
void()       PlayerPostThink;

void()      ClientKill;
void()      ClientConnect;
void()       PutClientInServer;      // call after setting the parm1... parms
void()      ClientDisconnect;

void()      SetNewParms;         // called when a client first connects to
                           // a server. sets parms so they can be
                           // saved off for restarts

void()      SetChangeParms;         // call to set parms for self so they can
                           // be saved for a level transition


//================================================
void      end_sys_globals;      // flag for structure dumping
//================================================

/*
==============================================================================

         SOURCE FOR ENTVARS_T C STRUCTURE

==============================================================================
*/

//
// system fields (*** = do not set in prog code, maintained by C code)
//
.float      modelindex;      // *** model index in the precached list
.vector      absmin, absmax;   // *** origin + mins / maxs

.float      ltime;         // local time for entity
.float      movetype;
.float      solid;

.vector      origin;         // ***
.vector      oldorigin;      // ***
.vector      velocity;
.vector      angles;
.vector      avelocity;
.vector      basevelocity;

.vector      punchangle;      // temp angle adjust from damage or recoil

.string      classname;      // spawn function
.string      model;
.float      frame;
.float      skin;
.float      effects;
.float      drawPercent;
.float      gravity;
.float      mass;
.float      light_level;

.vector      mins, maxs;      // bounding box extents reletive to origin
.vector      size;         // maxs - mins

.void()      touch;
.void()      use;
.void()      think;
.void()      blocked;      // for doors or plats, called when can't push other

.float      nextthink;
.entity      groundentity;

// stats
.float      health;
.float      frags;
.float      weapon;         // one of the IT_SHOTGUN, etc flags
.string      weaponmodel;
.float      weaponframe;
.float      currentammo;
.float      ammo_shells, ammo_nails, ammo_rockets, ammo_cells;

.float      items;         // bit flags
.float      items2;

.float      takedamage;
.entity      chain;
.float      deadflag;

.vector      view_ofs;         // add to origin to get eye point


.float      button0;      // fire
.float      button1;      // use
.float      button2;      // jump

.float      impulse;      // weapon changes

.float      fixangle;
.vector      v_angle;      // view / targeting angle for players
.float      idealpitch;      // calculated pitch angle for lookup up slopes
.float      pitch_speed;

.string      netname;

.entity    enemy;

.float      flags;

.float      colormap;
.float      team;

.float      max_health;      // players maximum health is stored here

.float      teleport_time;   // don't back up

.float      armortype;      // save this fraction of incoming damage
.float      armorvalue;

.float      waterlevel;      // 0 = not in, 1 = feet, 2 = wast, 3 = eyes
.float      watertype;      // a contents value

.float      ideal_yaw;
.float      yaw_speed;

.entity      aiment;

.entity    goalentity;      // a movetarget or an enemy

.float      spawnflags;

.string      target;
.string      targetname;

// damage is accumulated through a frame. and sent as one single
// message, so the super shotgun doesn't generate huge messages
.float      dmg_take;
.float      dmg_save;
.entity      dmg_inflictor;

.entity      owner;      // who launched a missile
.vector      movedir;   // mostly for doors, but also used for waterjump

.string      message;      // trigger messages

.float      sounds;      // either a cd track number or sound number

.string      noise, noise1, noise2, noise3;   // contains names of wavs to play
.float   dmg;
.float   dmgtime;
.float   air_finished;
.float   pain_finished;
.float   radsuit_finished;
.float   speed;

//================================================
void      end_sys_fields;         // flag for structure dumping
//================================================

/*
==============================================================================

            VARS NOT REFERENCED BY C CODE

==============================================================================
*/


//
// constants
//

float   FALSE               = 0;
float    TRUE               = 1;

// edict.flags
float   FL_FLY               = 1;
float   FL_SWIM               = 2;
float   FL_CLIENT            = 8;   // set for all client edicts
float   FL_INWATER            = 16;   // for enter / leave water splash
float   FL_MONSTER            = 32;
float   FL_GODMODE            = 64;   // player cheat
float   FL_NOTARGET            = 128;   // player cheat
float   FL_ITEM               = 256;   // extra wide size for bonus items
float   FL_ONGROUND            = 512;   // standing on something
float   FL_PARTIALGROUND      = 1024;   // not all corners are valid
float   FL_WATERJUMP         = 2048;   // player jumping out of water
float   FL_JUMPRELEASED         = 4096;   // for jump debouncing

// edict.movetype values
float   MOVETYPE_NONE         = 0;   // never moves
//float   MOVETYPE_ANGLENOCLIP   = 1;
//float   MOVETYPE_ANGLECLIP      = 2;
float   MOVETYPE_WALK         = 3;   // players only
float   MOVETYPE_STEP         = 4;   // discrete, not real time unless fall
float   MOVETYPE_FLY         = 5;
float   MOVETYPE_TOSS         = 6;   // gravity
float   MOVETYPE_PUSH         = 7;   // no clip to world, push and crush
float   MOVETYPE_NOCLIP         = 8;
float   MOVETYPE_FLYMISSILE      = 9;   // fly with extra size against monsters
float   MOVETYPE_BOUNCE         = 10;
float   MOVETYPE_BOUNCEMISSILE   = 11;   // bounce with extra size

// edict.solid values
float   SOLID_NOT            = 0;   // no interaction with other objects
float   SOLID_TRIGGER         = 1;   // touch on edge, but not blocking
float   SOLID_BBOX            = 2;   // touch on edge, block
float   SOLID_SLIDEBOX         = 3;   // touch on edge, but not an onground
float   SOLID_BSP            = 4;   // bsp clip, touch on edge, block

// range values
float   RANGE_MELEE            = 0;
float   RANGE_NEAR            = 1;
float   RANGE_MID            = 2;
float   RANGE_FAR            = 3;

// deadflag values

float   DEAD_NO               = 0;
float   DEAD_DYING            = 1;
float   DEAD_DEAD            = 2;
float   DEAD_RESPAWNABLE      = 3;

// takedamage values

float   DAMAGE_NO            = 0;
float   DAMAGE_YES            = 1;
float   DAMAGE_AIM            = 2;

// items
float   IT_AXE               = 4096;
float   IT_SHOTGUN            = 1;
float   IT_SUPER_SHOTGUN      = 2;
float   IT_NAILGUN            = 4;
float   IT_SUPER_NAILGUN      = 8;
float   IT_GRENADE_LAUNCHER      = 16;
float   IT_ROCKET_LAUNCHER      = 32;
float   IT_LIGHTNING         = 64;
float   IT_EXTRA_WEAPON         = 128;

float   IT_SHELLS            = 256;
float   IT_NAILS            = 512;
float   IT_ROCKETS            = 1024;
float   IT_CELLS            = 2048;

float   IT_ARMOR1            = 8192;
float   IT_ARMOR2            = 16384;
float   IT_ARMOR3            = 32768;
float   IT_SUPERHEALTH         = 65536;

float   IT_KEY1               = 131072;
float   IT_KEY2               = 262144;

float   IT_INVISIBILITY         = 524288;
float   IT_INVULNERABILITY      = 1048576;
float   IT_SUIT               = 2097152;
float   IT_QUAD               = 4194304;

// point content values

float   CONTENT_EMPTY         = -1;
float   CONTENT_SOLID         = -2;
float   CONTENT_WATER         = -3;
float   CONTENT_SLIME         = -4;
float   CONTENT_LAVA         = -5;
float   CONTENT_SKY            = -6;

float   STATE_TOP      = 0;
float   STATE_BOTTOM   = 1;
float   STATE_UP      = 2;
float   STATE_DOWN      = 3;

vector   VEC_ORIGIN = '0 0 0';
vector   VEC_HULL_MIN = '-16 -16 -24';
vector   VEC_HULL_MAX = '16 16 32';

vector   VEC_HULL2_MIN = '-32 -32 -24';
vector   VEC_HULL2_MAX = '32 32 64';

// protocol bytes
float   SVC_TEMPENTITY      = 23;
float   SVC_KILLEDMONSTER   = 27;
float   SVC_FOUNDSECRET      = 28;
float   SVC_INTERMISSION   = 30;
float   SVC_FINALE         = 31;
float   SVC_CDTRACK         = 32;
float   SVC_SELLSCREEN      = 33;


float   TE_SPIKE      = 0;
float   TE_SUPERSPIKE   = 1;
float   TE_GUNSHOT      = 2;
float   TE_EXPLOSION   = 3;
float   TE_TAREXPLOSION   = 4;
float   TE_LIGHTNING1   = 5;
float   TE_LIGHTNING2   = 6;
float   TE_WIZSPIKE      = 7;
float   TE_KNIGHTSPIKE   = 8;
float   TE_LIGHTNING3   = 9;
float   TE_LAVASPLASH   = 10;
float   TE_TELEPORT      = 11;

// sound channels
// channel 0 never willingly overrides
// other channels (1-7) allways override a playing sound on that channel
float   CHAN_AUTO      = 0;
float   CHAN_WEAPON      = 1;
float   CHAN_VOICE      = 2;
float   CHAN_ITEM      = 3;
float   CHAN_BODY      = 4;

float   ATTN_NONE      = 0;
float   ATTN_NORM      = 1;
float   ATTN_IDLE      = 2;
float   ATTN_STATIC      = 3;

// update types

float   UPDATE_GENERAL   = 0;
float   UPDATE_STATIC   = 1;
float   UPDATE_BINARY   = 2;
float   UPDATE_TEMP      = 3;

// entity effects

float   EF_BRIGHTFIELD   = 1;
float   EF_MUZZLEFLASH    = 2;
float   EF_BRIGHTLIGHT    = 4;
float   EF_DIMLIGHT    = 8;


// messages
float   MSG_BROADCAST   = 0;      // unreliable to all
float   MSG_ONE         = 1;      // reliable to one (msg_entity)
float   MSG_ALL         = 2;      // reliable to all
float   MSG_INIT      = 3;      // write to the init string

//================================================

//
// globals
//
float   movedist;
float   gameover;      // set when a rule exits

string   string_null;   // null string, nothing should be held here
float   empty_float;

entity   newmis;         // launch_spike sets this after spawning it

entity   activator;      // the entity that activated a trigger or brush

entity   damage_attacker;   // set by T_Damage
float   framecount;

float      skill;

//================================================

//
// world fields (FIXME: make globals)
//
.string      wad;
.string    map;
.float      worldtype;   // 0=medieval 1=metal 2=base

//================================================

.string      killtarget;

//
// quakeed fields
//
.float      light_lev;      // not used by game, but parsed by light util
.float      style;


//
// monster ai
//
.void()      th_stand;
.void()      th_walk;
.void()      th_run;
.void()      th_missile;
.void()      th_melee;
.void(entity attacker, float damage)      th_pain;
.void()      th_die;

.entity      oldenemy;      // mad at this player before taking damage

.float      speed;

.float   lefty;

.float   search_time;
.float   attack_state;

float   AS_STRAIGHT      = 1;
float   AS_SLIDING      = 2;
float   AS_MELEE      = 3;
float   AS_MISSILE      = 4;

//
// player only fields
//
.float      walkframe;

.float       attack_finished;
.float      pain_finished;

.float      invincible_finished;
.float      invisible_finished;
.float      super_damage_finished;
.float      radsuit_finished;

.float      invincible_time, invincible_sound;
.float      invisible_time, invisible_sound;
.float      super_time, super_sound;
.float      rad_time;
.float      fly_sound;

.float      axhitme;

.float      show_hostile;   // set to time+0.2 whenever a client fires a
                     // weapon or takes damage.  Used to alert
                     // monsters that otherwise would let the player go
.float      jump_flag;      // player jump flag
.float      swim_flag;      // player swimming sound flag
.float      air_finished;   // when time > air_finished, start drowning
.float      bubble_count;   // keeps track of the number of bubbles
.string      deathtype;      // keeps track of how the player died

//
// object stuff
//
.string      mdl;
.vector      mangle;         // angle at start

.vector      oldorigin;      // only used by secret door

.float      t_length, t_width;


//
// doors, etc
//
.vector      dest, dest1, dest2;
.float      wait;         // time from firing to restarting
.float      delay;         // time from activation to firing
.entity      trigger_field;   // door's trigger entity
.string      noise4;

//
// monsters
//
.float       pausetime;
.entity    movetarget;


//
// doors
//
.float      aflag;
.float      dmg;         // damage done by door when hit
   
//
// misc
//
.float      cnt;          // misc flag
   
//
// subs
//
.void()      think1;
.vector      finaldest, finalangle;

//
// triggers
//
.float      count;         // for counting triggers


//
// plats / doors / buttons
//
.float      lip;
.float      state;
.vector      pos1, pos2;      // top and bottom positions
.float      height;

//
// sounds
//
.float      waitmin, waitmax;
.float      distance;
.float      volume;




//===========================================================================
   

//
// builtin functions
//

void(vector ang)   makevectors      = #1;      // sets v_forward, etc globals
void(entity e, vector o) setorigin   = #2;
void(entity e, string m) setmodel   = #3;      // set movetype and solid first
void(entity e, vector min, vector max) setsize = #4;
// #5 was removed
void() break                  = #6;
float() random                  = #7;      // returns 0 - 1
void(entity e, float chan, string samp, float vol, float atten) sound = #8;
vector(vector v) normalize         = #9;
void(string e) error            = #10;
void(string e) objerror            = #11;
float(vector v) vlen            = #12;
float(vector v) vectoyaw         = #13;
entity() spawn                  = #14;
void(entity e) remove            = #15;

// sets trace_* globals
// nomonsters can be:
// An entity will also be ignored for testing if forent == test,
// forent->owner == test, or test->owner == forent
// a forent of world is ignored
void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16;   

entity() checkclient            = #17;   // returns a client to look for
entity(entity start, .string fld, string match) find = #18;
string(string s) precache_sound      = #19;
string(string s) precache_model      = #20;
void(entity client, string s)stuffcmd = #21;
entity(vector org, float rad) findradius = #22;
void(string s) bprint            = #23;
void(entity client, string s) sprint = #24;
void(string s) dprint            = #25;
string(float f) ftos            = #26;
string(vector v) vtos            = #27;
void() coredump                  = #28;      // prints all edicts
void() traceon                  = #29;      // turns statment trace on
void() traceoff                  = #30;
void(entity e) eprint            = #31;      // prints an entire edict
float(float yaw, float dist) walkmove   = #32;   // returns TRUE or FALSE
// #33 was removed
float(float yaw, float dist) droptofloor= #34;   // TRUE if landed on floor
void(float style, string value) lightstyle = #35;
float(float v) rint               = #36;      // round to nearest int
float(float v) floor            = #37;      // largest integer <= v
float(float v) ceil               = #38;      // smallest integer >= v
// #39 was removed
float(entity e) checkbottom         = #40;      // true if self is on ground
float(vector v) pointcontents      = #41;      // returns a CONTENT_*
// #42 was removed
float(float f) fabs = #43;
vector(entity e, float speed) aim = #44;      // returns the shooting vector
float(string s) cvar = #45;                  // return cvar.value
void(string s) localcmd = #46;               // put string into local que
entity(entity e) nextent = #47;               // for looping through all ents
void(vector o, vector d, float color, float count) particle = #48;// start a particle effect
void() ChangeYaw = #49;                  // turn towards self.ideal_yaw
                                 // at self.yaw_speed
// #50 was removed
vector(vector v) vectoangles         = #51;

//
// direct client message generation
//
void(float to, float f) WriteByte      = #52;
void(float to, float f) WriteChar      = #53;
void(float to, float f) WriteShort      = #54;
void(float to, float f) WriteLong      = #55;
void(float to, float f) WriteCoord      = #56;
void(float to, float f) WriteAngle      = #57;
void(float to, string s) WriteString   = #58;
void(float to, entity s) WriteEntity   = #59;

//
// broadcast client message generation
//

// void(float f) bWriteByte      = #59;
// void(float f) bWriteChar      = #60;
// void(float f) bWriteShort      = #61;
// void(float f) bWriteLong      = #62;
// void(float f) bWriteCoord      = #63;
// void(float f) bWriteAngle      = #64;
// void(string s) bWriteString   = #65;
// void(entity e) bWriteEntity = #66;

void(float step) movetogoal            = #67;

string(string s) precache_file      = #68;   // no effect except for -copy
void(entity e) makestatic      = #69;
void(string s) changelevel = #70;

//#71 was removed

void(string var, string val) cvar_set = #72;   // sets cvar.value

void(entity client, string s) centerprint = #73;   // sprint, but in middle

void(vector pos, string samp, float vol, float atten) ambientsound = #74;

string(string s) precache_model2   = #75;      // registered version only
string(string s) precache_sound2   = #76;      // registered version only
string(string s) precache_file2      = #77;      // registered version only

void(entity e) setspawnparms      = #78;      // set parm1... to the
                                    // values at level start
                                    // for coop respawn

//============================================================================

//
// subs.qc
//
void(vector tdest, float tspeed, void() func) SUB_CalcMove;
void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt;
void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
void()  SUB_CalcMoveDone;
void() SUB_CalcAngleMoveDone;
void() SUB_Null;
void() SUB_UseTargets;
void() SUB_Remove;

//
//   combat.qc
//
void(entity targ, entity inflictor, entity attacker, float damage) T_Damage;


float (entity e, float healamount, float ignore) T_Heal; // health function

float(entity targ, entity inflictor) CanDamage;




2. Now find the following:

Code:
   spot = find (world, classname, "info_player_start");
   if (!spot)
      error ("PutClientInServer: no info_player_start on level");
   
   return spot;


And replace with ...

(This should be rewritten to loop through all the info_player_start entities and then match the startspot ... but I was lazy and only had 5 minutes when working with the qc).

Code:
   if (!startspot) {
      // No startspot specified, use default
      spot = find (world, targetname, "default");
   } else {
      spot = find (world, targetname, startspot);
      // Looking for info_player_start named startspot
   }
   
   if (!spot)
      error ("PutClientInServer: no applicable spawn point");


PART III: Mapping Differences

You can now do ...

Quote:
{
"classname" "trigger_changelevel"
"spawnflags" "1" // Important, a normal trigger_changelevel removes itself
"map" "map1.spawn2" // Specify map plus spawn point name
...
}


Which when used would spawn you at:

Quote:
{
"classname" "info_player_start"
"targetname" "spawn2" // This is the name of this spawn point
"origin" "512 -416 192"
}


The general idea is that you now can have multiple info_player_start spots as multiple entry points into a map.

With every info_player_start you should specify the targetname as the name of the spawn point.

With every trigger_changelevel, you need to set the flag for no intermission or the trigger_changelevel will remove itself.

Quick with room for improvement and some need for polish, but very much functional and fun to play around with.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Engine Programming All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2004 phpBB Group