From 25ee961232b28df98f7be31050ab979e3c63fba0 Mon Sep 17 00:00:00 2001 From: div0 Date: Thu, 28 Aug 2008 13:26:19 +0000 Subject: [PATCH] r4215 | div0 | 2008-08-27 15:22:10 +0200 (Wed, 27 Aug 2008) | 2 lines fix (unrelated) bug in misc_laser code, causing CSQC entity message spam r4216 | div0 | 2008-08-28 09:39:24 +0200 (Thu, 28 Aug 2008) | 3 lines - get rid of delayed init where I found it and replace it by something that runs BEFORE think (in the very first StartFrame); makes sure misc_follow can work right (as all entities are on their INITIAL location then) - make misc_laser work with misc_follow r4217 | esteel | 2008-08-28 09:46:02 +0200 (Thu, 28 Aug 2008) | 2 lines explain jumping and pressing/holding space better, add a few more links at the end r4218 | div0 | 2008-08-28 11:37:08 +0200 (Thu, 28 Aug 2008) | 2 lines more laser madness (now: dark lasers, pulsating laser, laser beam interpolation) r4219 | m0rfar | 2008-08-28 13:10:47 +0200 (Thu, 28 Aug 2008) | 4 lines Changed default respawn times for 2 items. 50 health = 20 sec (was 15 sec) 25 armor = 20 sec (was 30 sec) This will hopefully result in a better experience for us all :) r4220 | div0 | 2008-08-28 14:14:11 +0200 (Thu, 28 Aug 2008) | 2 lines fix TE_SPARK again r4221 | div0 | 2008-08-28 15:23:46 +0200 (Thu, 28 Aug 2008) | 2 lines zoom: do it entirely client side git-svn-id: svn://svn.icculus.org/nexuiz/branches/nexuiz-2.0@4222 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- .patchsets | 2 +- Docs/basics.aft | 13 ++-- Docs/basics.html | 13 ++-- data/effectinfo.txt | 14 +--- data/particles/laserbeam.tga | Bin 428 -> 24459 bytes data/qcsrc/client/Main.qc | 35 ++++++++-- data/qcsrc/client/View.qc | 64 ++++++++++++++++++ data/qcsrc/client/hook.qc | 18 ++--- data/qcsrc/client/laser.qc | 104 +++++++++++++++++++++++++++-- data/qcsrc/client/main.qh | 7 ++ data/qcsrc/client/sbar.qc | 1 - data/qcsrc/common/constants.qh | 2 + data/qcsrc/server/cl_client.qc | 58 ++-------------- data/qcsrc/server/ctf.qc | 8 +-- data/qcsrc/server/defs.qh | 2 + data/qcsrc/server/domination.qc | 7 +- data/qcsrc/server/g_lights.qc | 38 ++++++----- data/qcsrc/server/g_triggers.qc | 73 ++++++++++++++------ data/qcsrc/server/g_world.qc | 2 + data/qcsrc/server/miscfunctions.qc | 70 +++++++++++++++++++ data/qcsrc/server/sv_main.qc | 2 + data/qcsrc/server/t_items.qc | 4 +- data/qcsrc/server/t_jumppads.qc | 3 +- data/qcsrc/server/t_plats.qc | 8 +-- data/qcsrc/server/t_teleporters.qc | 4 +- data/qcsrc/server/teamplay.qc | 16 +---- data/scripts/entities.def | 8 ++- 27 files changed, 405 insertions(+), 171 deletions(-) diff --git a/.patchsets b/.patchsets index 231bf2431..9bf1e269f 100644 --- a/.patchsets +++ b/.patchsets @@ -1,2 +1,2 @@ master = svn://svn.icculus.org/nexuiz/trunk -revisions_applied = 1-4213 +revisions_applied = 1-4221 diff --git a/Docs/basics.aft b/Docs/basics.aft index c6cba443b..de2d72072 100644 --- a/Docs/basics.aft +++ b/Docs/basics.aft @@ -54,7 +54,7 @@ To put up a good fight you need to survive an attack, so make sure you pick up h The health and armor system in Nexuiz is different to other games in that it tends toward an equilibrium of 100 health and 100 armor. You can collect as much health and armor as you like, but it will degenerate toward these values. The more health and armor you have, the faster it will degenerate - but when you pick them up the degeneration stops momentarily. Because of this, you can't just load up on health and armor and head off into battle - you need to constantly replenish to remain strong. On the converse, though, when your health is below 100 it will slowly ''re''generate (armor will not). The lower it is, the faster it will replenish. This regeneration stops for a few seconds if you are hurt, so just running away after a fight is not usually enough to restore you to useful levels again - you have to wait a while to regain your full health, and so picking up health after a fight is still important. -In regard to the previous point of working a map to control items, and also shooting at where your opponent will be, it's important to try to predict where on the map your opponents are even when you can't directly see them. Firstly, try to listen for them; secondly, learn the maps so you know the most useful routes and the most popular areas to which people tend to gravitate. It gives you a marked advantage to be able to say, "I saw or heard someone pick up an armor, so they must be at such-and-such location, and from there he can only go to this place or that place." Just shooting something to the likely places your opponent will be - a rocket or a grenade for example - can often result in either a surprise kill, or in surprise damage which you can then follow up. Don't underestimate how likely a well-aimed educated guess is to hit someone. Maybe he'll walk into it - rockets can be hard to see coming, and grenades lying on the floor or spammed into a small space can be really hard to avoid. Once you've hurt him you'll hear the tink of your damage, and you will also know for sure were he is and can finish him off. You can pretty much 'lock down' a map by picking up items, and making sure others have a hard time getting around which will also mess with their concentration. +In regard to the previous point of working a map to control items, and also shooting at where your opponent will be, it's important to try to predict where on the map your opponents are even when you can't directly see them. First, try to listen for them; secondly, learn the maps so you know the most useful routes and the most popular areas to which people tend to gravitate. It gives you a marked advantage to be able to say, "I saw or heard someone pick up an armor, so they must be at such-and-such location, and from there he can only go to this place or that place." Just shooting something to the likely places your opponent will be - a rocket or a grenade for example - can often result in either a surprise kill, or in surprise damage which you can then follow up. Don't underestimate how likely a well-aimed educated guess is to hit someone. Maybe he'll walk into it - rockets can be hard to see coming, and grenades lying on the floor or spammed into a small space can be really hard to avoid. Once you've hurt him you'll hear the tink of your damage, and you will also know for sure were he is and can finish him off. You can pretty much 'lock down' a map by picking up items, and making sure others have a hard time getting around which will also mess with their concentration. **''Using Weapons Effectively'' Among the items you can pick up are the weapons. Some of them are very unique to Nexuiz, and to get the most out of them you need to be familiar with every aspect of how they work - including the rate of fire, the damage per hit, the spread and speed of the payload, and so on. @@ -84,7 +84,7 @@ Now that you know all the guns you also have to understand that in Nexuiz each p **''Using Jumping Effectively'' You will need to be able to get around the map quickly and efficiently if you are to hunt down your opponents, and collect items well. As already mentioned, the laser is useful for jumping and gaining speed - but you can use all explosive weapons for a similar effect. Other weapons, however, will cause more damage to your health, and not all offer the same strong push as the laser. The rocket launcher will push you twice as far, but you will also take ''lots'' more damage. The mortar has the same push as the laser, but its explosion is bigger so the timing and the right angle are less of an issue. For this reason, the mortar is better for a beginner to learn these sorts of jumps. -You should also know that you gain a bit extra speed moving forward simply by jumping. When you walk, there is friction from the floor which keeps you at a constant speed. If you jump as you walk, the amount you touch the floor is considerably less, and so the friction does not slow you down as much. The longer you jump, the faster you go. The most simple way to do this is to run forward, jump, and then while you are in the air release and repress the jump button. Your character will jump again the moment you land, without you having to worry about timing hitting the jump key. Unlike other games, Nexuiz does not need you to do this with strange key combinations or even with circular movements of the mouse - trying to do these will generally make you slower. The only time that strafe-jumping (holding one of the strafe-keys while jumping) is useful is for the very first jump you make. This can increase your jump speed a little - although not by much. +You should also know that you gain a bit extra speed moving forward simply by jumping. When you walk, there is friction from the floor which keeps you at a constant speed. If you jump as you walk, the amount you touch the floor is considerably less, and so the friction does not slow you down as much. The longer you jump, the faster you go. The most simple way to do this is to run forward, jump, and then while you are in the air release and repress/hold the jump button. Your character will jump again the moment you land, without you having to worry about timing hitting the jump key. Just repeat it a few times and you will notice you get faster and faster. Unlike other games, Nexuiz does not need you to do this with strange key combinations or even with circular movements of the mouse - trying to do these will generally make you slower. The only time that strafe-jumping (holding one of the strafe-keys while jumping) is useful is for the very first jump you make. This can increase your jump speed a little - although not by much. Another useful thing to know about movement is that if you run and jump, you can release the forward key and instead press the left or right key, and turn your mouse in the same direction. Doing so will turn or 'bend' your jump in this direction. This is a useful trick to get around corners, to become less predictable, and also to avoid having to stop jumping to change direction. @@ -119,11 +119,16 @@ Nexuiz supports several different game modes and some smaller changes called mut There are some more of these mutators, but those are the most important ones. A list of useful links: - *CTF guide and ctf MAPS guide: {+http://alientrap.org/forum/viewtopic.php?t=849+} - *Team communication explained: {+http://alientrap.org/forum/viewtopic.php?t=555+} + *The official Nexuiz forum: {+http://alientrap.org/forum+} + *The semiofficial Nexuiz wiki: {+http://alientrap.org/wiki+} + *The official Nexuiz ladder: {+http://planetnexuiz.com/ladder+} + *The Official Nexuiz tournaments: {+http://planetnexuiz.com/tourney+} *General Nexuiz Tips and Tricks: {+http://alientrap.org/forum/viewtopic.php?t=90+} *How to get more help via IRC: {+http://alientrap.org/forum/viewtopic.php?t=497+} + *CTF guide and ctf MAPS guide: {+http://alientrap.org/forum/viewtopic.php?t=849+} + *Team communication explained: {+http://alientrap.org/forum/viewtopic.php?t=555+} *A few useful jumps: http://esteel.planetnexuiz.de/nexuiz/demos/jumps.pk3 (copy the file into Nexuiz/data and you should find the demos in the demos menu) *FPS settings: {+http://alientrap.org/forum/viewtopic.php?t=1628+} *A list of all available variables in Nexuiz: {+http://alientrap.org/wiki/pmwiki.php?n=Main.GameTweaks+} *A list of all available commands in Nexuiz: {+http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands+} + *Some info about generic strategy in games: {+http://alientrap.org/forum/viewtopic.php?t=3496+} diff --git a/Docs/basics.html b/Docs/basics.html index da3b8a237..dae15a6e1 100644 --- a/Docs/basics.html +++ b/Docs/basics.html @@ -188,7 +188,7 @@ To put up a good fight you need to survive an attack, so make sure you pick up h The health and armor system in Nexuiz is different to other games in that it tends toward an equilibrium of 100 health and 100 armor. You can collect as much health and armor as you like, but it will degenerate toward these values. The more health and armor you have, the faster it will degenerate - but when you pick them up the degeneration stops momentarily. Because of this, you can't just load up on health and armor and head off into battle - you need to constantly replenish to remain strong. On the converse, though, when your health is below 100 it will slowly regenerate (armor will not). The lower it is, the faster it will replenish. This regeneration stops for a few seconds if you are hurt, so just running away after a fight is not usually enough to restore you to useful levels again - you have to wait a while to regain your full health, and so picking up health after a fight is still important.

