set g_ctf_stalemate_endcondition 1 "condition for stalemate mode to be finished: 1 = If ONE flag is no longer stale, 2 = If BOTH flags are no longer stale"
set g_ctf_stalemate_time 60 "time for each flag until stalemate mode is activated"
set g_ctf_flagcarrier_waypointforenemy_spotting 1 "show the enemy flagcarrier location if a team mate presses +use to spot them"
-set g_ctf_dropped_capture_delay 1.5 "autocapture delay when flag is thrown onto the base - counted from throw, not landing"
+set g_ctf_dropped_capture_delay 1 "autocapture delay when flag is thrown onto the base - counted from when the flag lands on the ground"
set g_ctf_dropped_capture_radius 100 "allow dropped flags to be automatically captured by base flags if the dropped flag is within this radius of it"
set g_ctf_flag_damageforcescale 2
set g_ctf_portalteleport 0 "allow flag carriers to go through portals made in portal gun without dropping the flag"
set g_cts_finish_kill_delay 2 "kill player this many seconds after stage completion to prevent cheating by starting out with more speed than otherwise possible; set it to 0 to not kill or to -1 to kill instantly"
set g_cts_send_rankings_cnt 15 "send this number of map records to clients"
set g_cts_removeprojectiles 0 "remove projectiles when the player dies, to prevent using weapons earlier in the stage than intended"
+set g_cts_drop_monster_items 0 "allow killed monsters to drop their items"
// ==========================
set g_monster_mage_heal_delay 1.5
set g_monster_mage_heal_minhealth 250
set g_monster_mage_heal_range 250
-set g_monster_mage_heal_self 40
set g_monster_mage_health 400
set g_monster_mage_shield_blockpercent 0.8
set g_monster_mage_shield_delay 7
flag.angles = '0 0 0';
SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
flag.ctf_droptime = time;
+ flag.ctf_landtime = 0;
flag.ctf_dropper = player;
flag.ctf_status = FLAG_DROPPED;
flag.solid = SOLID_TRIGGER;
flag.ctf_dropper = player;
flag.ctf_droptime = time;
+ flag.ctf_landtime = 0;
flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
if(tmp_entity.ctf_status == FLAG_DROPPED)
if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
- if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
+ if((this.noalign || tmp_entity.ctf_landtime) && time > ((this.noalign) ? tmp_entity.ctf_droptime : tmp_entity.ctf_landtime) + autocvar_g_ctf_dropped_capture_delay)
ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
}
return;
case FLAG_DROPPED:
{
this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
+ if(IS_ONGROUND(this) && !this.ctf_landtime)
+ this.ctf_landtime = time; // landtime is reset when thrown, and we don't want to restart the timer if the flag is pushed
if(autocvar_g_ctf_flag_dropped_floatinwater)
{
flag.ctf_dropper = NULL;
flag.ctf_pickuptime = 0;
flag.ctf_droptime = 0;
+ flag.ctf_landtime = 0;
flag.ctf_flagdamaged_byworld = false;
navigation_dynamicgoal_unset(flag);
float ctf_captimerecord; // record time for capturing the flag
.float ctf_pickuptime;
.float ctf_droptime;
+.float ctf_landtime;
.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
.entity ctf_dropper; // don't allow spam of dropping the flag
.float next_take_time;
float autocvar_g_cts_finish_kill_delay;
bool autocvar_g_cts_selfdamage;
bool autocvar_g_cts_removeprojectiles;
+bool autocvar_g_cts_drop_monster_items;
// legacy bot roles
.float race_checkpoint;
if (Item_IsLoot(item))
{
+ if(item.monster_loot && autocvar_g_cts_drop_monster_items)
+ return false;
return true;
}
}
float autocvar_g_monster_mage_attack_teleport_delay = 2;
float autocvar_g_monster_mage_attack_teleport_random = 0.4;
float autocvar_g_monster_mage_attack_teleport_random_range = 1200;
-float autocvar_g_monster_mage_heal_self;
float autocvar_g_monster_mage_heal_allies;
float autocvar_g_monster_mage_heal_minhealth;
float autocvar_g_monster_mage_heal_range;
);
}
case 2: return (GetResource(targ, RES_ARMOR) < autocvar_g_balance_armor_regenstable);
- case 3: return (GetResource(targ, RES_HEALTH) > 0);
}
return false;
fx = EFFECT_ARMOR_REPAIR;
}
break;
- case 3:
- float hp = ((it == this) ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_allies);
- TakeResource(it, RES_HEALTH, hp); // TODO: use regular damage functions? needs a way to bypass friendly fire checks
- fx = EFFECT_RAGE;
- break;
}
Send_Effect(fx, it.origin, '0 0 0', 1);
{
e.noalign = true;
StartItem(e, e.monster_loot);
+ if(startitem_failed || wasfreed(e))
+ return;
e.gravity = 1;
setorigin(e, org);
e.velocity = randomvec() * 175 + '0 0 325';
void Monster_Reset(entity this)
{
+ if(this.spawnflags & MONSTERFLAG_SPAWNED)
+ {
+ Monster_Remove(this);
+ return;
+ }
+
setorigin(this, this.pos1);
this.angles = this.pos2;
// Supporting functions for VoteCommand
// ======================================
-float Votecommand_check_assignment(entity caller, float assignment)
+bool Votecommand_check_assignment(entity caller, float assignment)
{
- float from_server = (!caller);
+ bool from_server = (!caller);
if ((assignment == VC_ASGNMNT_BOTH)
|| ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY)
return output;
}
-float VoteCommand_checknasty(string vote_command)
+bool VoteCommand_checknasty(string vote_command)
{
- if ((strstrofs(vote_command, ";", 0) >= 0)
+ return !((strstrofs(vote_command, ";", 0) >= 0)
|| (strstrofs(vote_command, "\n", 0) >= 0)
|| (strstrofs(vote_command, "\r", 0) >= 0)
- || (strstrofs(vote_command, "$", 0) >= 0)) return false;
-
- return true;
+ || (strstrofs(vote_command, "$", 0) >= 0));
}
// NOTE: requires input to be surrounded by spaces
string VoteCommand_checkreplacements(string input)
{
- string output = input;
+ // add a space around the input so the start and end of the list is captured
+ string output = strcat(" ", input, " ");
// allow gotomap replacements
output = strreplace(" map ", " gotomap ", output);
output = strreplace(" chmap ", " gotomap ", output);
return output;
}
-float VoteCommand_checkinlist(string vote_command, string list)
+bool VoteCommand_checkinlist(string vote_command, string list)
{
- string l = VoteCommand_checkreplacements(strcat(" ", list, " "));
+ if (vote_command == "" || list == "")
+ return false;
- if (strstrofs(l, VoteCommand_checkreplacements(strcat(" ", vote_command, " ")), 0) >= 0) return true;
-
- return false;
+ string l = VoteCommand_checkreplacements(list);
+ return (strstrofs(l, VoteCommand_checkreplacements(vote_command), 0) >= 0);
}
string ValidateMap(string validated_map, entity caller)
if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
{
delete(this);
- return;
+ return; // TODO does not set startitem_failed
}
this.classname = def.m_canonical_spawnfunc;
// mirror-impact of something hitting the projectile instead of the
// projectile hitting the something, or a touchareagrid one. Neither of
// these stop the projectile from moving, so...
+ // NOTE: this notice is disabled to prevent spam as projectiles can hit content-less objects (other projectiles!)
+#if 0
if(trace_dphitcontents == 0)
{
LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %i, classname: %s, origin: %v)", this, this.classname, this.origin);
checkclient(this); // TODO: .health is checked in the engine with this, possibly replace with a QC function?
}
+#endif
if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
return true;
if (toucher == NULL && this.size != '0 0 0')
traceline(this.origin - tic, this.origin + tic, MOVE_NORMAL, this);
if (trace_fraction >= 1)
{
- LOG_TRACE("Odd... did not hit...?");
+ // NOTE: this notice can occur when projectiles hit non-world objects, better to not spam the console!
+ //LOG_TRACE("Odd... did not hit...?");
}
else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
{