.float strength_finished; // NOTE: this field is used only by map entities, it does not directly apply the strength stat
.float invincible_finished; // ditto
-#define spawnfunc_body(item) \
- if (!Item_IsDefinitionAllowed(item)) \
+#define SPAWNFUNC_BODY(item) \
+ if (item && Item_IsDefinitionAllowed(item)) \
+ StartItem(this, item); \
+ else \
{ \
startitem_failed = true; \
delete(this); \
- return; \
- } \
- StartItem(this, item)
+ }
#define SPAWNFUNC_ITEM(name, item) \
spawnfunc(name) \
{ \
- spawnfunc_body(item); \
+ SPAWNFUNC_BODY(item) \
}
#define SPAWNFUNC_ITEM_COND(name, cond, item1, item2) \
- spawnfunc(name) \
- { \
- entity item = (cond) ? item1 : item2; \
- spawnfunc_body(item); \
- }
+ SPAWNFUNC_ITEM(name, (cond ? item1 : item2))
#else
#include "ammo.qh"
-#ifdef SVQC
-
-METHOD(Shells, m_spawnfunc_hookreplace, GameItem(Shells this, entity e))
-{
- if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
- {
- return ITEM_Bullets;
- }
- return this;
-}
-
-#endif
ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
ATTRIB(Powerup, m_respawntimejitter, float(), GET(g_pickup_respawntimejitter_powerup));
+ ATTRIB(Powerup, count, float);
#endif
ENDCLASS(Powerup)
void powerup_strength_init(Pickup this, entity item)
{
if(!item.strength_finished)
- item.strength_finished = autocvar_g_balance_powerup_strength_time;
+ item.strength_finished = (item.count) ? item.count : autocvar_g_balance_powerup_strength_time;
}
#endif
REGISTER_ITEM(Strength, Powerup) {
void powerup_shield_init(Pickup this, entity item)
{
if(!item.invincible_finished)
- item.invincible_finished = autocvar_g_balance_powerup_invincible_time;
+ item.invincible_finished = (item.count) ? item.count : autocvar_g_balance_powerup_invincible_time;
}
#endif
REGISTER_ITEM(Shield, Powerup) {
if (!this.lip)
this.lip = 4;
- if(this.wait == -1 && autocvar_sv_q3defragcompat)
- this.wait = 0.1; // compatibility for q3df: "instant" return
+ if(this.wait < 0 && q3compat)
+ this.wait = 0.1; // compatibility for q3: -1 = return immediately
if(this.noise != "")
precache_sound(this.noise);
}
// TODO: other soundpacks
- if (this.sounds > 0)
+ if (this.sounds > 0 || q3compat)
{
+ // Doors in Q3 always have sounds (they're hard coded in Q3 engine)
this.noise2 = "plats/medplat1.wav";
this.noise1 = "plats/medplat2.wav";
}
+ if (q3compat)
+ {
+ // CPMA adds these fields for overriding the engine sounds
+ string s = GetField_fullspawndata(this, "sound_start", true);
+ string e = GetField_fullspawndata(this, "sound_end", true);
+
+ if (s)
+ this.noise2 = strzone(s);
+ if (e)
+ this.noise1 = strzone(e);
+ }
+
// sound when door stops moving
if(this.noise1 && this.noise1 != "")
{
}
else if (!this.speed)
{
- if (autocvar_sv_q3defragcompat)
+ if (q3compat)
this.speed = 400;
else
this.speed = 100;
this.noise1 = "plats/plat2.wav";
}
- if (this.sounds == 2)
+ if (this.sounds == 2 || q3compat)
{
+ // Plats in Q3 always have sounds (they're hard coded in Q3 engine)
this.noise = "plats/medplat1.wav";
this.noise1 = "plats/medplat2.wav";
}
if (this.sound2)
this.noise1 = this.sound2;
+ if (q3compat)
+ {
+ // CPMA adds these fields for overriding the engine sounds
+ string s = GetField_fullspawndata(this, "sound_start", true);
+ string e = GetField_fullspawndata(this, "sound_end", true);
+
+ if (s)
+ this.noise = strzone(s);
+ if (e)
+ this.noise1 = strzone(e);
+ }
+
if(this.noise && this.noise != "")
{
precache_sound(this.noise);
.float triggerhurttime;
void trigger_hurt_touch(entity this, entity toucher)
{
+ if (!toucher.takedamage)
+ return;
if (this.active != ACTIVE_ACTIVE)
return;
// only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
if (toucher.iscreature)
{
- if (toucher.takedamage)
- if (toucher.triggerhurttime < time)
+ if (time >= toucher.triggerhurttime + ((q3compat && !(this.spawnflags & HURT_SLOW)) ? autocvar_sys_ticrate : 1))
{
EXACTTRIGGER_TOUCH(this, toucher);
- toucher.triggerhurttime = time + ((autocvar_sv_q3defragcompat && !(this.spawnflags & HURT_SLOW)) ? 0.1 : 1);
+ toucher.triggerhurttime = time;
entity own;
own = this.enemy;
}
else if(toucher.damagedbytriggers)
{
- if(toucher.takedamage)
- {
- EXACTTRIGGER_TOUCH(this, toucher);
- Damage(toucher, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0');
- }
+ EXACTTRIGGER_TOUCH(this, toucher);
+ Damage(toucher, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0');
}
-
- return;
}
/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
this.use = trigger_hurt_use;
this.enemy = world; // I hate you all
if (!this.dmg)
- this.dmg = ((autocvar_sv_q3defragcompat) ? 5 : 10000);
+ this.dmg = ((q3compat) ? 5 : 10000);
if (this.message == "")
this.message = "was in the wrong place";
if (this.message2 == "")
return false;
vector org = targ.origin;
-#ifdef SVQC
- if(autocvar_sv_q3defragcompat)
-#elif defined(CSQC)
- if(STAT(Q3DEFRAGCOMPAT))
-#endif
+
+ if(STAT(Q3COMPAT))
{
org.z += targ.mins_z;
org.z += 1; // off by 1!
{ // we can't just delete(this) here, because this is a touch function
// called while C code is looping through area links...
settouch(this, func_null);
+ this.use = func_null;
}
}
setthink(this, func_null);
this.nextthink = 0;
this.team = this.team_saved;
+ this.use = multi_use;
}
/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
this.wait = 0;
this.use = multi_use;
- if(this.wait == -1 && autocvar_sv_q3defragcompat)
+ if(this.wait == -1 && (q3compat & Q3COMPAT_DEFI))
this.wait = 0.1; // compatibility for q3df: "instant" return
EXACTTRIGGER_INIT;
{
switch(buffname)
{
- case "ammoregen": return "ammo";
- case "haste": case "scout": return "speed";
- case "guard": return "resistance";
- case "revival": case "regen": return "medic";
- case "invis": return "invisible";
- case "jumper": return "jump";
+ case "ammoregen": return "ammo"; // Q3TA ammoregen
+ case "haste": return "speed"; // Q3A haste
+ case "doubler": return "inferno"; // Q3TA doubler
+ case "scout": return "bash"; // Q3TA scout
+ case "guard": return "resistance"; // Q3TA guard
+ case "revival": case "regen": return "medic"; // WOP revival, Q3A regen
+ case "invis": return "invisible"; // Q3A invis
+ case "jumper": return "jump"; // WOP jumper
+ case "invulnerability": return "vampire"; // Q3TA invulnerability
+ case "kamikaze": return "vengeance"; // Q3TA kamikaze
+ case "teleporter": return "swapper"; // Q3A personal teleporter
default: return buffname;
}
}
this.m_color = '0.76 1 0.1';
}
BUFF_SPAWNFUNCS(ammo, BUFF_AMMO)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO)
+BUFF_SPAWNFUNC_Q3COMPAT(item_ammoregen, BUFF_AMMO)
REGISTER_BUFF(RESISTANCE) {
this.m_name = _("Resistance");
this.m_color = '0.36 1 0.07';
}
BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(guard, BUFF_RESISTANCE)
+BUFF_SPAWNFUNC_Q3COMPAT(item_guard, BUFF_RESISTANCE)
REGISTER_BUFF(SPEED) {
this.m_name = _("Speed");
this.m_color = '0.1 1 0.84';
}
BUFF_SPAWNFUNCS(speed, BUFF_SPEED)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(haste, BUFF_SPEED)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(scout, BUFF_SPEED)
+BUFF_SPAWNFUNC_Q3COMPAT(item_haste, BUFF_SPEED)
REGISTER_BUFF(MEDIC) {
this.m_name = _("Medic");
this.m_color = '1 0.12 0';
}
BUFF_SPAWNFUNCS(medic, BUFF_MEDIC)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(regen, BUFF_MEDIC)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(revival, BUFF_MEDIC)
+BUFF_SPAWNFUNC_Q3COMPAT(item_regen, BUFF_MEDIC)
+BUFF_SPAWNFUNC_Q3COMPAT(item_revival, BUFF_MEDIC)
REGISTER_BUFF(BASH) {
this.m_name = _("Bash");
this.m_color = '1 0.39 0';
}
BUFF_SPAWNFUNCS(bash, BUFF_BASH)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(doubler, BUFF_BASH)
+BUFF_SPAWNFUNC_Q3COMPAT(item_scout, BUFF_BASH)
REGISTER_BUFF(VAMPIRE) {
this.m_name = _("Vampire");
this.m_color = '1 0 0.24';
}
BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE)
+BUFF_SPAWNFUNC_Q3COMPAT(holdable_invulnerability, BUFF_VAMPIRE)
REGISTER_BUFF(DISABILITY) {
this.m_name = _("Disability");
this.m_color = '1 0.23 0.61';
}
BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE)
+BUFF_SPAWNFUNC_Q3COMPAT(holdable_kamikaze, BUFF_VENGEANCE)
REGISTER_BUFF(JUMP) {
this.m_name = _("Jump");
this.m_color = '0.24 0.78 1';
}
BUFF_SPAWNFUNCS(jump, BUFF_JUMP)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(jumper, BUFF_JUMP)
+BUFF_SPAWNFUNC_Q3COMPAT(item_jumper, BUFF_JUMP)
REGISTER_BUFF(INVISIBLE) {
this.m_name = _("Invisible");
this.m_color = '0.5 0.5 1';
}
BUFF_SPAWNFUNCS(invisible, BUFF_INVISIBLE)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE)
+BUFF_SPAWNFUNC_Q3COMPAT(item_invis, BUFF_INVISIBLE)
REGISTER_BUFF(INFERNO) {
this.m_name = _("Inferno");
this.m_color = '1 0.62 0';
}
BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO)
+BUFF_SPAWNFUNC_Q3COMPAT(item_doubler, BUFF_INFERNO)
REGISTER_BUFF(SWAPPER) {
this.m_name = _("Swapper");
this.m_color = '0.63 0.36 1';
}
BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER)
+BUFF_SPAWNFUNC_Q3COMPAT(holdable_teleporter, BUFF_SWAPPER)
REGISTER_BUFF(MAGNET) {
this.m_name = _("Magnet");
this.m_color = '0.23 0.44 1';
}
BUFF_SPAWNFUNCS(flight, BUFF_FLIGHT)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(flight, BUFF_FLIGHT)
+BUFF_SPAWNFUNC_Q3COMPAT(item_flight, BUFF_FLIGHT)
void buff_Init_Compat(entity ent, entity replacement);
#define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \
STAT(BUFFS, this) = b.m_itemid; \
- this.team = t; \
+ if(teamplay) \
+ this.team_forced = t; \
buff_Init(this); \
}
#define BUFF_SPAWNFUNCS(e, b) \
BUFF_SPAWNFUNC(e##_team2, b, NUM_TEAM_2) \
BUFF_SPAWNFUNC(e##_team3, b, NUM_TEAM_3) \
BUFF_SPAWNFUNC(e##_team4, b, NUM_TEAM_4)
- #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) spawnfunc(item_##o) { buff_Init_Compat(this, r); }
+ #define BUFF_SPAWNFUNC_Q3COMPAT(o, r) spawnfunc(o) { buff_Init_Compat(this, r); }
#else
#define BUFF_SPAWNFUNC(e, b, t)
#define BUFF_SPAWNFUNCS(e, b)
- #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r)
+ #define BUFF_SPAWNFUNC_Q3COMPAT(o, r)
#endif
string Buff_UndeprecateName(string buffname);
vector buff_GlowColor(entity buff)
{
- //if(buff.team) { return Team_ColorRGB(buff.team); }
+ //if(buff.team_forced) { return Team_ColorRGB(buff.team_forced); }
return buff.m_color;
}
if(autocvar_g_buffs_waypoint_distance <= 0) return;
entity buff = buff_FirstFromFlags(STAT(BUFFS, e));
- entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team, e, buff_waypoint, true, RADARICON_Buff);
+ entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team_forced, e, buff_waypoint, true, RADARICON_Buff);
wp.wp_extra = buff.m_id;
WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod);
e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
if(!IS_PLAYER(toucher))
return; // incase mutator changed toucher
- if((this.team && DIFF_TEAM(toucher, this))
+ if((this.team_forced && toucher.team != this.team_forced)
|| (STAT(FROZEN, toucher))
|| (toucher.vehicle)
|| (time < PS(toucher).buff_shield)
if(this.buff_active)
{
- if(this.team && !this.buff_waypoint)
+ if(this.team_forced && !this.buff_waypoint)
buff_Waypoint_Spawn(this);
if(this.lifetime && time >= this.lifetime)
bool buff_Customize(entity this, entity client)
{
entity player = WaypointSprite_getviewentity(client);
- if(!this.buff_active || (this.team && DIFF_TEAM(player, this)))
+ if(!this.buff_active || (this.team_forced && player.team != this.team_forced))
{
this.alpha = 0.3;
if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); }
{
if(!cvar("g_buffs")) { delete(this); return; }
- if(!teamplay && this.team) { this.team = 0; }
-
entity buff = buff_FirstFromFlags(STAT(BUFFS, this));
if(!STAT(BUFFS, this) || !buff_Available(buff))
void buff_Init_Compat(entity ent, entity replacement)
{
- if (ent.spawnflags & 2)
- ent.team = NUM_TEAM_1;
- else if (ent.spawnflags & 4)
- ent.team = NUM_TEAM_2;
+ if (teamplay)
+ {
+ if (ent.spawnflags & 2)
+ ent.team_forced = NUM_TEAM_1;
+ else if (ent.spawnflags & 4)
+ ent.team_forced = NUM_TEAM_2;
+ }
STAT(BUFFS, ent) = replacement.m_itemid;
player.alpha = ((autocvar_g_buffs_invisible_alpha) ? autocvar_g_buffs_invisible_alpha : -1); // powerups reset alpha, so we must enforce this (TODO)
if(STAT(BUFFS, player) & BUFF_MEDIC.m_itemid)
- if(time >= player.buff_medic_healtime)
+ if(teamplay && time >= player.buff_medic_healtime)
{
buff_Medic_Heal(player);
player.buff_medic_healtime = time + autocvar_g_buffs_medic_heal_delay;
.float buff_effect_delay;
// buff definitions
-.float buff_active;
+.bool buff_active;
.float buff_activetime;
-.float buff_activetime_updated;
+.bool buff_activetime_updated;
.entity buff_waypoint;
.int oldbuffs; // for updating effects
.float buff_shield; // delay for players to keep them from spamming buff pickups
: 0;
STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod;
}
- bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox; // NOTE: these hitboxes are off by 1 due to engine differences
- STAT(PL_MIN, this) = (q3dfcompat) ? '-15 -15 -20' : autocvar_sv_player_mins;
- STAT(PL_MAX, this) = (q3dfcompat) ? '15 15 36' : autocvar_sv_player_maxs;
- STAT(PL_VIEW_OFS, this) = (q3dfcompat) ? '0 0 30' : autocvar_sv_player_viewoffset;
- STAT(PL_CROUCH_MIN, this) = (q3dfcompat) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins;
- STAT(PL_CROUCH_MAX, this) = (q3dfcompat) ? '15 15 20' : autocvar_sv_player_crouch_maxs;
- STAT(PL_CROUCH_VIEW_OFS, this) = (q3dfcompat) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset;
+ bool q3hb = q3compat && autocvar_sv_q3compat_changehitbox;
+ STAT(PL_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_mins;
+ STAT(PL_MAX, this) = (q3hb) ? '15 15 36' : autocvar_sv_player_maxs;
+ STAT(PL_VIEW_OFS, this) = (q3hb) ? '0 0 30' : autocvar_sv_player_viewoffset;
+ STAT(PL_CROUCH_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins;
+ STAT(PL_CROUCH_MAX, this) = (q3hb) ? '15 15 20' : autocvar_sv_player_crouch_maxs;
+ STAT(PL_CROUCH_VIEW_OFS, this) = (q3hb) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset;
// old stats
// fix some new settings
#ifdef SVQC
#include <server/autocvars.qh>
#include <server/client.qh>
+#include <server/compat/quake3.qh>
#include <common/mapobjects/trigger/secret.qh>
#endif
#endif
REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
-#ifdef SVQC
-bool autocvar_sv_q3defragcompat;
-#endif
-REGISTER_STAT(Q3DEFRAGCOMPAT, bool, autocvar_sv_q3defragcompat)
+REGISTER_STAT(Q3COMPAT, int, q3compat)
#ifdef SVQC
#include "physics/movetypes/movetypes.qh"
}
}
+entity GetAmmoItem(int ammotype)
+{
+ switch (ammotype)
+ {
+ case RES_SHELLS: return ITEM_Shells;
+ case RES_BULLETS: return ITEM_Bullets;
+ case RES_ROCKETS: return ITEM_Rockets;
+ case RES_CELLS: return ITEM_Cells;
+ case RES_PLASMA: return ITEM_Plasma;
+ case RES_FUEL: return ITEM_JetpackFuel;
+ }
+ LOG_WARNF("Invalid ammo type %d ", ammotype);
+ return NULL;
+ // WEAPONTODO: use this generic func to reduce duplication ?
+ // GetAmmoPicture GetAmmoName notif_arg_item_wepammo ammo_pickupevalfunc ?
+}
+
#ifdef CSQC
int GetAmmoTypeFromNum(int i)
{
spawnfunc(name) { weapon_defaultspawnfunc(this, weapon); }
#define SPAWNFUNC_WEAPON_COND(name, cond, wep1, wep2) \
- spawnfunc(name) \
- { \
- entity wep = (cond) ? wep1 : wep2; \
- weapon_defaultspawnfunc(this, wep); \
- }
+ SPAWNFUNC_WEAPON(name, (cond ? wep1 : wep2))
#else
string GetAmmoName(int ammotype);
+entity GetAmmoItem(int ammotype);
+
#ifdef CSQC
int GetAmmoTypeFromNum(int i);
int GetAmmoStat(int ammotype);
REGISTER_NET_TEMP(TE_CSQC_SHOCKWAVEPARTICLE)
#ifdef SVQC
-// enable when shockwave replaces shotgun
-#if 0
-METHOD(Shockwave, m_spawnfunc_hookreplace, Weapon(Shockwave this, entity e))
-{
- //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO
- if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
- {
- return WEP_MACHINEGUN;
- }
- return this;
-}
-#endif
const float MAX_SHOCKWAVE_HITS = 10;
//#define DEBUG_SHOCKWAVE
// enable to debug melee range
//#define SHOTGUN_MELEEDEBUG
-METHOD(Shotgun, m_spawnfunc_hookreplace, Weapon(Shotgun this, entity e))
-{
- if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
- {
- return WEP_MACHINEGUN;
- }
- return this;
-}
-
void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force, entity bullet_trail_effect)
{
W_DecreaseAmmo(thiswep, actor, ammocount, weaponentity);
ENDCLASS(Shotgun)
REGISTER_WEAPON(SHOTGUN, shotgun, NEW(Shotgun));
-SPAWNFUNC_WEAPON(weapon_shotgun, WEP_SHOTGUN)
noref bool require_spawnfunc_prefix;
.bool spawnfunc_checked;
/** Not for production use, provides access to a dump of the entity's fields when it is parsed from map data */
-//noref string __fullspawndata;
+noref string __fullspawndata;
+.string fullspawndata;
// Optional type checking; increases compile time too much to be enabled by default
#if 0
FIELD_SCALAR(fld, noise2) \
FIELD_SCALAR(fld, noise3) \
FIELD_SCALAR(fld, noise) \
+ FIELD_SCALAR(fld, notcpm) \
+ FIELD_SCALAR(fld, notfree) \
+ FIELD_SCALAR(fld, notta) \
+ FIELD_SCALAR(fld, notteam) \
+ FIELD_SCALAR(fld, notvq3) \
FIELD_SCALAR(fld, phase) \
FIELD_SCALAR(fld, platmovetype) \
FIELD_SCALAR(fld, race_place) \
this.classname = #id; \
if (!this.spawnfunc_checked) { \
_checkWhitelisted(this, #id); \
+ if (__fullspawndata) { \
+ /* not supported in old DP */ \
+ /* must be read inside the real spawnfunc */ \
+ this.fullspawndata = __fullspawndata; \
+ } \
this.spawnfunc_checked = true; \
if (this) { \
/* not worldspawn, delay spawn */ \
bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher)
{
vector emin = toucher.absmin, emax = toucher.absmax;
- // the engine offsets absolute bounding boxes by a single quake unit
- // we must undo that here to allow accurate touching
- emin += '1 1 1';
- emax -= '1 1 1';
+ if(STAT(Q3COMPAT))
+ {
+ // DP's tracebox enlarges absolute bounding boxes by a single quake unit
+ // we must undo that here to allow accurate touching
+ emin += '1 1 1';
+ emax -= '1 1 1';
+ }
return !WarpZoneLib_BoxTouchesBrush(emin, emax, this, toucher);
}
string autocvar_sv_motd;
int autocvar_sv_name_maxlength = 64;
bool autocvar_sv_precacheplayermodels;
-bool autocvar_sv_q3acompat_machineshotgunswap;
bool autocvar_sv_servermodelsonly;
int autocvar_sv_spectate;
float autocvar_sv_spectator_speed_multiplier;
bool autocvar_g_weaponswitch_debug;
bool autocvar_g_weaponswitch_debug_alternate;
bool autocvar_g_allow_checkpoints;
-bool autocvar_sv_q3defragcompat_changehitbox = false;
+bool autocvar_sv_q3compat_changehitbox;
int autocvar_sv_clones;
bool autocvar_g_footsteps;
float autocvar_sv_maxidle;
this.respawn_flags = 0;
this.respawn_time = 0;
STAT(RESPAWN_TIME, this) = 0;
- bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox;
- this.scale = ((q3dfcompat) ? 0.9 : autocvar_sv_player_scale);
+ this.scale = ((q3compat && autocvar_sv_q3compat_changehitbox) ? 0.9 : autocvar_sv_player_scale);
this.fade_time = 0;
this.pain_finished = 0;
this.pushltime = 0;
#include <common/weapons/_all.qh>
#include <common/weapons/_all.qh>
-//***********************
-//QUAKE 1 ENTITIES - So people can play quake1 maps with the xonotic weapons
-//***********************
-SPAWNFUNC_WEAPON(weapon_nailgun, WEP_ELECTRO)
+/***********************
+ * QUAKE 1 ENTITIES - So people can play quake1 maps with the xonotic weapons
+ ***********************
+ weapon_nailgun handled in quake3.qc
+ item_armor1 handled in items.qc
+*/
+
SPAWNFUNC_WEAPON(weapon_supernailgun, WEP_HAGAR)
SPAWNFUNC_WEAPON(weapon_supershotgun, WEP_MACHINEGUN)
SPAWNFUNC_ITEM(item_spikes, ITEM_Bullets)
-//spawnfunc(item_armor1) {spawnfunc_item_armor_medium(this);} // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
SPAWNFUNC_ITEM(item_armor2, ITEM_ArmorMega)
SPAWNFUNC_ITEM(item_armorInv, ITEM_ArmorMega) // TODO: make sure we actually want this
SPAWNFUNC_ITEM_COND(item_health, (this.spawnflags & 2), ITEM_HealthMega, ITEM_HealthMedium)
#include <server/resources.qh>
#include <server/world.qh>
-//***********************
-//QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
-//***********************
+/***********************
+ * QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
+ ***********************
+
+ * Map entities NOT handled in this file:
+ holdable_invulnerability Q3TA buffs mutator
+ holdable_kamikaze Q3TA buffs mutator
+ holdable_teleporter Q3A buffs mutator
+ item_ammoregen Q3TA buffs mutator
+ item_doubler Q3TA buffs mutator
+ item_guard Q3TA buffs mutator
+ item_scout Q3TA buffs mutator
+ item_armor_jacket CPMA quake2.qc
+ item_flight Q3A buffs mutator
+ item_haste Q3A buffs mutator
+ item_health Q3A quake.qc
+ item_health_large Q3A items.qc
+ item_health_small Q3A health.qh
+ item_health_mega Q3A health.qh
+ item_invis Q3A buffs mutator
+ item_quad Q3A items.qc
+ item_regen Q3A buffs mutator
+ weapon_machinegun Q3A machinegun.qh
+ weapon_grenadelauncher Q3A mortar.qh
+ weapon_rocketlauncher Q3A devastator.qh
+ CTF spawnfuncs handled in sv_ctf.qc
+
+ NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
+*/
+
+// SG -> MG || SG
+SPAWNFUNC_Q3_COND(weapon_shotgun, ammo_shells, (q3compat & Q3COMPAT_ARENA), WEP_MACHINEGUN, WEP_SHOTGUN)
+
+// MG -> SG || MG
+// Technically we should replace weapon_machinegun with WEP_SHOTGUN if Q3COMPAT_ARENA, but it almost never occurs on Q3 maps
+SPAWNFUNC_Q3AMMO_COND(ammo_bullets, (q3compat & Q3COMPAT_ARENA), WEP_SHOTGUN, WEP_MACHINEGUN)
-// NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
+// GL -> Mortar
+SPAWNFUNC_Q3AMMO(ammo_grenades, WEP_MORTAR)
-// SG -> SG
-SPAWNFUNC_ITEM(ammo_shells, ITEM_Shells)
+// Team Arena Proximity Launcher -> Mortar
+// It's more accurate to spawn Mine Layer but players prefer Mortar, and weapon_grenadelauncher is usually disabled by "notta" and weapon_prox_launcher placed at the same origin
+SPAWNFUNC_Q3(weapon_prox_launcher, ammo_mines, WEP_MORTAR)
-// MG -> MG
-SPAWNFUNC_ITEM(ammo_bullets, ITEM_Bullets)
+// Team Arena Chaingun -> HLAC
+SPAWNFUNC_Q3(weapon_chaingun, ammo_belt, WEP_HLAC)
-// GL -> Mortar
-SPAWNFUNC_ITEM(ammo_grenades, ITEM_Rockets)
+// Quake Live Heavy Machine Gun -> HLAC
+SPAWNFUNC_Q3(weapon_hmg, ammo_hmg, WEP_HLAC)
-// Mines -> Rockets
-SPAWNFUNC_WEAPON(weapon_prox_launcher, WEP_MINE_LAYER)
-SPAWNFUNC_ITEM(ammo_mines, ITEM_Rockets)
+// Team Arena Nailgun -> Crylink || Quake Nailgun -> Electro
+SPAWNFUNC_Q3_COND(weapon_nailgun, ammo_nails, cvar("sv_mapformat_is_quake3"), WEP_CRYLINK, WEP_ELECTRO)
-// LG -> Lightning
-SPAWNFUNC_WEAPON(weapon_lightning, WEP_ELECTRO)
-SPAWNFUNC_ITEM(ammo_lightning, ITEM_Cells)
+// LG -> Electro
+SPAWNFUNC_Q3(weapon_lightning, ammo_lightning, WEP_ELECTRO)
// Plasma -> Hagar
-SPAWNFUNC_WEAPON(weapon_plasmagun, WEP_HAGAR)
-SPAWNFUNC_ITEM(ammo_cells, ITEM_Rockets)
+SPAWNFUNC_Q3(weapon_plasmagun, ammo_cells, WEP_HAGAR)
// Rail -> Vortex
-SPAWNFUNC_WEAPON(weapon_railgun, WEP_VORTEX)
-SPAWNFUNC_ITEM(ammo_slugs, ITEM_Cells)
+SPAWNFUNC_Q3(weapon_railgun, ammo_slugs, WEP_VORTEX)
-// BFG -> Crylink
-SPAWNFUNC_WEAPON(weapon_bfg, WEP_CRYLINK)
-SPAWNFUNC_ITEM(ammo_bfg, ITEM_Cells)
+// BFG -> Crylink || Fireball
+SPAWNFUNC_Q3_COND(weapon_bfg, ammo_bfg, cvar_string("g_mod_balance") == "XDF", WEP_CRYLINK, WEP_FIREBALL)
+ // FIXME: WEP_FIREBALL has no ammo_type field so ammo_bfg is deleted by SPAWNFUNC_BODY
// grappling hook -> hook
SPAWNFUNC_WEAPON(weapon_grapplinghook, WEP_HOOK)
// RL -> RL
-SPAWNFUNC_ITEM(ammo_rockets, ITEM_Rockets)
+SPAWNFUNC_Q3AMMO(ammo_rockets, WEP_DEVASTATOR)
+
+// Gauntlet -> Tuba
+SPAWNFUNC_ITEM(weapon_gauntlet, WEP_TUBA)
// Armor
SPAWNFUNC_ITEM(item_armor_body, ITEM_ArmorMega)
SPAWNFUNC_ITEM(item_armor_combat, ITEM_ArmorBig)
SPAWNFUNC_ITEM(item_armor_shard, ITEM_ArmorSmall)
+SPAWNFUNC_ITEM(item_armor_green, ITEM_ArmorMedium) // CCTF
+
+// Battle Suit
SPAWNFUNC_ITEM(item_enviro, ITEM_Shield)
// medkit -> armor (we have no holdables)
InitializeEntity(this, target_init_verify, INITPRIO_FINDTARGET);
}
-// weapon give ent from defrag
+// weapon give ent from Q3
void target_give_init(entity this)
{
IL_EACH(g_items, it.targetname == this.target,
{
- if (it.classname == "weapon_devastator") {
- SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(devastator, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "devastator");
- }
- else if (it.classname == "weapon_vortex") {
- SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(vortex, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "vortex");
- }
- else if (it.classname == "weapon_electro") {
- SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(electro, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "electro");
- }
- else if (it.classname == "weapon_hagar") {
- SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(hagar, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "hagar");
- }
- else if (it.classname == "weapon_crylink") {
- SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(crylink, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "crylink");
- }
- else if (it.classname == "weapon_mortar") {
- SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(mortar, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "mortar");
- }
- else if (it.classname == "weapon_shotgun") {
- SetResourceExplicit(this, RES_SHELLS, GetResource(this, RES_SHELLS) + it.count * WEP_CVAR_PRI(shotgun, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "shotgun");
- }
- else if (it.classname == "item_armor_mega")
- SetResourceExplicit(this, RES_ARMOR, 100);
- else if (it.classname == "item_health_mega")
- SetResourceExplicit(this, RES_HEALTH, 200);
- else if (it.classname == "item_buff") {
+ if (it.classname == "item_buff")
+ {
entity buff = buff_FirstFromFlags(STAT(BUFFS, it));
this.netname = cons(this.netname, buff.netname);
- STAT(BUFF_TIME, this) = it.count;
+ STAT(BUFF_TIME, this) += it.count;
+ }
+ else
+ {
+ if (it.ammo_rockets)
+ this.ammo_rockets += it.ammo_rockets;
+ else if (it.ammo_cells)
+ this.ammo_cells += it.ammo_cells;
+ else if (it.ammo_shells)
+ this.ammo_shells += it.ammo_shells;
+ else if (it.ammo_nails)
+ this.ammo_nails += it.ammo_nails;
+ else if (it.invincible_finished)
+ this.invincible_finished += it.invincible_finished;
+ else if (it.strength_finished)
+ this.strength_finished += it.strength_finished;
+ else if (it.health)
+ this.health += it.health;
+ else if (it.armorvalue)
+ this.armorvalue += it.armorvalue;
+
+ this.netname = cons(this.netname, it.netname);
}
//remove(it); // removing ents in init functions causes havoc, workaround:
- setthink(it, SUB_Remove);
- it.nextthink = time;
+ setthink(it, SUB_Remove);
+ it.nextthink = time;
});
this.spawnflags = 2;
this.spawnfunc_checked = true;
this.use = fragsfilter_use;
}
-//spawnfunc(item_flight) /* handled by buffs mutator */
-//spawnfunc(item_doubler) /* handled by buffs mutator */
-//spawnfunc(item_haste) /* handled by buffs mutator */
-//spawnfunc(item_health) /* handled in t_quake.qc */
-//spawnfunc(item_health_large) /* handled in items.qc */
-//spawnfunc(item_health_small) /* handled in items.qc */
-//spawnfunc(item_health_mega) /* handled in items.qc */
-//spawnfunc(item_invis) /* handled by buffs mutator */
-//spawnfunc(item_regen) /* handled by buffs mutator */
-
-// CTF spawnfuncs handled in mutators/gamemode_ctf.qc now
-
-.float notteam;
-.float notsingle;
-.float notfree;
-.float notq3a;
-.float notta;
+.bool notteam;
+.bool notsingle;
+.bool notfree;
+.bool notta;
+.bool notvq3;
+.bool notcpm;
.string gametype;
bool DoesQ3ARemoveThisEntity(entity this)
{
// Q3 style filters (DO NOT USE, THIS IS COMPAT ONLY)
- if(this.notq3a)
- if(!teamplay || g_tdm || g_ctf)
+ // DeFRaG mappers use "notcpm" or "notvq3" to disable an entity in CPM or VQ3 physics
+ // Xonotic is usually played with a CPM-based physics so we default to CPM mode
+ if(cvar_string("g_mod_physics") == "Q3")
+ {
+ if(this.notvq3)
return true;
+ }
+ else if(this.notcpm)
+ return true;
+ // Q3 mappers use "notq3a" or "notta" to disable an entity in Q3A or Q3TA
+ // Xonotic has ~equivalent features to Team Arena
if(this.notta)
- if (!(!teamplay || g_tdm || g_ctf))
- return true;
+ return true;
if(this.notsingle)
if(maxclients == 1)
if(this.gametype)
{
string gametypename;
- // static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester", "teamtournament"}
+ // From ioq3 g_spawn.c: static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester"};
gametypename = "ffa";
if(teamplay)
gametypename = "team";
gametypename = "tournament";
if(maxclients == 1)
gametypename = "single";
- // we do not have the other types (obelisk, harvester, teamtournament)
+ // we do not have the other types (obelisk, harvester)
if(strstrofs(this.gametype, gametypename, 0) < 0)
return true;
}
return false;
}
+
+int GetAmmoConsumptionQ3(string netname)
+// Returns ammo consumed per shot by the primary/default fire mode
+// Returns 0 if the netname has no ammo cvar
+{
+ switch (netname)
+ {
+ case "arc": return autocvar_g_balance_arc_beam_ammo;
+ case "devastator": return autocvar_g_balance_devastator_ammo;
+ case "machinegun": return autocvar_g_balance_machinegun_sustained_ammo;
+ case "minelayer": return autocvar_g_balance_minelayer_ammo;
+ case "seeker": return autocvar_g_balance_seeker_tag_ammo;
+ default: return cvar(strcat("g_balance_", netname, "_primary_ammo"));
+ }
+}
+
#pragma once
+int q3compat = 0;
+#define Q3COMPAT_ARENA BIT(0)
+#define Q3COMPAT_DEFI BIT(1)
+
bool DoesQ3ARemoveThisEntity(entity this);
+int GetAmmoConsumptionQ3(string netname);
.int fragsfilter_cnt;
+
+/* We tell the ammo spawnfunc which weapon will use the ammo so it can
+ * calculate the amount required for the number of shots in the count field,
+ * and so the type can be looked up rather than specified in quake3.qc
+ */
+// Ammo only, unconditional
+#define SPAWNFUNC_Q3AMMO(ammo_classname, xonwep) \
+ spawnfunc(ammo_classname) \
+ { \
+ if(this.count && xonwep.ammo_type) \
+ SetResource(this, xonwep.ammo_type, this.count * GetAmmoConsumptionQ3(xonwep.netname)); \
+ SPAWNFUNC_BODY(GetAmmoItem(xonwep.ammo_type)) \
+ }
+
+// Ammo only, conditional
+#define SPAWNFUNC_Q3AMMO_COND(ammo_classname, cond, xonwep1, xonwep0) \
+ SPAWNFUNC_Q3AMMO(ammo_classname, (cond ? xonwep1 : xonwep0))
+
+// Weapon & ammo, unconditional
+#define SPAWNFUNC_Q3(weapon_classname, ammo_classname, xonwep) \
+ SPAWNFUNC_WEAPON(weapon_classname, xonwep) \
+ SPAWNFUNC_Q3AMMO(ammo_classname, xonwep)
+
+// Weapon & ammo, conditional
+#define SPAWNFUNC_Q3_COND(weapon_classname, ammo_classname, cond, xonwep1, xonwep0) \
+ SPAWNFUNC_WEAPON_COND(weapon_classname, cond, xonwep1, xonwep0) \
+ SPAWNFUNC_Q3AMMO_COND(ammo_classname, cond, xonwep1, xonwep0)
+
if(autocvar_spawn_debug >= 2)
{
- // why not flags & fl_item?
- FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
- LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
- LOG_TRACE(" vs ", it.netname, vtos(it.origin));
- error("Mapper sucks.");
- });
+ // why not flags & fl_item?
+ FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
+ LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
+ LOG_TRACE(" vs ", it.netname, vtos(it.origin));
+ error("Mapper sucks.");
+ });
this.is_item = true;
}
weaponsInMap |= WepSet_FromWeapon(REGISTRY_GET(Weapons, weaponid));
- if ( def.instanceOfPowerup
- || def.instanceOfWeaponPickup
+ if ( def.instanceOfPowerup
+ || def.instanceOfWeaponPickup
|| (def.instanceOfHealth && def != ITEM_HealthSmall)
|| (def.instanceOfArmor && def != ITEM_ArmorSmall)
|| (itemid & (IT_KEY1 | IT_KEY2))
this.bot_pickupevalfunc = pickupevalfunc;
this.bot_pickupbasevalue = pickupbasevalue;
this.mdl = this.model ? this.model : strzone(this.item_model_ent.model_str());
- this.netname = itemname;
+ this.netname = (def.m_weapon) ? def.m_weapon.netname : def.netname;
settouch(this, Item_Touch);
setmodel(this, MDL_Null); // precision set below
//this.effects |= EF_LOWPRECISION;
void StartItem(entity this, GameItem def)
{
- def = def.m_spawnfunc_hookreplace(def, this);
- if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
- {
- delete(this);
- return;
- }
- this.classname = def.m_canonical_spawnfunc;
- _StartItem(
- this,
- this.itemdef = def,
- def.m_respawntime(), // defaultrespawntime
- def.m_respawntimejitter() // defaultrespawntimejitter
+ def = def.m_spawnfunc_hookreplace(def, this);
+
+ if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
+ {
+ delete(this);
+ return;
+ }
+
+ this.classname = def.m_canonical_spawnfunc;
+
+ _StartItem(
+ this,
+ this.itemdef = def,
+ def.m_respawntime(), // defaultrespawntime
+ def.m_respawntimejitter() // defaultrespawntimejitter
);
}
// Compatibility spawn functions
-// FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
-SPAWNFUNC_ITEM(item_armor1, ITEM_ArmorSmall)
+SPAWNFUNC_ITEM_COND(item_armor1, cvar("sv_mapformat_is_quake3"), ITEM_ArmorSmall, ITEM_ArmorMedium)
SPAWNFUNC_ITEM(item_armor25, ITEM_ArmorMega)
return;
}
- if (DoesQ3ARemoveThisEntity(this)) {
+ if (q3compat && DoesQ3ARemoveThisEntity(this)) {
delete(this);
return;
}
}
}
+string GetField_fullspawndata(entity e, string f, ...)
+/* Retrieves the value of a map entity field from fullspawndata
+ * This bypasses field value changes made by the engine,
+ * eg string-to-float and escape sequence substitution.
+ *
+ * Avoids the need to declare fields just to read them once :)
+ *
+ * Returns the last instance of the field to match DarkPlaces behaviour.
+ * Path support: converts \ to / and tests the file if a third (bool, true) arg is passed.
+ * Returns string_null if the entity does not have the field, or the file is not in the VFS.
+ *
+ * FIXME: entities with //comments are not supported.
+ */
+{
+ string v = string_null;
+
+ if (!e.fullspawndata)
+ {
+ LOG_WARNF("^1EDICT %s (classname %s) has no fullspawndata, engine lacks support?", ftos(num_for_edict(e)), e.classname);
+ return v;
+ }
+
+ if (strstrofs(e.fullspawndata, "//", 0) >= 0)
+ {
+ // tokenize and tokenize_console return early if "//" is reached,
+ // which can leave an odd number of tokens and break key:value pairing.
+ LOG_WARNF("^1EDICT %s fullspawndata contains unsupported //comment^7%s", ftos(num_for_edict(e)), e.fullspawndata);
+ return v;
+ }
+
+ //print(sprintf("%s(EDICT %s, FIELD %s)\n", __FUNC__, ftos(num_for_edict(e)), f));
+ //print(strcat("FULLSPAWNDATA:", e.fullspawndata, "\n"));
+
+ // tokenize treats \ as an escape, but tokenize_console returns the required literal
+ for (int t = tokenize_console(e.fullspawndata) - 3; t > 0; t -= 2)
+ {
+ //print(sprintf("\tTOKEN %s:%s\t%s:%s\n", ftos(t), ftos(t + 1), argv(t), argv(t + 1)));
+ if (argv(t) == f)
+ {
+ v = argv(t + 1);
+ break;
+ }
+ }
+
+ //print(strcat("RESULT: ", v, "\n\n"));
+
+ if (v && ...(0, bool) == true)
+ {
+ v = strreplace("\\", "/", v);
+ if (whichpack(v) == "")
+ return string_null;
+ }
+
+ return v;
+}
+
void WarpZone_PostInitialize_Callback()
{
// create waypoint links for warpzones
return false;
}
+void defrag_waypointsprites(entity targeted, entity checkpoint)
+{
+ for(entity t = findchain(target, targeted.targetname); t; t = t.chain)
+ {
+ if(t.modelindex)
+ {
+ entity s = WP_RaceStart;
+
+ if(checkpoint.classname == "target_checkpoint")
+ s = WP_RaceCheckpoint;
+ else if(checkpoint.classname == "target_stopTimer")
+ s = WP_RaceFinish;
+
+ vector o = (t.absmin + t.absmax) * 0.5;
+
+ WaypointSprite_SpawnFixed(s, o, t, sprite, RADARICON_NONE);
+
+ t.sprite.realowner = checkpoint;
+ t.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
+ }
+
+ if(t.targetname)
+ defrag_waypointsprites(t, checkpoint);
+ }
+}
+
void trigger_race_checkpoint_verify(entity this)
{
- static bool have_verified;
+ static bool have_verified;
if (have_verified) return;
have_verified = true;
pl_race_place = 0;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for respawning in race) - bailing out"));
- }
+ }
if (i == 0) {
// qualifying only
pl_race_place = race_lowest_place_spawn;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
- }
+ }
// race only (initial spawn)
g_race_qualifying = 0;
pl_race_place = p;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for initially spawning in race) - bailing out"));
- }
+ }
}
}
}
pl_race_place = race_lowest_place_spawn;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint 0 misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
- }
+ }
} else {
pl_race_checkpoint = race_NextCheckpoint(0);
g_race_qualifying = 1;
for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) {
if (argv(0) == cp.targetname) {
cp.race_checkpoint = stof(argv(1));
- }
- }
+ }
+ }
}
fclose(fh);
}
g_race_qualifying = qual;
- IL_EACH(g_race_targets, it.classname == "target_checkpoint" || it.classname == "target_startTimer" || it.classname == "target_stopTimer",
- {
- if(it.targetname == "" || !it.targetname) // somehow this is a case...
- continue;
- entity cpt = it;
- FOREACH_ENTITY_STRING(target, cpt.targetname,
- {
- vector org = (it.absmin + it.absmax) * 0.5;
- if(cpt.race_checkpoint == 0)
- WaypointSprite_SpawnFixed(WP_RaceStart, org, it, sprite, RADARICON_NONE);
- else
- WaypointSprite_SpawnFixed(WP_RaceCheckpoint, org, it, sprite, RADARICON_NONE);
-
- it.sprite.realowner = cpt;
- it.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
- });
- });
-
if (race_timed_checkpoint) {
if (defrag_ents) {
IL_EACH(g_race_targets, it.classname == "target_checkpoint" || it.classname == "target_startTimer" || it.classname == "target_stopTimer",
{
- entity cpt = it;
- if(it.classname == "target_startTimer" || it.classname == "target_stopTimer") {
- if(it.targetname == "" || !it.targetname) // somehow this is a case...
- continue;
- FOREACH_ENTITY_STRING(target, cpt.targetname, {
- if(it.sprite)
- WaypointSprite_UpdateSprites(it.sprite, ((cpt.classname == "target_startTimer") ? WP_RaceStart : WP_RaceFinish), WP_Null, WP_Null);
- });
- }
+ defrag_waypointsprites(it, it);
+
if(it.classname == "target_checkpoint") {
if(it.race_checkpoint == -2)
defragcpexists = -1; // something's wrong with the defrag cp file or it has not been written yet, set defragcpexists to -1 so that it will be rewritten when someone finishes
for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) {
if (cp.race_checkpoint > largest_cp_id) {
largest_cp_id = cp.race_checkpoint;
- }
- }
+ }
+ }
for (entity cp = NULL; (cp = find(cp, classname, "target_stopTimer"));) {
cp.race_checkpoint = largest_cp_id + 1; // finish line
- }
+ }
race_highest_checkpoint = largest_cp_id + 1;
race_timed_checkpoint = largest_cp_id + 1;
} else {
for (entity cp = NULL; (cp = find(cp, classname, "target_stopTimer"));) {
cp.race_checkpoint = 255; // finish line
- }
+ }
race_highest_checkpoint = 255;
race_timed_checkpoint = 255;
}
{
if (it.race_checkpoint == 0) {
WaypointSprite_UpdateSprites(it.sprite, WP_RaceStart, WP_Null, WP_Null);
- } else if (it.race_checkpoint == race_timed_checkpoint) {
+ } else if (it.race_checkpoint == race_timed_checkpoint) {
WaypointSprite_UpdateSprites(it.sprite, WP_RaceFinish, WP_Null, WP_Null);
}
- });
+ });
}
}
- if (defrag_ents) {
+ if (defrag_ents) { /* The following hack shall be removed when per-player trigger_multiple.wait is implemented for cts */
for (entity trigger = NULL; (trigger = find(trigger, classname, "trigger_multiple")); ) {
for (entity targ = NULL; (targ = find(targ, targetname, trigger.target)); ) {
if (targ.classname == "target_checkpoint" || targ.classname == "target_startTimer" || targ.classname == "target_stopTimer") {
/// \brief Indicates that the player is not allowed to join a team.
const int TEAM_NOT_ALLOWED = -1;
-.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
-
.int m_team_balance_state; ///< Holds the state of the team balance entity.
.entity m_team_balance_team[NUM_TEAMS]; ///< ???
bool lockteams;
+.int team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
+
// ========================== Global teams API ================================
/// \brief Returns the global team entity at the given index.
// if we don't already have ammo, give us some ammo
if ((wpn.ammo_type != RES_NONE) && !GetResource(this, wpn.ammo_type))
{
- switch (wpn.ammo_type)
+ int ammo = 0;
+ if (q3compat && this.count > 0)
+ ammo = this.count * GetAmmoConsumptionQ3(wpn.netname);
+ // WEAPONTODO: magazines of MG, rifle and OK weapons are unaccounted for
+ else
{
- case RES_SHELLS: SetResource(this, wpn.ammo_type, cvar("g_pickup_shells_weapon")); break;
- case RES_BULLETS: SetResource(this, wpn.ammo_type, cvar("g_pickup_nails_weapon")); break;
- case RES_ROCKETS: SetResource(this, wpn.ammo_type, cvar("g_pickup_rockets_weapon")); break;
- case RES_CELLS: SetResource(this, wpn.ammo_type, cvar("g_pickup_cells_weapon")); break;
- case RES_PLASMA: SetResource(this, wpn.ammo_type, cvar("g_pickup_plasma_weapon")); break;
- case RES_FUEL: SetResource(this, wpn.ammo_type, cvar("g_pickup_fuel_weapon")); break;
+ switch (wpn.ammo_type)
+ {
+ case RES_SHELLS: ammo = cvar("g_pickup_shells_weapon"); break;
+ case RES_BULLETS: ammo = cvar("g_pickup_nails_weapon"); break;
+ case RES_ROCKETS: ammo = cvar("g_pickup_rockets_weapon"); break;
+ case RES_CELLS: ammo = cvar("g_pickup_cells_weapon"); break;
+ case RES_PLASMA: ammo = cvar("g_pickup_plasma_weapon"); break;
+ case RES_FUEL: ammo = cvar("g_pickup_fuel_weapon"); break;
+ }
}
+
+ SetResource(this, wpn.ammo_type, ammo);
}
#if 0 // WEAPONTODO
MapInfo_Enumerate();
MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
- if(fexists(strcat("scripts/", mapname, ".arena")))
- cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
-
- if(fexists(strcat("scripts/", mapname, ".defi")))
- cvar_settemp("sv_q3defragcompat", "1");
+ q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
+ q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
{
sv_gameplayfix_gravityunaffectedbyticrate 1
sv_gameplayfix_nogravityonground 1
-set sv_q3acompat_machineshotgunswap 0 "shorthand for swapping machinegun and shotgun (for Q3A map compatibility in mapinfo files)"
-set sv_q3defragcompat 0 "toggle for some compatibility hacks (for Q3DF map compatibility)"
+set sv_q3compat_changehitbox 0 "use Q3 player hitbox dimensions and camera height on Q3 maps (maps with an entry in a .arena or .defi file)
set g_movement_highspeed 1 "multiplier scale for movement speed (applies to sv_maxspeed and sv_maxairspeed, also applies to air acceleration when g_movement_highspeed_q3_compat is set to 0)"
set g_movement_highspeed_q3_compat 0 "apply speed modifiers to air movement in a more Q3-compatible way (only apply speed buffs and g_movement_highspeed to max air speed, not to acceleration)"