-In regard to the previous point of working a map to control items, and also shooting at where your opponent will be, it's important to try to predict where on the map your opponents are even when you can't directly see them. Firstly, try to listen for them; secondly, learn the maps so you know the most useful routes and the most popular areas to which people tend to gravitate. It gives you a marked advantage to be able to say, "I saw or heard someone pick up an armor, so they must be at such-and-such location, and from there he can only go to this place or that place." Just shooting something to the likely places your opponent will be - a rocket or a grenade for example - can often result in either a surprise kill, or in surprise damage which you can then follow up. Don't underestimate how likely a well-aimed educated guess is to hit someone. Maybe he'll walk into it - rockets can be hard to see coming, and grenades lying on the floor or spammed into a small space can be really hard to avoid. Once you've hurt him you'll hear the tink of your damage, and you will also know for sure were he is and can finish him off. You can pretty much 'lock down' a map by picking up items, and making sure others have a hard time getting around which will also mess with their concentration. +In regard to the previous point of working a map to control items, and also shooting at where your opponent will be, it's important to try to predict where on the map your opponents are even when you can't directly see them. First, try to listen for them; secondly, learn the maps so you know the most useful routes and the most popular areas to which people tend to gravitate. It gives you a marked advantage to be able to say, "I saw or heard someone pick up an armor, so they must be at such-and-such location, and from there he can only go to this place or that place." Just shooting something to the likely places your opponent will be - a rocket or a grenade for example - can often result in either a surprise kill, or in surprise damage which you can then follow up. Don't underestimate how likely a well-aimed educated guess is to hit someone. Maybe he'll walk into it - rockets can be hard to see coming, and grenades lying on the floor or spammed into a small space can be really hard to avoid. Once you've hurt him you'll hear the tink of your damage, and you will also know for sure were he is and can finish him off. You can pretty much 'lock down' a map by picking up items, and making sure others have a hard time getting around which will also mess with their concentration.

Using Weapons Effectively

@@ -234,7 +234,7 @@ Now that you know all the guns you also have to understand that in Nexuiz each p You will need to be able to get around the map quickly and efficiently if you are to hunt down your opponents, and collect items well. As already mentioned, the laser is useful for jumping and gaining speed - but you can use all explosive weapons for a similar effect. Other weapons, however, will cause more damage to your health, and not all offer the same strong push as the laser. The rocket launcher will push you twice as far, but you will also take lots more damage. The mortar has the same push as the laser, but its explosion is bigger so the timing and the right angle are less of an issue. For this reason, the mortar is better for a beginner to learn these sorts of jumps.

-You should also know that you gain a bit extra speed moving forward simply by jumping. When you walk, there is friction from the floor which keeps you at a constant speed. If you jump as you walk, the amount you touch the floor is considerably less, and so the friction does not slow you down as much. The longer you jump, the faster you go. The most simple way to do this is to run forward, jump, and then while you are in the air release and repress the jump button. Your character will jump again the moment you land, without you having to worry about timing hitting the jump key. Unlike other games, Nexuiz does not need you to do this with strange key combinations or even with circular movements of the mouse - trying to do these will generally make you slower. The only time that strafe-jumping (holding one of the strafe-keys while jumping) is useful is for the very first jump you make. This can increase your jump speed a little - although not by much. +You should also know that you gain a bit extra speed moving forward simply by jumping. When you walk, there is friction from the floor which keeps you at a constant speed. If you jump as you walk, the amount you touch the floor is considerably less, and so the friction does not slow you down as much. The longer you jump, the faster you go. The most simple way to do this is to run forward, jump, and then while you are in the air release and repress/hold the jump button. Your character will jump again the moment you land, without you having to worry about timing hitting the jump key. Just repeat it a few times and you will notice you get faster and faster. Unlike other games, Nexuiz does not need you to do this with strange key combinations or even with circular movements of the mouse - trying to do these will generally make you slower. The only time that strafe-jumping (holding one of the strafe-keys while jumping) is useful is for the very first jump you make. This can increase your jump speed a little - although not by much.

Another useful thing to know about movement is that if you run and jump, you can release the forward key and instead press the left or right key, and turn your mouse in the same direction. Doing so will turn or 'bend' your jump in this direction. This is a useful trick to get around corners, to become less predictable, and also to avoid having to stop jumping to change direction. @@ -284,14 +284,19 @@ There are some more of these mutators, but those are the most important ones.

A list of useful links:

diff --git a/data/effectinfo.txt b/data/effectinfo.txt index d71edfde8..36add68ad 100644 --- a/data/effectinfo.txt +++ b/data/effectinfo.txt @@ -600,7 +600,7 @@ velocitymultiplier 1 // sparks (quake effect) effect TE_SPARK -count 1000 +count 1 type spark color 0x8f4333 0xfff31b size 0.4 0.4 @@ -2289,15 +2289,3 @@ liquidfriction 16 velocityjitter 512 512 512 -// sparks -effect func_sparks -count 1000 -type spark -color 0x8f4333 0xfff31b -size 0.4 0.4 -alpha 0 256 768 -gravity 1 -bounce -1 -velocityoffset 0 0 80 -velocityjitter 64 64 64 -velocitymultiplier 1 diff --git a/data/particles/laserbeam.tga b/data/particles/laserbeam.tga index 5d8b0816ea982bbbcd60d0ffb1e952c576c41d4d..88acafaab9682af1ad79e83dc2c31b1b7f27cf3a 100644 GIT binary patch literal 24459 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 diff --git a/data/qcsrc/client/Main.qc b/data/qcsrc/client/Main.qc index 739aa2977..30958102c 100644 --- a/data/qcsrc/client/Main.qc +++ b/data/qcsrc/client/Main.qc @@ -67,6 +67,11 @@ void CSQC_Init(void) registercmd("sbar_columns_set"); registercmd("sbar_columns_help"); + registercmd("+button3"); + registercmd("-button3"); + registercmd("+button4"); + registercmd("-button4"); + registercvar("sbar_usecsqc", "1"); registercvar("sbar_columns", "default", CVAR_SAVE); @@ -197,6 +202,7 @@ void PostInit(void) // CSQC_ConsoleCommand : Used to parse commands in the console that have been registered with the "registercmd" function // Return value should be 1 if CSQC handled the command, otherwise return 0 to have the engine handle it. +float button_zoom; void Cmd_Sbar_SetFields(float); void Cmd_Sbar_Help(float); float CSQC_ConsoleCommand(string strMessage) @@ -210,11 +216,19 @@ float CSQC_ConsoleCommand(string strMessage) local string strCmd; strCmd = argv(0); - /*if(strCmd == "ctf_menu") { - ctf_menu_show(); - nReturn = true; - } else*/ - if(strCmd == "ons_map") { + if(strCmd == "+button4") { // zoom + button_zoom = 1; + return true; + } else if(strCmd == "-button4") { // zoom + button_zoom = 0; + return true; + } else if(strCmd == "+button3") { // secondary + button_attack2 = 1; + return false; + } else if(strCmd == "-button3") { // secondary + button_attack2 = 0; + return false; + } else if(strCmd == "ons_map") { Cmd_ons_map(); return true; } else if(strCmd == "sbar_columns_set") { @@ -600,6 +614,7 @@ void Net_ReadInit() { csqc_revision = ReadShort(); maxclients = ReadByte(); + minstagib = ReadByte(); CSQC_CheckRevision(); } @@ -723,6 +738,12 @@ void Net_ReadSpectating() spectatee_status = newspectatee_status; } +void Net_ReadSpawn() +{ + zoomin_effect = 1; + current_viewzoom = 0.6; +} + // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer. // You must ALWAYS first acquire the temporary ID, which is sent as a byte. // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event. @@ -769,6 +790,10 @@ float CSQC_Parse_TempEntity() Net_GrapplingHook(); bHandled = true; break; + case TE_CSQC_SPAWN: // TE_BEAM + Net_ReadSpawn(); + bHandled = true; + break; default: // No special logic for this temporary entity; return 0 so the engine can handle it bHandled = false; diff --git a/data/qcsrc/client/View.qc b/data/qcsrc/client/View.qc index f262cf9cd..72c143b98 100644 --- a/data/qcsrc/client/View.qc +++ b/data/qcsrc/client/View.qc @@ -2,6 +2,68 @@ float drawtime; +float tan(float x) +{ + return sin(x) / cos(x); +} +float atan2(float y, float x) +{ + vector v; + v = '1 0 0' * x + '0 1 0' * y; + v = vectoangles(v); + return v_y * 0.01745329251994329576; +} + +vector GetCurrentFov() +{ + float zoomspeed, zoomfactor, zoomdir; + + zoomfactor = cvar("cl_zoomfactor"); + if(zoomfactor < 1 || zoomfactor > 16) + zoomfactor = 2.5; + zoomspeed = cvar("cl_zoomspeed"); + if(zoomspeed >= 0) + if(zoomspeed < 0.5 || zoomspeed > 16) + zoomspeed = 3.5; + + zoomdir = button_zoom; + if(activeweapon == 7 && !minstagib) + zoomdir += button_attack2; + + if(zoomdir) + zoomin_effect = 0; + + if(zoomin_effect) + { + current_viewzoom = min(1, current_viewzoom + drawframetime); + } + else + { + if(zoomspeed < 0) // instant zoom + { + if(zoomdir) + current_viewzoom = 1 / zoomfactor; + else + current_viewzoom = 1; + } + else + { + if(zoomdir) + current_viewzoom = 1 / bound(1, 1 / current_viewzoom + drawframetime * zoomspeed * (zoomfactor - 1), zoomfactor); + else + current_viewzoom = bound(1 / zoomfactor, current_viewzoom + drawframetime * zoomspeed * (1 - 1 / zoomfactor), 1); + } + } + + float frustumx, frustumy, fovx, fovy; + frustumy = tan(cvar("fov") * 0.00872664625997164788) * 0.75 * current_viewzoom; + frustumx = frustumy * cvar("vid_width") / cvar("vid_height") / cvar("vid_pixelheight"); + fovx = atan2(frustumx, 1) / 0.00872664625997164788; + fovy = atan2(frustumy, 1) / 0.00872664625997164788; + + return '1 0 0' * fovx + '0 1 0' * fovy; +} + void CSQC_common_hud(void); void CSQC_kh_hud(void); @@ -37,6 +99,8 @@ void CSQC_UpdateView(void) // Assign Standard Viewflags // Draw the World (and sky) R_SetView(VF_DRAWWORLD, 1); + + R_SetView(VF_FOV, GetCurrentFov()); // Draw the Crosshair R_SetView(VF_DRAWCROSSHAIR, !Sbar_WouldDrawScoreboard()); diff --git a/data/qcsrc/client/hook.qc b/data/qcsrc/client/hook.qc index d240d1855..a568e612c 100644 --- a/data/qcsrc/client/hook.qc +++ b/data/qcsrc/client/hook.qc @@ -2,18 +2,14 @@ .vector HookEnd; .float HookKillTime; -void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, vector rgb, float drawflag) +void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float alpha, float drawflag) { // I want to draw a quad... // from and to are MIDPOINTS. - float t; vector axis, thickdir, A, B, C, D; float length_tex; - t = -2 * time; - t = random(); - axis = normalize(to - from); length_tex = aspect * vlen(to - from) / thickness; @@ -26,10 +22,10 @@ void Draw_CylindricLine(vector from, vector to, float thickness, string texture, D = to - thickdir * (thickness / 2); R_BeginPolygon(texture, drawflag); - R_PolygonVertex(A, '0 0 0' + t * '1 0 0', rgb, 1); - R_PolygonVertex(B, '0 1 0' + t * '1 0 0', rgb, 1); - R_PolygonVertex(C, '0 1 0' + (t + length_tex) * '1 0 0', rgb, 1); - R_PolygonVertex(D, '0 0 0' + (t + length_tex) * '1 0 0', rgb, 1); + R_PolygonVertex(A, '0 0 0' + shift * '1 0 0', rgb, alpha); + R_PolygonVertex(B, '0 1 0' + shift * '1 0 0', rgb, alpha); + R_PolygonVertex(C, '0 1 0' + (shift + length_tex) * '1 0 0', rgb, alpha); + R_PolygonVertex(D, '0 0 0' + (shift + length_tex) * '1 0 0', rgb, alpha); R_EndPolygon(); } @@ -72,9 +68,9 @@ void Draw_GrapplingHook() rgb = '.3 1 .3'; } if(checkextension("DP_SV_WRITEPICTURE")) - Draw_CylindricLine(b, a, 8, tex, 0.25, '1 1 1', DRAWFLAG_NORMAL); + Draw_CylindricLine(b, a, 8, tex, 0.25, random(), '1 1 1', 1, DRAWFLAG_NORMAL); else - Draw_CylindricLine(b, a, 1, "", 0.25, rgb, DRAWFLAG_NORMAL); + Draw_CylindricLine(b, a, 1, "", 0.25, 0, rgb, 1, DRAWFLAG_NORMAL); } void Net_GrapplingHook() diff --git a/data/qcsrc/client/laser.qc b/data/qcsrc/client/laser.qc index 73c899d1c..07768aabe 100644 --- a/data/qcsrc/client/laser.qc +++ b/data/qcsrc/client/laser.qc @@ -1,17 +1,95 @@ +// FIXME make this generic code, to be used for other entities too? +.vector iorigin1, iorigin2; +.vector ivelocity1, ivelocity2; +.vector iangles1, iangles2; +.float itime1, itime2; +void InterpolateOrigin_Note() +{ + float dt; + + self.iorigin1 = self.iorigin2; + self.iangles1 = self.iangles2; + self.ivelocity1 = self.ivelocity2; + + self.iorigin2 = self.origin; + self.iangles2 = self.angles; + self.ivelocity2 = self.velocity; + + dt = time - self.itime1; + + if(vlen(self.iorigin2 - self.iorigin1) > 1000) + { + self.iorigin1 = self.iorigin2; + self.iangles1 = self.iangles2; + self.ivelocity1 = self.ivelocity2; + } + else if(vlen(self.ivelocity2 - self.ivelocity1) > 1000) + { + self.iangles1 = self.iangles2; + self.ivelocity1 = self.ivelocity2; + } + + if(dt < 0.2) + { + self.itime1 = time; + self.itime2 = time + getstatf(STAT_SYS_TICRATE); + } + else + { + // don't lerp + self.itime1 = self.itime2 = time; + } +} +void InterpolateOrigin_Do() +{ + if(self.itime1 && self.itime2 && self.itime1 != self.itime2) + { + float f; + f = bound(0, (time - self.itime1) / (self.itime2 - self.itime1), 1); + self.origin = (1 - f) * self.iorigin1 + f * self.iorigin2; + self.angles = (1 - f) * self.iangles1 + f * self.iangles2; + self.velocity = (1 - f) * self.ivelocity1 + f * self.ivelocity2; + } +} +void InterpolateOrigin_Undo() +{ + self.origin = self.iorigin2; + self.angles = self.iangles2; + self.velocity = self.ivelocity2; +} + // a laser goes from origin in direction angles // it has color 'colormod' // and stops when something is in the way .float cnt; // end effect .vector colormod; .float state; // on-off +.float count; // flags for the laser +.vector velocity; +.float alpha; void Draw_Laser() { if(!self.state) return; - makevectors(self.angles); - traceline(self.origin, self.origin + v_forward * 32768, 0, self); - Draw_CylindricLine(self.origin, trace_endpos, 2, "particles/laserbeam", 1, self.colormod, DRAWFLAG_ADDITIVE); // TODO make a texture to make the laser look smoother + InterpolateOrigin_Do(); + if(self.count & 0x80) + { + traceline(self.origin, self.velocity, 0, self); + } + else + { + makevectors(self.angles); + traceline(self.origin, self.origin + v_forward * 32768, 0, self); + } + if(self.alpha) + { + Draw_CylindricLine(self.origin, trace_endpos, 2, "particles/laserbeam", 0, time * 3, self.colormod, self.alpha, DRAWFLAG_NORMAL); // TODO make a texture to make the laser look smoother + } + else + { + Draw_CylindricLine(self.origin, trace_endpos, 2, "particles/laserbeam", 0, time * 3, self.colormod, 1, DRAWFLAG_ADDITIVE); // TODO make a texture to make the laser look smoother + } pointparticles(self.cnt, trace_endpos, trace_plane_normal, 256 * drawframetime); } @@ -20,6 +98,8 @@ void Ent_Laser() float f; // 30 bytes, or 13 bytes for just moving f = ReadByte(); + self.count = (f & 0xC0); + InterpolateOrigin_Undo(); if(f & 1) { self.origin_x = ReadCoord(); @@ -31,14 +111,28 @@ void Ent_Laser() self.colormod_x = ReadByte() / 255.0; self.colormod_y = ReadByte() / 255.0; self.colormod_z = ReadByte() / 255.0; + if(f & 0x40) + self.alpha = ReadByte() / 255.0; + else + self.alpha = 0; self.cnt = ReadShort(); // effect number } if(f & 2) { - self.angles_x = ReadCoord(); - self.angles_y = ReadCoord(); + if(f & 0x80) + { + self.velocity_x = ReadCoord(); + self.velocity_y = ReadCoord(); + self.velocity_z = ReadCoord(); + } + else + { + self.angles_x = ReadCoord(); + self.angles_y = ReadCoord(); + } } if(f & 4) self.state = ReadByte(); + InterpolateOrigin_Note(); self.draw = Draw_Laser; } diff --git a/data/qcsrc/client/main.qh b/data/qcsrc/client/main.qh index 2021c2b13..93220afad 100644 --- a/data/qcsrc/client/main.qh +++ b/data/qcsrc/client/main.qh @@ -113,3 +113,10 @@ entity teamslots[17]; // 17 teams (including "spectator team") .void(void) draw; float drawframetime; vector view_origin, view_angles, view_forward, view_right, view_up; + +float button_zoom; +float button_attack2; +float activeweapon; +float minstagib; +float current_viewzoom; +float zoomin_effect; diff --git a/data/qcsrc/client/sbar.qc b/data/qcsrc/client/sbar.qc index b4a7f0592..e125fc4cd 100644 --- a/data/qcsrc/client/sbar.qc +++ b/data/qcsrc/client/sbar.qc @@ -1,6 +1,5 @@ float last_weapon; -float activeweapon; float weapontime; float sb_lines; // still don't know what to do with that NOTE: check dp's sbar.c to see what that should be diff --git a/data/qcsrc/common/constants.qh b/data/qcsrc/common/constants.qh index 6f2aebacb..342988224 100644 --- a/data/qcsrc/common/constants.qh +++ b/data/qcsrc/common/constants.qh @@ -197,9 +197,11 @@ const float TE_CSQC_SCORESINFO = 108; const float TE_CSQC_RACE = 109; const float TE_CSQC_FORCESCOREBOARD = 110; const float TE_CSQC_SPECTATING = 111; +const float TE_CSQC_SPAWN = 112; const float STAT_KH_KEYS = 32; const float STAT_CTF_STATE = 33; +const float STAT_SYS_TICRATE = 34; const float CTF_STATE_ATTACK = 1; const float CTF_STATE_DEFEND = 2; const float CTF_STATE_COMMANDER = 3; diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index 6dc498cc2..f3a73a21b 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -473,7 +473,6 @@ void PutObserverInServer (void) self.punchvector = '0 0 0'; self.oldvelocity = self.velocity; self.customizeentityforclient = Client_customizeentityforclient; - self.viewzoom = 1; self.wantswelcomemessage = 1; if(g_arena) @@ -680,8 +679,11 @@ void PutClientInServer (void) self.punchvector = '0 0 0'; self.oldvelocity = self.velocity; - self.viewzoom = 0.6; - self.has_zoomed = 0; + msg_entity = self; + WRITESPECTATABLE_MSG_ONE({ + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_SPAWN); + }); self.customizeentityforclient = Client_customizeentityforclient; @@ -772,6 +774,7 @@ void SendCSQCInfo(void) WriteByte(MSG_ONE, TE_CSQC_INIT); WriteShort(MSG_ONE, CSQC_REVISION); WriteByte(MSG_ONE, maxclients); + WriteByte(MSG_ONE, g_minstagib); } /* @@ -1668,7 +1671,6 @@ void SpectateCopy(entity spectatee) { self.punchangle = spectatee.punchangle; self.view_ofs = spectatee.view_ofs; self.v_angle = spectatee.v_angle; - self.viewzoom = spectatee.viewzoom; self.velocity = spectatee.velocity; self.dmg_take = spectatee.dmg_take; self.dmg_save = spectatee.dmg_save; @@ -1922,6 +1924,7 @@ void() ctf_setstatus; .float spectatee_status; void PlayerPreThink (void) { + self.stat_sys_ticrate = cvar("sys_ticrate"); if(blockSpectators) checkSpectatorBlock(); @@ -2118,53 +2121,6 @@ void PlayerPreThink (void) W_WeaponFrame(); - { - float zoomfactor, zoomspeed, zoomdir; - zoomfactor = self.cvar_cl_zoomfactor; - if(zoomfactor < 1 || zoomfactor > 16) - zoomfactor = 2.5; - zoomspeed = self.cvar_cl_zoomspeed; - if(zoomspeed >= 0) // < 0 is instant zoom - if(zoomspeed < 0.5 || zoomspeed > 16) - zoomspeed = 3.5; - - zoomdir = self.BUTTON_ZOOM; - if(self.BUTTON_ATCK2) - if(self.weapon == WEP_NEX) - if(!g_minstagib) - zoomdir = 1; - - if(zoomdir) - self.has_zoomed = 1; - - if(self.has_zoomed) - { - if(zoomspeed <= 0) // instant zoom - { - if(zoomdir) - self.viewzoom = 1 / zoomfactor; - else - self.viewzoom = 1; - } - else - { - // geometric zoom would be: - // self.viewzoom = bound(1 / zoomfactor, self.viewzoom * pow(zoomfactor, (zoomdir ? -1 : 1) * frametime * zoomspeed), 1); - // however, testing showed that arithmetic/harmonic zoom works better - if(zoomdir) - // self.viewzoom = 1 / bound(1, 1 / self.viewzoom + (zoomdir ? 1 : -1) * frametime * zoomspeed * (zoomfactor - 1), zoomfactor); - // zoom in = arithmetic: 1x, 2x, 3x, 4x, ..., 8x - self.viewzoom = 1 / bound(1, 1 / self.viewzoom + frametime * zoomspeed * (zoomfactor - 1), zoomfactor); - else - // self.viewzoom = bound(1 / zoomfactor, self.viewzoom + (zoomdir ? -1 : 1) * frametime * zoomspeed * (1 - 1 / zoomfactor), 1); - // zoom out = harmonic: 8/1x, 8/2x, 8/3x, 8/4x, ..., 8/8x - self.viewzoom = bound(1 / zoomfactor, self.viewzoom + frametime * zoomspeed * (1 - 1 / zoomfactor), 1); - } - } - else - self.viewzoom = min(1, self.viewzoom + frametime); // spawn zoom-in - } - player_powerups(); player_regen(); player_anim(); diff --git a/data/qcsrc/server/ctf.qc b/data/qcsrc/server/ctf.qc index f2d1690c6..471baa2ee 100644 --- a/data/qcsrc/server/ctf.qc +++ b/data/qcsrc/server/ctf.qc @@ -662,8 +662,6 @@ void ctf_spawnteams() void ctf_delayedinit() { - self.think = SUB_Remove; - self.nextthink = time; // if no teams are found, spawn defaults if (find(world, classname, "ctf_team") == world) ctf_spawnteams(); @@ -673,11 +671,7 @@ void ctf_delayedinit() void ctf_init() { - local entity e; - - e = spawn(); - e.think = ctf_delayedinit; - e.nextthink = time + 0.1; + InitializeEntity(world, ctf_delayedinit, INITPRIO_GAMETYPE); flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"))); }; diff --git a/data/qcsrc/server/defs.qh b/data/qcsrc/server/defs.qh index 8add124c5..26b737d6e 100644 --- a/data/qcsrc/server/defs.qh +++ b/data/qcsrc/server/defs.qh @@ -28,6 +28,7 @@ float sv_foginterval; entity activator; string string_null; +const var void(void) func_null; float player_count; float currentbots; @@ -489,3 +490,4 @@ float independent_players; #define MAKE_INDEPENDENT_PLAYER(e) ((e).solid = SOLID_TRIGGER) string clientstuff; +.float stat_sys_ticrate; diff --git a/data/qcsrc/server/domination.qc b/data/qcsrc/server/domination.qc index 714f9b60e..9d210052f 100644 --- a/data/qcsrc/server/domination.qc +++ b/data/qcsrc/server/domination.qc @@ -560,8 +560,6 @@ void dom_delayedinit() { local entity head; - self.think = SUB_Remove; - self.nextthink = time; // if no teams are found, spawn defaults if (find(world, classname, "dom_team") == world) dom_spawnteams(); @@ -592,7 +590,6 @@ void dom_delayedinit() void dom_init() { - local entity e; // we have to precache default models/sounds even if they might not be // used because spawnfunc_worldspawn is executed before any other entities are read, // so we don't even know yet if this map is set up for domination... @@ -602,9 +599,7 @@ void dom_init() precache_model("models/domination/dom_pink.md3"); precache_model("models/domination/dom_unclaimed.md3"); precache_sound("domination/claim.wav"); - e = spawn(); - e.think = dom_delayedinit; - e.nextthink = time + 0.1; + InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE); // teamplay is always on in domination, defaults to hurt self but not teammates //if(!cvar("teamplay")) diff --git a/data/qcsrc/server/g_lights.qc b/data/qcsrc/server/g_lights.qc index 9af723115..ce4acc620 100644 --- a/data/qcsrc/server/g_lights.qc +++ b/data/qcsrc/server/g_lights.qc @@ -33,11 +33,13 @@ void dynlight_think() if(!self.owner) remove(self); - self.nextthink = 0.1; + self.nextthink = time + 0.1; }; void dynlight_find_aiment() { local entity targ; + if (!self.target) + objerror ("dynlight: no target to follow"); targ = find(world, targetname, self.target); self.movetype = MOVETYPE_FOLLOW; @@ -46,19 +48,33 @@ void dynlight_find_aiment() self.punchangle = targ.angles; self.view_ofs = self.origin - targ.origin; self.v_angle = self.angles - targ.angles; - self.nextthink = 0.1; self.think = dynlight_think; + self.nextthink = time + 0.1; }; void dynlight_find_path() { local entity targ; + if (!self.target) + objerror ("dynlight: no target to follow"); targ = find(world, targetname, self.target); self.target = targ.target; setorigin (self, targ.origin); - self.nextthink = self.ltime + 0.1; self.think = train_next; + self.nextthink = time + 0.1; }; +void dynlight_find_target() +{ + local entity targ; + if (!self.target) + objerror ("dynlight: no target to follow"); + + targ = find(world, targetname, self.target); + setattachment(self, targ, self.dtagname); + self.owner = targ; + self.think = dynlight_think; + self.nextthink = time + 0.1; +} void dynlight_use() { if (self.light_lev == 0) @@ -89,22 +105,14 @@ void spawnfunc_dynlight() //tag attaching if (self.dtagname) { - if (!self.target) - objerror ("dynlight: no target to follow"); - targ = find(world, targetname, self.target); - setattachment(self, targ, self.dtagname); - self.owner = targ; - self.think = dynlight_think; + InitializeEntity(self, dynlight_find_target, INITPRIO_FINDTARGET); return; } // entity following if (self.spawnflags & DFOLLOW) { - if (!self.target) - objerror ("dynlight: no target to follow"); - self.nextthink = time + 0.1; - self.think = dynlight_find_aiment; + InitializeEntity(self, dynlight_find_aiment, INITPRIO_FINDTARGET); return; } // path following @@ -114,7 +122,7 @@ void spawnfunc_dynlight() self.movetype = MOVETYPE_PUSH; if (!self.speed) self.speed = 100; - self.nextthink = self.ltime + 0.1; - self.think = dynlight_find_path; + InitializeEntity(self, dynlight_find_path, INITPRIO_FINDTARGET); + return; } }; diff --git a/data/qcsrc/server/g_triggers.qc b/data/qcsrc/server/g_triggers.qc index a65c13968..71f8c79ff 100644 --- a/data/qcsrc/server/g_triggers.qc +++ b/data/qcsrc/server/g_triggers.qc @@ -598,7 +598,7 @@ void spawnfunc_func_sparks() self.mins = '0 0 0'; self.maxs = '0 0 0'; self.velocity = '0 0 -1'; - self.mdl = "func_sparks"; + self.mdl = "TE_SPARK"; self.impulse = 0.1 / self.wait; self.wait = 0; @@ -710,16 +710,35 @@ void misc_laser_aim() vector a; if(self.enemy) { - a = vectoangles(self.enemy.origin - self.origin); - a_x = -a_x; - if(a != self.angles) + if(self.spawnflags & 2) { - self.angles = a; - self.SendFlags |= 2; + if(self.enemy.origin != self.mangle) + { + self.mangle = self.enemy.origin; + self.SendFlags |= 2; + } + } + else + { + a = vectoangles(self.enemy.origin - self.origin); + a_x = -a_x; + if(a != self.mangle) + { + self.mangle = a; + self.SendFlags |= 2; + } } } if(self.origin != self.oldorigin) + { self.SendFlags |= 1; + self.oldorigin = self.origin; + } +} + +void misc_laser_init() +{ + self.enemy = find(world, targetname, self.target); } void misc_laser_think() @@ -728,19 +747,14 @@ void misc_laser_think() self.nextthink = time; - if(!self.count) - { - self.enemy = find(world, targetname, self.target); - self.count = 1; - } - if(!self.state) return; misc_laser_aim(); - makevectors(self.angles); - o = self.origin + 32768 * v_forward; + o = self.enemy.origin; + if not(self.spawnflags & 2) + o = self.origin + normalize(o - self.origin) * 32768; if(self.dmg) { @@ -754,6 +768,11 @@ void misc_laser_think() float laser_SendEntity(entity to, float fl) { WriteByte(MSG_ENTITY, ENT_CLIENT_LASER); + fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser + if(self.spawnflags & 2) + fl |= 0x80; + if(self.alpha) + fl |= 0x40; WriteByte(MSG_ENTITY, fl); if(fl & 1) { @@ -766,19 +785,30 @@ float laser_SendEntity(entity to, float fl) WriteByte(MSG_ENTITY, self.colormod_x * 255.0); WriteByte(MSG_ENTITY, self.colormod_y * 255.0); WriteByte(MSG_ENTITY, self.colormod_z * 255.0); + if(fl & 0x40) + WriteByte(MSG_ENTITY, self.alpha * 255.0); WriteShort(MSG_ENTITY, self.cnt); } if(fl & 2) { - WriteCoord(MSG_ENTITY, self.angles_x); - WriteCoord(MSG_ENTITY, self.angles_y); + if(fl & 0x80) + { + WriteCoord(MSG_ENTITY, self.enemy.origin_x); + WriteCoord(MSG_ENTITY, self.enemy.origin_y); + WriteCoord(MSG_ENTITY, self.enemy.origin_z); + } + else + { + WriteCoord(MSG_ENTITY, self.mangle_x); + WriteCoord(MSG_ENTITY, self.mangle_y); + } } if(fl & 4) WriteByte(MSG_ENTITY, self.state); return 1; } -/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON +/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED Any object touching the beam will be hurt Keys: "target" @@ -810,11 +840,13 @@ void spawnfunc_misc_laser() self.cnt = particleeffectnum("misc_laser_beam_end"); } if(self.colormod == '0 0 0') - self.colormod = '1 0 0'; + if(!self.alpha) + self.colormod = '1 0 0'; if(!self.message) self.message = "saw the light"; self.think = misc_laser_think; self.nextthink = time; + InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET); self.effects = EF_NODEPTHTEST; self.SendEntity = laser_SendEntity; @@ -1066,7 +1098,7 @@ void spawnfunc_trigger_monoflop() self.state = 0; } -void follow_think() +void follow_init() { entity src, dst; src = find(world, targetname, self.killtarget); @@ -1089,6 +1121,5 @@ void follow_think() void spawnfunc_misc_follow() { - self.think = follow_think; - self.nextthink = time; + InitializeEntity(self, follow_init, INITPRIO_FINDTARGET); } diff --git a/data/qcsrc/server/g_world.qc b/data/qcsrc/server/g_world.qc index 110ddd4bf..bc9e6891f 100644 --- a/data/qcsrc/server/g_world.qc +++ b/data/qcsrc/server/g_world.qc @@ -365,6 +365,8 @@ void spawnfunc_worldspawn (void) } } + addstat(STAT_SYS_TICRATE, AS_FLOAT, stat_sys_ticrate); + next_pingtime = time + 5; world_initialized = 1; } diff --git a/data/qcsrc/server/miscfunctions.qc b/data/qcsrc/server/miscfunctions.qc index 8c0be7967..f621f05f4 100644 --- a/data/qcsrc/server/miscfunctions.qc +++ b/data/qcsrc/server/miscfunctions.qc @@ -1190,3 +1190,73 @@ float ExactTriggerHit() // WARNING: this kills the trace globals #define EXACTTRIGGER_TOUCH if not(ExactTriggerHit()) return #define EXACTTRIGGER_INIT InitSolidBSPTrigger(); self.solid = SOLID_TRIGGER + +#define INITPRIO_FIRST 0 +#define INITPRIO_GAMETYPE 0 +#define INITPRIO_GAMETYPE_FALLBACK 1 +#define INITPRIO_FINDTARGET 10 +#define INITPRIO_SETLOCATION 90 +#define INITPRIO_LAST 99 + +.void(void) initialize_entity; +.float initialize_entity_order; +.entity initialize_entity_next; +entity initialize_entity_first; +void InitializeEntity(entity e, void(void) func, float order) +{ + entity prev, cur; + + if(!e || e.initialize_entity) + { + // make a proxy initializer entity + entity e_old; + e_old = e; + e = spawn(); + e.classname = "initialize_entity"; + e.enemy = e_old; + } + + e.initialize_entity = func; + e.initialize_entity_order = order; + + cur = initialize_entity_first; + for(;;) + { + if(!cur || cur.initialize_entity_order > order) + { + // insert between prev and cur + if(prev) + prev.initialize_entity_next = e; + else + initialize_entity_first = e; + e.initialize_entity_next = cur; + return; + } + prev = cur; + cur = cur.initialize_entity_next; + } +} +void InitializeEntitiesRun() +{ + for(self = initialize_entity_first; self; ) + { + entity e; + var void(void) func; + e = self.initialize_entity_next; + func = self.initialize_entity; + self.initialize_entity_order = 0; + self.initialize_entity = func_null; + self.initialize_entity_next = world; + if(self.classname == "initialize_entity") + { + entity e_old; + e_old = self.enemy; + remove(self); + self = e_old; + } + dprint("Delayed initialization: ", self.classname, "\n"); + func(); + self = e; + } + initialize_entity_first = world; +} diff --git a/data/qcsrc/server/sv_main.qc b/data/qcsrc/server/sv_main.qc index d547be831..ff980a4ff 100644 --- a/data/qcsrc/server/sv_main.qc +++ b/data/qcsrc/server/sv_main.qc @@ -136,6 +136,8 @@ void StartFrame (void) if(RedirectionThink()) return; + InitializeEntitiesRun(); + sv_maxairspeed = cvar("sv_maxairspeed"); sv_maxspeed = cvar ("sv_maxspeed"); sv_friction = cvar ("sv_friction"); diff --git a/data/qcsrc/server/t_items.qc b/data/qcsrc/server/t_items.qc index 6f68d67be..eb5d82897 100644 --- a/data/qcsrc/server/t_items.qc +++ b/data/qcsrc/server/t_items.qc @@ -683,7 +683,7 @@ void spawnfunc_item_armor_medium (void) { self.armorvalue = g_pickup_armormedium; if(!self.max_armorvalue) self.max_armorvalue = g_pickup_armormedium_max; - StartItem ("models/items/g_armormedium.md3", "misc/armor1.wav", 30, "25 Armor", IT_ARMOR, 0, commodity_pickupevalfunc, 20000); + StartItem ("models/items/g_armormedium.md3", "misc/armor1.wav", 20, "25 Armor", IT_ARMOR, 0, commodity_pickupevalfunc, 20000); } void spawnfunc_item_armor_large (void) { @@ -715,7 +715,7 @@ void spawnfunc_item_health_large (void) { self.max_health = g_pickup_healthlarge_max; if(!self.health) self.health = g_pickup_healthlarge; - StartItem ("models/items/g_h50.md3", "misc/mediumhealth.wav", 15, "50 Health", IT_25HP, 0, commodity_pickupevalfunc, 20000); + StartItem ("models/items/g_h50.md3", "misc/mediumhealth.wav", 20, "50 Health", IT_25HP, 0, commodity_pickupevalfunc, 20000); } void spawnfunc_item_health_mega (void) { diff --git a/data/qcsrc/server/t_jumppads.qc b/data/qcsrc/server/t_jumppads.qc index ada452bdb..c50afb1b6 100644 --- a/data/qcsrc/server/t_jumppads.qc +++ b/data/qcsrc/server/t_jumppads.qc @@ -255,8 +255,7 @@ void spawnfunc_trigger_push() precache_sound (self.noise); // this must be called to spawn the teleport waypoints for bots - self.think = trigger_push_findtarget; - self.nextthink = time + 0.2; + InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET); }; void spawnfunc_target_push() {}; diff --git a/data/qcsrc/server/t_plats.qc b/data/qcsrc/server/t_plats.qc index e95215be2..3bcaf46a5 100644 --- a/data/qcsrc/server/t_plats.qc +++ b/data/qcsrc/server/t_plats.qc @@ -344,6 +344,8 @@ void func_train_find() local entity targ; targ = find(world, targetname, self.target); self.target = targ.target; + if (!self.target) + objerror("func_train_find: no next target"); setorigin(self, targ.origin - self.mins); self.nextthink = self.ltime + 1; self.think = train_next; @@ -365,8 +367,7 @@ void spawnfunc_func_train() self.effects |= EF_LOWPRECISION; // wait for targets to spawn - self.nextthink = self.ltime + 0.1; - self.think = func_train_find; + InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION); }; @@ -1126,8 +1127,7 @@ void spawnfunc_func_door() // LinkDoors can't be done until all of the doors have been spawned, so // the sizes can be detected properly. - self.think = LinkDoors; - self.nextthink = self.ltime + 0.1; + InitializeEntity(self, LinkDoors, INITPRIO_FINDTARGET); }; /* diff --git a/data/qcsrc/server/t_teleporters.qc b/data/qcsrc/server/t_teleporters.qc index a063efcf2..c3441b2d7 100644 --- a/data/qcsrc/server/t_teleporters.qc +++ b/data/qcsrc/server/t_teleporters.qc @@ -129,8 +129,8 @@ void spawnfunc_trigger_teleport (void) EXACTTRIGGER_INIT; - self.think = teleport_findtarget; - self.nextthink = time + 0.2; + // this must be called to spawn the teleport waypoints for bots + InitializeEntity(self, teleport_findtarget, INITPRIO_FINDTARGET); if (!self.target) objerror ("Teleporter with no target"); diff --git a/data/qcsrc/server/teamplay.qc b/data/qcsrc/server/teamplay.qc index f70a7ccb3..33e1fbd57 100644 --- a/data/qcsrc/server/teamplay.qc +++ b/data/qcsrc/server/teamplay.qc @@ -142,8 +142,6 @@ void ReadGameCvars() void default_delayedinit() { - remove(self); - if(!scores_initialized) ScoreRules_generic(); } @@ -333,10 +331,7 @@ void InitGameplayMode() cvar_set("fraglimit", "0"); } - entity e; - e = spawn(); - e.nextthink = time + 0.3; // MUST be after all other delayed inits! - e.think = default_delayedinit; + InitializeEntity(world, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK); } string GetClientVersionMessage() { @@ -1323,8 +1318,6 @@ void tdm_spawnteams() void tdm_delayedinit() { - self.think = SUB_Remove; - self.nextthink = time; // if no teams are found, spawn defaults if (find(world, classname, "tdm_team") == world) tdm_spawnteams(); @@ -1332,10 +1325,5 @@ void tdm_delayedinit() void tdm_init() { - local entity e; - e = spawn(); - e.think = tdm_delayedinit; - e.nextthink = time + 0.1; + InitializeEntity(world, tdm_delayedinit, INITPRIO_GAMETYPE); }; - - diff --git a/data/scripts/entities.def b/data/scripts/entities.def index c999718c3..bcf0948ce 100644 --- a/data/scripts/entities.def +++ b/data/scripts/entities.def @@ -283,7 +283,7 @@ model="models/items/g_a25.md3" /*QUAKED item_armor_medium (.4 .8 .4) (-30 -30 0) (30 30 32) FLOATING Medium Armor (default 25 armor points) -------- KEYS -------- -respawntime: time till it respawns (default: 30) +respawntime: time till it respawns (default: 20) armorvalue: amount of armor it gives (default: 25 (g_pickup_armormedium)) max_armorvalue: max of armor it increases to (default: 999 (g_pickup_armormedium_max)) -------- SPAWNFLAGS -------- @@ -351,7 +351,7 @@ noise3: sound played when flag is lost in the field and respawns itself /*QUAKED item_health_large (.9 .3 .3) (-30 -30 0) (30 30 48) FLOATING Large Health (default 50 health points) -------- KEYS -------- -respawntime: time till it respawns (default: 15) +respawntime: time till it respawns (default: 20) health: amount of health it gives (default: 50 (g_pickup_healthlarge)) max_health: max of health it increases to (default: 999 (g_pickup_healthlarge_max)) -------- SPAWNFLAGS -------- @@ -496,7 +496,7 @@ LINEAR: Use a linear falloff. Default is inverse distance squared (more realisti NOANGLE: Ignore angle attenuation. */ -/*QUAKED misc_laser (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON +/*QUAKED misc_laser (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON FINITE Laser beam emitter -------- KEYS -------- target: target_position the laser targets (may be another entity, the laser will then target its origin. Use origin brushes, that is!) @@ -504,8 +504,10 @@ mdl: name of particle effect for the beam end point (see effectinfo.txt; default colormod: color of the laser beam (default: red, that is, 1 0 0) dmg: damage inflicted by the beam per second, or -1 for an instant-death ray targetname: name to target this (then its state is toggled) +alpha: when set, makes a dark laser of the given strength; may be combined with colormod -------- SPAWNFLAGS -------- START_ON: when targeted, the laser will start switched on +FINITE: the laser does not extend over its target like light would do, but stops there (takes more bandwidth) -------- NOTES -------- Use trigger_monoflop if you want the laser to turn off for a while, then turn back on */ -- 2.39.2