_cl_playerskin 0
seta cl_reticle 1 "control for toggling whether ANY zoom reticles are shown"
-seta cl_reticle_stretch 0 "whether to stretch reticles so they fit the screen (brakes image proportions)"
+seta cl_reticle_stretch 0 "whether to stretch reticles so they fit the screen (breaks image proportions)"
seta cl_reticle_item_nex 1 "draw aiming reticle for the nex weapon's zoom, 0 disables and values between 0 and 1 change alpha"
seta cl_reticle_item_normal 1 "draw reticle when zooming with the zoom button, 0 disables and values between 0 and 1 change alpha"
fov 100
set cl_hitsound_antispam_time 0.05 "don't play the hitsound more often than this"
seta cl_eventchase_death 1 "camera goes into 3rd person mode when the player is dead"
+seta cl_eventchase_nexball 1 "camera goes into 3rd person mode when in nexball game-mode"
seta cl_eventchase_distance 140 "final camera distance"
seta cl_eventchase_speed 1.3 "how fast the camera slides back, 0 is instant"
seta cl_eventchase_maxs "12 12 8" "max size of eventchase camera bbox"
seta g_race_laps_limit -1 "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_invasion_point_limit -1 "Invasion point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
// =================================
set g_invasion_warmup 10 "time between waves to prepare for battle"
set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
set g_invasion_zombies_only 0 "only spawn zombies"
-set g_invasion_spawn_delay 2 "spawn more monsters after this delay"
+set g_invasion_spawn_delay 0.25
+set g_invasion_spawnpoint_spawn_delay 0.5
+set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
+set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
sex Male
weight 105
age 26
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
overkill solider
sex Male
weight 105
age 26
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
overkill officer
\ No newline at end of file
sex Male
weight 105
age 28
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
overkill solider
sex Male
weight 105
age 28
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
overkill solider
sex None
weight 200
age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
Overkill robot
sex None
weight 200
age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
Overkill robot
sex None
weight 200
age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
Overkill robot
sex None
weight 200
age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
Overkill robot
set g_monsters_teams 1
set g_monsters_score_kill 0
set g_monsters_score_spawned 0
+set g_monsters_sounds 1
set g_monsters_spawnshieldtime 2
set g_monsters_typefrag 1
set g_monsters_target_range 2000
-// Mark all cvars listed in cl_forced_saved_cvars as saved. That way they'll never disappear from config.cfg.
-alias _cl_forced_saved_cvars_next "set _forced_saved_cvar_ doit; set _forced_saved_cvar_${1 ?} done; _cl_forced_saved_cvars_chck ${* ?}"
-alias _cl_forced_saved_cvars_chck "_cl_forced_saved_cvars_$_forced_saved_cvar_ ${* ?}"
-alias _cl_forced_saved_cvars_doit "seta $1 \"${$1}\"; _cl_forced_saved_cvars_next ${2- ?}"
-alias _cl_forced_saved_cvars_done ""
-_cl_forced_saved_cvars_next $cl_forced_saved_cvars
+// Mark all cvars listed in menu_forced_saved_cvars as saved. That way they'll never disappear from config.cfg.
+alias _menu_forced_saved_cvars_next "set _forced_saved_cvar_ doit; set _forced_saved_cvar_${1 ?} done; _menu_forced_saved_cvars_chck ${* ?}"
+alias _menu_forced_saved_cvars_chck "_menu_forced_saved_cvars_$_forced_saved_cvar_ ${* ?}"
+alias _menu_forced_saved_cvars_doit "seta $1 \"${$1}\"; _menu_forced_saved_cvars_next ${2- ?}"
+alias _menu_forced_saved_cvars_done ""
+_menu_forced_saved_cvars_next ${menu_forced_saved_cvars ?}
XON_BUILDSYSTEM =
all: qc
+.PHONY: all
.PHONY: qc
-qc:
- $(MAKE) qc-recursive
-
-.PHONY: qc-recursive
-qc-recursive: ../menu.dat ../progs.dat ../csprogs.dat
+qc: ../menu.dat ../progs.dat ../csprogs.dat
.PHONY: clean
clean:
default:
if(GetTeam(Team, false) == world)
{
- printf(_("trying to switch to unsupported team %d\n"), Team);
+ dprintf("trying to switch to unsupported team %d\n", Team);
Team = NUM_SPECTATOR;
}
break;
default:
if(GetTeam(Team, false) == world)
{
- printf(_("trying to switch to unsupported team %d\n"), Team);
+ dprintf("trying to switch to unsupported team %d\n", Team);
Team = NUM_SPECTATOR;
}
break;
// event chase camera
if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
{
- if(((spectatee_status >= 0 && (autocvar_cl_eventchase_death && is_dead)) || intermission) && !autocvar_cl_orthoview)
+ WepSet weapons_stat = WepSet_GetFromStat();
+ if(((spectatee_status >= 0 && (autocvar_cl_eventchase_death && is_dead)) || intermission) && !autocvar_cl_orthoview || (autocvar_cl_eventchase_nexball && gametype == MAPINFO_TYPE_NEXBALL && !(weapons_stat & WepSet_FromWeapon(WEP_PORTO))))
{
// make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.)
vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org);
float autocvar_cl_hitsound;
float autocvar_cl_hitsound_antispam_time;
var float autocvar_cl_eventchase_death = 1;
+var float autocvar_cl_eventchase_nexball = 1;
var float autocvar_cl_eventchase_distance = 140;
var float autocvar_cl_eventchase_speed = 1.3;
var vector autocvar_cl_eventchase_maxs = '12 12 8';
break;
}
- if((self.owner.sv_entnum == player_localentnum - 1))
+ if((self.owner.sv_entnum == player_localentnum - 1) && autocvar_chase_active <= 0)
{
switch(self.HookType)
{
case ENT_CLIENT_HOOK:
intensity = 1;
offset = 0;
- if(t == NUM_TEAM_1)
+ switch(t)
{
- tex = "particles/hook_red";
- rgb = '1 .3 .3';
- }
- else if(t == NUM_TEAM_2)
- {
- tex = "particles/hook_blue";
- rgb = '.3 .3 1';
- }
- else if(t == NUM_TEAM_3)
- {
- tex = "particles/hook_yellow";
- rgb = '1 1 .3';
- }
- else if(t == NUM_TEAM_4)
- {
- tex = "particles/hook_pink";
- rgb = '1 .3 1';
- }
- else
- {
- tex = "particles/hook_green";
- rgb = '.3 1 .3';
+ case NUM_TEAM_1: tex = "particles/hook_red"; rgb = '1 0.3 0.3'; break;
+ case NUM_TEAM_2: tex = "particles/hook_blue"; rgb = '0.3 0.3 1'; break;
+ case NUM_TEAM_3: tex = "particles/hook_yellow"; rgb = '1 1 0.3'; break;
+ case NUM_TEAM_4: tex = "particles/hook_pink"; rgb = '1 0.3 1'; break;
+ default: tex = "particles/hook_white"; rgb = getcsqcplayercolor(self.sv_entnum); break;
}
break;
case ENT_CLIENT_LGBEAM:
if(sf & 1)
{
- self.owner = playerslots[ReadByte() - 1];
+ float myowner = ReadByte();
+ self.owner = playerslots[myowner - 1];
+ self.sv_entnum = myowner;
switch(self.HookType)
{
default:
#define HUD_PANEL(NAME,draw_func,name) \
float HUD_PANEL_##NAME; \
- void ##draw_func(void); \
+ void draw_func(void); \
void RegisterHUD_Panel_##NAME() \
{ \
HUD_PANEL_LAST = HUD_PANEL_##NAME = HUD_PANEL_NUM; \
hud_panelent.classname = "hud_panel"; \
hud_panelent.panel_name = #name; \
hud_panelent.panel_id = HUD_PANEL_##NAME; \
- hud_panelent.panel_draw = ##draw_func; \
+ hud_panelent.panel_draw = draw_func; \
++HUD_PANEL_NUM; \
} \
ACCUMULATE_FUNCTION(RegisterHUD_Panels, RegisterHUD_Panel_##NAME);
return 1;
}
+vector getcsqcplayercolor(float pl)
+{
+ entity e;
+
+ e = CSQCModel_server2csqc(pl);
+ if(e)
+ {
+ if(e.colormap > 0)
+ return colormapPaletteColor(((e.colormap >= 1024) ? e.colormap : stof(getplayerkeyvalue(e.colormap - 1, "colors"))) & 0x0F, TRUE);
+ }
+
+ return '1 1 1';
+}
+
float getplayerisdead(float pl)
{
entity e;
}
pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
}
- else if(autocvar_scoreboard_accuracy && spectatee_status != -1 && !warmup_stage) {
+ else if(autocvar_scoreboard_accuracy && spectatee_status == 0 && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
if(teamplay)
pos = HUD_DrawScoreboardAccuracyStats(pos, Team_ColorRGB(myteam), bg_size);
else
// Print info string
float tl, fl, ll;
- str = sprintf(_("playing on ^2%s^7"), shortmapname);
+ str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
tl = getstatf(STAT_TIMELIMIT);
fl = getstatf(STAT_FRAGLIMIT);
ll = getstatf(STAT_LEADLIMIT);
// print information about respawn status
float respawn_time = getstatf(STAT_RESPAWN_TIME);
+ if(!intermission)
if(respawn_time)
{
if(respawn_time < 0)
case "race-finish": return _("Finish");
case "race-start": return _("Start");
case "race-start-finish": return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
+ case "goal": return _("Goal");
case "nb-ball": return _("Ball");
case "ka-ball": return _("Ball");
case "ka-ballcarrier": return _("Ball carrier");
MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
else if(startsWith(v, "vehicle_"))
MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
+ else if(startsWith(v, "monster_"))
+ MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
else if(v == "target_music" || v == "trigger_music")
_MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM
}
cvar_set("g_freezetag_teams", v);
cvar_set("g_keyhunt_teams", v);
cvar_set("g_domination_default_teams", v);
+ cvar_set("g_invasion_teams", v);
}
else if(k == "qualifying_timelimit")
{
if (t == "weapons") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
+ else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
else
dprint("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30");
#define g_keepaway IS_GAMETYPE(KEEPAWAY)
-REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=5");
+REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=50 teams=0");
#define g_invasion IS_GAMETYPE(INVASION)
const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps
const float MAPINFO_FEATURE_VEHICLES = 2;
const float MAPINFO_FEATURE_TURRETS = 4;
+const float MAPINFO_FEATURE_MONSTERS = 8;
const float MAPINFO_FLAG_HIDDEN = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
const float MAPINFO_FLAG_FORBIDDEN = 2; // don't even allow the map by a cvar setting that allows hidden maps
{
pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
- WaypointSprite_UpdateHealth(head.sprite, head.health);
+ if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE))
+ WaypointSprite_UpdateHealth(head.sprite, head.health);
}
}
{
self.classname = "monster_mage";
- self.monster_spawnfunc = spawnfunc_monster_mage;
-
- if(Monster_CheckAppearFlags(self))
- return;
-
- if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+ if(!monster_initialize(MON_MAGE)) { remove(self); return; }
}
// compatibility with old spawns
}
case MR_PRECACHE:
{
- precache_model ("models/monsters/mage.dpm");
+ precache_model("models/monsters/mage.dpm");
precache_sound ("weapons/grenade_impact.wav");
precache_sound ("weapons/tagexp1.wav");
return TRUE;
{
case MR_PRECACHE:
{
- precache_model ("models/monsters/mage.dpm");
return TRUE;
}
}
case MONSTER_ATTACK_RANGED:
{
if(time >= self.shambler_lastattack) // shambler doesn't attack much
+ if(self.flags & FL_ONGROUND)
if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500)
{
self.frame = shambler_anim_smash;
{
self.classname = "monster_shambler";
- self.monster_spawnfunc = spawnfunc_monster_shambler;
-
- if(Monster_CheckAppearFlags(self))
- return;
-
- if(!monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
+ if(!monster_initialize(MON_SHAMBLER)) { remove(self); return; }
}
float m_shambler(float req)
}
case MR_PRECACHE:
{
- precache_model ("models/monsters/shambler.mdl");
+ precache_model("models/monsters/shambler.mdl");
return TRUE;
}
}
{
case MR_PRECACHE:
{
- precache_model ("models/monsters/shambler.mdl");
return TRUE;
}
}
pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world);
- for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+ for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != MON_SPIDER)
e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
remove(self);
{
self.classname = "monster_spider";
- self.monster_spawnfunc = spawnfunc_monster_spider;
-
- if(Monster_CheckAppearFlags(self))
- return;
-
- if(!monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+ if(!monster_initialize(MON_SPIDER)) { remove(self); return; }
}
float m_spider(float req)
}
case MR_PRECACHE:
{
- precache_model ("models/monsters/spider.dpm");
+ precache_model("models/monsters/spider.dpm");
precache_sound ("weapons/electro_fire2.wav");
return TRUE;
}
{
case MR_PRECACHE:
{
- precache_model ("models/monsters/spider.dpm");
return TRUE;
}
}
{
self.classname = "monster_wyvern";
- self.monster_spawnfunc = spawnfunc_monster_wyvern;
-
- if(Monster_CheckAppearFlags(self))
- return;
-
- if(!monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
+ if(!monster_initialize(MON_WYVERN)) { remove(self); return; }
}
// compatibility with old spawns
}
case MR_PRECACHE:
{
- precache_model ("models/monsters/wizard.mdl");
+ precache_model("models/monsters/wizard.mdl");
return TRUE;
}
}
{
case MR_PRECACHE:
{
- precache_model ("models/monsters/wizard.mdl");
return TRUE;
}
}
{
self.classname = "monster_zombie";
- self.monster_spawnfunc = spawnfunc_monster_zombie;
-
- self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
-
- if(Monster_CheckAppearFlags(self))
- return;
-
- if(!monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+ if(!monster_initialize(MON_ZOMBIE)) { remove(self); return; }
}
float m_zombie(float req)
if(self.spawnflags & MONSTERFLAG_NORESPAWN)
self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
+ self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
+
self.monster_loot = spawnfunc_item_health_medium;
self.monster_attackfunc = zombie_attack;
self.frame = zombie_anim_spawn;
}
case MR_PRECACHE:
{
- precache_model ("models/monsters/zombie.dpm");
+ precache_model("models/monsters/zombie.dpm");
return TRUE;
}
}
{
case MR_PRECACHE:
{
- precache_model ("models/monsters/zombie.dpm");
return TRUE;
}
}
e.mins = min_s;
e.maxs = max_s;
e.model = strzone(strcat("models/monsters/", modelname));
-
- #ifndef MENUQC
- func(MR_PRECACHE);
- #endif
}
float m_null(float dummy) { return 0; }
void register_monsters_done()
if(m)
return m;
return dummy_monster_info;
-}
\ No newline at end of file
+}
-entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag)
{
// ensure spawnfunc database is initialized
- initialize_field_db();
+ //initialize_field_db();
entity e = spawn();
float i;
if(!respwn)
e.spawnflags |= MONSTERFLAG_NORESPAWN;
+ if(invincible)
+ e.spawnflags |= MONSTERFLAG_INVINCIBLE;
+
setorigin(e, orig);
if(monster == "random")
if(mon.netname == monster)
{
found = TRUE;
+ monster_id = mon.monsterid; // we have the monster, old monster id is no longer required
break;
}
}
e.angles = spawnedby.angles;
}
- monster = strcat("$ spawnfunc_monster_", monster);
+ //monster = strcat("$ spawnfunc_monster_", monster);
+
+ entity oldself = self;
+ self = e;
+ monster_initialize(monster_id);
+ self = oldself;
- target_spawn_edit_entity(e, monster, world, world, world, world, world);
+ //target_spawn_edit_entity(e, monster, world, world, world, world, world);
return e;
}
-entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag);
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag);
// =========================
-void monster_item_spawn()
-{
- if(self.monster_loot)
- self.monster_loot();
-
- self.gravity = 1;
- self.reset = SUB_Remove;
- self.noalign = TRUE;
- self.velocity = randomvec() * 175 + '0 0 325';
- self.classname = "droppedweapon"; // hax
- self.item_spawnshieldtime = time + 0.7;
-
- SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
-}
-
void monster_dropitem()
{
if(!self.candrop || !self.monster_loot)
return;
vector org = self.origin + ((self.mins + self.maxs) * 0.5);
- entity e = spawn();
-
- setorigin(e, org);
+ entity e = spawn(), oldself = self;
e.monster_loot = self.monster_loot;
MUTATOR_CALLHOOK(MonsterDropItem);
e = other;
- if(e)
+ if(e && e.monster_loot)
{
- e.think = monster_item_spawn;
- e.nextthink = time + 0.3;
+ self = e;
+ e.noalign = TRUE;
+ e.monster_loot();
+ e.gravity = 1;
+ e.movetype = MOVETYPE_TOSS;
+ e.reset = SUB_Remove;
+ setorigin(e, org);
+ e.velocity = randomvec() * 175 + '0 0 325';
+ e.item_spawnshieldtime = time + 0.7;
+ e.classname = "droppedweapon"; // use weapon handling to remove it on touch
+ SUB_SetFade(e, time + autocvar_g_monsters_drop_time, 1);
+ self = oldself;
}
}
if(targ == ent)
return FALSE; // don't attack ourselves
- traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+ //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
- if(trace_ent != targ)
- return FALSE;
+ //if(trace_ent != targ)
+ //return FALSE;
if(targ.vehicle_flags & VHF_ISVEHICLE)
if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED))
if(time < game_starttime)
return FALSE; // monsters do nothing before the match has started
- if(vlen(targ.origin - ent.origin) >= ent.target_range)
- return FALSE; // enemy is too far away
-
if(targ.takedamage == DAMAGE_NO)
return FALSE; // enemy can't be damaged
if (targ.freezetag_frozen)
return FALSE; // ignore frozen
- if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
+ if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT))
if(ent.enemy != targ)
{
float dot;
entity head, closest_target = world;
head = findradius(ent.origin, ent.target_range);
+ //head = WarpZone_FindRadius(ent.origin, ent.target_range, TRUE);
while(head) // find the closest acceptable target to pass to
{
{
// if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
vector head_center = CENTER_OR_VIEWOFS(head);
+ //vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
vector ent_center = CENTER_OR_VIEWOFS(ent);
- //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+ traceline(ent_center, head_center, MOVE_NORMAL, ent);
+
+ if(trace_ent == head)
if(closest_target)
{
vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+ //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
{ closest_target = head; }
}
void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
{
- if(delaytoo && time < self.msound_delay)
+ if(!autocvar_g_monsters_sounds) { return; }
+
+ if(delaytoo)
+ if(time < self.msound_delay)
return; // too early
GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
return TRUE;
}
+float monster_initialize(float mon_id);
+void monster_respawn()
+{
+ // is this function really needed?
+ monster_initialize(self.monsterid);
+}
+
void Monster_Fade ()
{
if(Monster_CanRespawn(self))
{
self.spawnflags |= MONSTERFLAG_RESPAWNED;
- self.think = self.monster_spawnfunc;
+ self.think = monster_respawn;
self.nextthink = time + self.respawntime;
- self.ltime = 0;
+ self.monster_lifetime = 0;
self.deadflag = DEAD_RESPAWNING;
if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
{
// number of monsters spawned with mobspawn command
totalspawned -= 1;
- if(IS_CLIENT(self.realowner))
- self.realowner.monstercount -= 1;
-
SUB_SetFade(self, time + 3, 1);
}
}
// enemy is always preferred target
if(self.enemy)
{
- makevectors(self.angles);
+ vector targ_origin = ((self.enemy.absmin + self.enemy.absmax) * 0.5);
+ targ_origin = WarpZone_RefSys_TransformOrigin(self.enemy, self, targ_origin); // origin of target as seen by the monster (us)
+ WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+
+ if((self.enemy == world)
+ || (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1)
+ || (self.enemy.freezetag_frozen)
+ || (self.enemy.flags & FL_NOTARGET)
+ || (self.enemy.alpha < 0.5)
+ || (self.enemy.takedamage == DAMAGE_NO)
+ || (vlen(self.origin - targ_origin) > self.target_range)
+ || ((trace_fraction < 1) && (trace_ent != self.enemy)))
+ //|| (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) // TODO: chase timelimit?
+ {
+ self.enemy = world;
+ self.pass_distance = 0;
+ }
+
+ if(self.enemy)
+ {
+ /*WarpZone_TrailParticles(world, particleeffectnum("red_pass"), self.origin, targ_origin);
+ print("Trace origin: ", vtos(targ_origin), "\n");
+ print("Target origin: ", vtos(self.enemy.origin), "\n");
+ print("My origin: ", vtos(self.origin), "\n"); */
+
+ self.monster_movestate = MONSTER_MOVE_ENEMY;
+ self.last_trace = time + 1.2;
+ return targ_origin;
+ }
+
+ /*makevectors(self.angles);
self.monster_movestate = MONSTER_MOVE_ENEMY;
self.last_trace = time + 1.2;
- return self.enemy.origin;
+ return self.enemy.origin; */
}
switch(self.monster_moveflags)
{
vector pos;
self.monster_movestate = MONSTER_MOVE_WANDER;
- self.last_trace = time + 2;
-
- self.angles_y = rint(random() * 500);
- makevectors(self.angles);
- pos = self.origin + v_forward * 600;
-
- if(self.flags & FL_FLY || self.flags & FL_SWIM)
- if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
- {
- pos_z = random() * 200;
- if(random() >= 0.5)
- pos_z *= -1;
- }
if(targ)
{
self.last_trace = time + 0.5;
pos = targ.origin;
}
+ else
+ {
+ self.last_trace = time + self.wander_delay;
+
+ self.angles_y = rint(random() * 500);
+ makevectors(self.angles);
+ pos = self.origin + v_forward * self.wander_distance;
+
+ if(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM))
+ {
+ pos_z = random() * 200;
+ if(random() >= 0.5)
+ pos_z *= -1;
+ }
+ }
return pos;
}
}
}
+void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed)
+{
+ float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
+ float initial_height = 0; //min(50, (targ_distance * tanh(20)));
+ float current_height = (initial_height * min(1, (current_distance / self.pass_distance)));
+ //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
+
+ vector targpos;
+ if(current_height) // make sure we can actually do this arcing path
+ {
+ targpos = (to + ('0 0 1' * current_height));
+ WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+ if(trace_fraction < 1)
+ {
+ //print("normal arc line failed, trying to find new pos...");
+ WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, mon);
+ targpos = (trace_endpos + '0 0 -10');
+ WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+ if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
+ /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
+ }
+ }
+ else { targpos = to; }
+
+ //mon.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+
+ vector desired_direction = normalize(targpos - from);
+ if(turnrate) { mon.velocity = (normalize(normalize(mon.velocity) + (desired_direction * 50)) * movespeed); }
+ else { mon.velocity = (desired_direction * movespeed); }
+
+ //mon.steerto = steerlib_attract2(targpos, 0.5, 500, 0.95);
+ //mon.angles = vectoangles(mon.velocity);
+}
+
void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
{
- fixedmakevectors(self.angles);
+ //fixedmakevectors(self.angles);
if(self.target2)
self.goalentity = find(world, targetname, self.target2);
monster_speed_run = runspeed;
monster_speed_walk = walkspeed;
- if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+ if(MUTATOR_CALLHOOK(MonsterMove) || gameover || self.draggedby != world || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
{
runspeed = walkspeed = 0;
if(time >= self.spawn_time)
if(DIFF_TEAM(self.monster_owner, self))
self.monster_owner = world;
- if(self.enemy && self.enemy.health < 1)
- self.enemy = world; // enough!
-
if(time >= self.last_enemycheck)
{
- if(!monster_isvalidtarget(self.enemy, self))
- self.enemy = world;
-
if(!self.enemy)
{
self.enemy = FindTarget(self);
if(self.enemy)
+ {
+ WarpZone_RefSys_Copy(self.enemy, self);
+ WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver
+ self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax)));
+
+ self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' * self.origin_x) + ('0 1 0' * self.origin_y)));
MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
+ }
}
- self.last_enemycheck = time + 0.5;
+ self.last_enemycheck = time + 1; // check for enemies every second
}
if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
if(!self.enemy)
MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE);
- if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
- self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-
if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
{
self.state = 0;
self.touch = MonsterTouch;
}
- //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-
- float turny = 0;
- vector real_angle = vectoangles(self.steerto) - self.angles;
-
- if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
- turny = 20;
-
- if(self.flags & FL_SWIM)
- turny = vlen(self.angles - self.moveto);
-
- if(turny)
- {
- turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
- self.angles_y += turny;
- }
-
if(self.state == MONSTER_STATE_ATTACK_MELEE)
self.moveto = self.origin;
if(self.enemy && self.enemy.vehicle)
runspeed = 0;
- if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
- v_forward = normalize(self.moveto - self.origin);
- else
+ if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM)))
+ //v_forward = normalize(self.moveto - self.origin);
+ //else
self.moveto_z = self.origin_z;
if(vlen(self.origin - self.moveto) > 64)
{
- if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
+ monster_CalculateVelocity(self, self.moveto, self.origin, TRUE, ((self.enemy) ? runspeed : walkspeed));
+
+ /*&if(self.flags & FL_FLY || self.flags & FL_SWIM)
movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
else
- movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+ movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */
if(time > self.pain_finished)
if(time > self.attack_finished_single)
if (vlen(self.velocity) <= 30)
self.frame = manim_idle;
}
+
+ self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+
+ vector real_angle = vectoangles(self.steerto) - self.angles;
+ float turny = 25;
+ if(self.state == MONSTER_STATE_ATTACK_MELEE)
+ turny = 0;
+ if(turny)
+ {
+ turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+ self.angles_y += turny;
+ }
monster_checkattack(self, self.enemy);
}
CSQCMODEL_AUTOUPDATE();
- if(self.ltime != 0)
- if(time >= self.ltime)
+ if(self.monster_lifetime != 0)
+ if(time >= self.monster_lifetime)
{
Monster_Fade();
return;
void Monster_Appear()
{
self.enemy = activator;
- self.spawnflags &= ~MONSTERFLAG_APPEAR;
- self.monster_spawnfunc();
+ self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop
+ monster_initialize(self.monsterid);
}
-float Monster_CheckAppearFlags(entity ent)
+float Monster_CheckAppearFlags(entity ent, float monster_id)
{
if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
return FALSE;
ent.think = func_null;
+ ent.monsterid = monster_id; // set so this monster is properly registered (otherwise, normal initialization is used)
ent.nextthink = 0;
ent.use = Monster_Appear;
ent.flags = FL_MONSTER; // set so this monster can get butchered
// number of monsters spawned with mobspawn command
totalspawned -= 1;
- if(IS_CLIENT(self.realowner))
- self.realowner.monstercount -= 1;
-
self.think = SUB_Remove;
self.nextthink = time + 0.1;
+ self.event_damage = func_null;
}
}
{
self.think = monster_dead_think;
self.nextthink = time;
- self.ltime = time + 5;
+ self.monster_lifetime = time + 5;
monster_dropitem();
{
// number of monsters spawned with mobspawn command
totalspawned -= 1;
-
- if(IS_CLIENT(self.realowner))
- self.realowner.monstercount -= 1;
}
if(self.candrop && self.weapon)
W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
- self.event_damage = monsters_corpse_damage;
+ self.event_damage = ((gibbed) ? func_null : monsters_corpse_damage);
self.solid = SOLID_CORPSE;
self.takedamage = DAMAGE_AIM;
self.deadflag = DEAD_DEAD;
self.state = 0;
self.attack_finished_single = 0;
- if(!(self.flags & FL_FLY))
+ if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
self.velocity = '0 0 0';
MON_ACTION(self.monsterid, MR_DEATH);
void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
{
+ if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL)
+ return;
+
if(time < self.pain_finished && deathtype != DEATH_KILL)
return;
if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
return;
+ if(deathtype == DEATH_FALL && self.draggedby != world)
+ return;
+
vector v;
float take, save;
}
}
-void monster_setupcolors()
+void monster_setupcolors(entity mon)
{
- if(IS_PLAYER(self.monster_owner))
- self.colormap = self.monster_owner.colormap;
- else if(teamplay && self.team)
- self.colormap = 1024 + (self.team - 1) * 17;
+ if(IS_PLAYER(mon.monster_owner))
+ mon.colormap = mon.monster_owner.colormap;
+ else if(teamplay && mon.team)
+ mon.colormap = 1024 + (mon.team - 1) * 17;
else
{
- if(self.monster_skill <= MONSTER_SKILL_EASY)
- self.colormap = 1029;
- else if(self.monster_skill <= MONSTER_SKILL_MEDIUM)
- self.colormap = 1027;
- else if(self.monster_skill <= MONSTER_SKILL_HARD)
- self.colormap = 1038;
- else if(self.monster_skill <= MONSTER_SKILL_INSANE)
- self.colormap = 1028;
- else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE)
- self.colormap = 1032;
+ if(mon.monster_skill <= MONSTER_SKILL_EASY)
+ mon.colormap = 1029;
+ else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
+ mon.colormap = 1027;
+ else if(mon.monster_skill <= MONSTER_SKILL_HARD)
+ mon.colormap = 1038;
+ else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
+ mon.colormap = 1028;
+ else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
+ mon.colormap = 1032;
else
- self.colormap = 1024;
+ mon.colormap = 1024;
+ }
+}
+
+void monster_changeteam(entity ent, float newteam)
+{
+ if(!teamplay) { return; }
+
+ ent.team = newteam;
+ ent.monster_attack = TRUE; // new team, activate attacking
+ monster_setupcolors(ent);
+
+ if(ent.sprite)
+ {
+ WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
+
+ ent.sprite.team = newteam;
+ ent.sprite.SendFlags |= 1;
}
}
self.think = monster_think;
self.nextthink = self.ticrate;
- if(self.ltime)
- if(time >= self.ltime)
+ if(self.monster_lifetime)
+ if(time >= self.monster_lifetime)
{
Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin);
return;
if(!self.attack_range)
self.attack_range = autocvar_g_monsters_attack_range;
+ if(!self.wander_delay) { self.wander_delay = 2; }
+ if(!self.wander_distance) { self.wander_distance = 600; }
+
precache_monstersounds();
UpdateMonsterSounds();
MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
- WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+ {
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
self.think = monster_think;
self.nextthink = time + self.ticrate;
return TRUE;
}
-float monster_initialize(float mon_id, float nodrop)
+float monster_initialize(float mon_id)
{
- if(!autocvar_g_monsters)
- return FALSE;
+ if(!autocvar_g_monsters) { return FALSE; }
+ if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); }
+ if(Monster_CheckAppearFlags(self, mon_id)) { return TRUE; } // return true so the monster isn't removed
entity mon = get_monsterinfo(mon_id);
self.team = 0;
if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
- if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+ if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
monsters_total += 1;
setmodel(self, mon.model);
- setsize(self, mon.mins, mon.maxs);
+ //setsize(self, mon.mins, mon.maxs);
self.flags = FL_MONSTER;
self.takedamage = DAMAGE_AIM;
self.bot_attack = TRUE;
self.candrop = TRUE;
self.view_ofs = '0 0 1' * (self.maxs_z * 0.5);
self.oldtarget2 = self.target2;
+ self.pass_distance = 0;
self.deadflag = DEAD_NO;
- self.scale = 1;
- self.noalign = nodrop;
+ self.noalign = ((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM));
self.spawn_time = time;
self.spider_slowness = 0;
self.gravity = 1;
self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+ if(!self.scale)
+ self.scale = 1;
+
+ if(autocvar_g_monsters_edit)
+ self.grab = 1; // owner may carry their monster
+
if(autocvar_g_fullbrightplayers)
self.effects |= EF_FULLBRIGHT;
}
if(mon.spawnflags & MONSTER_SIZE_BROKEN)
- self.scale = 1.3;
+ if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+ self.scale *= 1.3;
+
+ setsize(self, mon.mins * self.scale, mon.maxs * self.scale);
if(!self.ticrate)
self.ticrate = autocvar_g_monsters_think_delay;
return FALSE;
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
- monster_setupcolors();
+ monster_setupcolors(self);
CSQCMODEL_AUTOINIT();
.float monster_attack;
.entity monster_owner; // new monster owner entity, fixes non-solid monsters
-.float monstercount; // per player monster count
.float stat_monsters_killed; // stats
.float stat_monsters_total;
void monsters_setstatus(); // monsters.qc
.float monster_moveflags; // checks where to move when not attacking
+.float wander_delay;
+.float wander_distance;
+
+.float monster_lifetime;
+
.float spider_slowness; // special spider timer
void monster_remove(entity mon); // removes a monster
const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters)
const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
const float MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters
-.void() monster_spawnfunc;
-
.float monster_movestate; // used to tell what the monster is currently doing
const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
MSG_CENTER_NOTIF(1, CENTER_JOIN_PREVENT, 0, 0, "", CPID_PREVENT_JOIN, "0 0", _("^K1You may not join the game at this time.\nThe player limit reached maximum capacity."), "") \
MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_DROPPED, 1, 0, "s1", CPID_KEEPAWAY, "0 0", _("^BG%s^BG has dropped the ball!"), "") \
MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP, 1, 0, "s1", CPID_KEEPAWAY, "0 0", _("^BG%s^BG has picked up the ball!"), "") \
+ MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP_SELF, 0, 0, "", CPID_KEEPAWAY, "0 0", _("^BGYou picked up the ball"), "") \
MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_WARN, 0, 0, "", CPID_KEEPAWAY_WARN, "0 0", _("^BGKilling people while you don't have the ball gives no points!"), "") \
MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_HELP, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGAll keys are in your team's hands!\nHelp the key carriers to meet!"), "") \
MULTITEAM_CENTER(1, CENTER_KEYHUNT_INTERFERE_, 4, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGAll keys are in ^TC^TT team^BG's hands!\nInterfere ^F4NOW^BG!"), "") \
return CONTENT_EMPTY;
}
#endif
+
+vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
+{
+ return
+ (c - 2 * b + a) * (t * t) +
+ (b - a) * (2 * t) +
+ a;
+}
+
+vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
+{
+ return
+ (c - 2 * b + a) * (2 * t) +
+ (b - a) * 2;
+}
float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents);
float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents);
#endif
+
+// Quadratic splines (bezier)
+vector bezier_quadratic_getpoint(vector a, vector p, vector b, float t);
+vector bezier_quadratic_getderivative(vector a, vector p, vector b, float t);
#include "xonotic/dialog_settings_effects.c"
#include "xonotic/dialog_settings_audio.c"
#include "xonotic/dialog_settings_user.c"
+#include "xonotic/dialog_settings_user_languagewarning.c"
#include "xonotic/dialog_settings_misc.c"
#include "xonotic/dialog_multiplayer.c"
#include "xonotic/dialog_multiplayer_playersetup.c"
return 0;
}
+void firstRun_setLanguage(entity me)
+{
+ if(prvm_language != cvar_string("_menu_prvm_language"))
+ localcmd("\nprvm_language \"$_menu_prvm_language\"; saveconfig; menu_restart\n");
+}
+
void XonoticFirstRunDialog_fill(entity me)
{
entity e;
me.TR(me);
me.TD(me, 6, 2, e = makeXonoticLanguageList());
e.name = "languageselector_firstrun";
- e.doubleClickCommand = "prvm_language \"$_menu_prvm_language\"; saveconfig; menu_restart";
+ e.setLanguage = firstRun_setLanguage;
me.TR(me);
me.TR(me);
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticLanguageWarningDialog) EXTENDS(XonoticDialog)
+ METHOD(XonoticLanguageWarningDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+ ATTRIB(XonoticLanguageWarningDialog, title, string, _("Warning"))
+ ATTRIB(XonoticLanguageWarningDialog, color, vector, SKINCOLOR_DIALOG_HUDCONFIRM)
+ ATTRIB(XonoticLanguageWarningDialog, intendedWidth, float, 0.6)
+ ATTRIB(XonoticLanguageWarningDialog, rows, float, 5)
+ ATTRIB(XonoticLanguageWarningDialog, columns, float, 4)
+ENDCLASS(XonoticLanguageWarningDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticLanguageWarningDialog_fill(entity me)
+{
+ entity e;
+ me.TR(me);
+ me.TD(me, 1, 4, e = makeXonoticTextLabel(0, _("While connected language changes will be applied only to the menu,")));
+ me.TR(me);
+ me.TD(me, 1, 4, e = makeXonoticTextLabel(0, _("full language changes will take effect starting from the next game")));
+ me.TR(me);
+ me.TR(me);
+ // reconnect command doesn't work properly, otherwise it would replace disconnect
+ me.TD(me, 1, 2, e = makeXonoticCommandButton(_("Disconnect now"), '0 0 0', "disconnect", 0));
+ me.TD(me, 1, 2, e = makeXonoticCommandButton(_("Switch language"), '0 0 0', "prvm_language \"$_menu_prvm_language\"; menu_restart; menu_cmd languageselect", 0));
+}
+#endif
if(key == K_ESCAPE)
return;
+ // forbid these keys from being bound in the menu
+ if(key == K_CAPSLOCK || key == K_NUMLOCK)
+ {
+ KeyBinder_Bind_Change(me, me);
+ return;
+ }
+
func = Xonotic_KeyBinds_Functions[me.selectedItem];
if(func == "")
return;
METHOD(XonoticLanguageList, languageParameter, string(entity, float, float))
ATTRIB(XonoticLanguageList, name, string, "languageselector") // change this to make it noninteractive (for first run dialog)
-
- ATTRIB(XonoticLanguageList, doubleClickCommand, string, "prvm_language \"$_menu_prvm_language\"\nmenu_restart\nmenu_cmd languageselect")
ENDCLASS(XonoticLanguageList)
entity makeXonoticLanguageList();
void XonoticLanguageList_setLanguage(entity me)
{
- localcmd(sprintf("\n%s\n", me.doubleClickCommand));
+ if(prvm_language != cvar_string("_menu_prvm_language"))
+ {
+ if(!(gamestatus & GAME_CONNECTED))
+ localcmd("\nprvm_language \"$_menu_prvm_language\"; menu_restart; menu_cmd languageselect\n");
+ else
+ DialogOpenButton_Click(me, main.languageWarningDialog);
+ }
}
string XonoticLanguageList_languageParameter(entity me, float i, float key)
ATTRIB(MainWindow, crosshairDialog, entity, NULL)
ATTRIB(MainWindow, hudDialog, entity, NULL)
ATTRIB(MainWindow, hudconfirmDialog, entity, NULL)
+ ATTRIB(MainWindow, languageWarningDialog, entity, NULL)
ATTRIB(MainWindow, mainNexposee, entity, NULL)
ATTRIB(MainWindow, fadedAlpha, float, SKINALPHA_BEHIND)
ATTRIB(MainWindow, dialogToShow, entity, NULL)
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+ me.languageWarningDialog = i = spawnXonoticLanguageWarningDialog();
+ i.configureDialog(i);
+ me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
// dialog used by singleplayer
me.winnerDialog = i = spawnXonoticWinnerDialog();
GAMETYPE(MAPINFO_TYPE_LMS) \
GAMETYPE(MAPINFO_TYPE_NEXBALL) \
GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
- GAMETYPE(MAPINFO_TYPE_RACE) \
+ if (cvar("developer")) GAMETYPE(MAPINFO_TYPE_RACE) \
GAMETYPE(MAPINFO_TYPE_CTS) \
GAMETYPE(MAPINFO_TYPE_TEAM_DEATHMATCH) \
//GAMETYPE(MAPINFO_TYPE_INVASION) \
float i;
i = 0;
- #define GAMETYPE(id) if(i++ == cnt) return id;
+ #define GAMETYPE(id) { if(i++ == cnt) return id; }
GAMETYPES
#undef GAMETYPE
#define MEAN_EVALUATE(prefix) mean_evaluate(self,prefix##_accumulator,prefix##_count,prefix##_mean)
#define MEAN_DECLARE(prefix,m) float prefix##_mean = m; .float prefix##_count, prefix##_accumulator
+.float anticheat_fixangle_endtime;
+
float anticheat_div0_evade_evasion_delta;
.float anticheat_div0_evade_offset;
.vector anticheat_div0_evade_v_angle;
.vector anticheat_div0_strafebot_forward_prev;
MEAN_DECLARE(anticheat_div0_strafebot_new, 5);
+// Snap-aim detection: we track the average angular speed of aiming over time, in "radians per second".
+// Signal: a high-power mean. Cheaters will have high "signal" here.
+// Noise: a low-power mean. Active/shivery players will have high "noise" here.
+// Note one can always artificially add noise - so very high values of both signal and noise need to be checked too.
+MEAN_DECLARE(anticheat_idle_snapaim_signal, 5);
+MEAN_DECLARE(anticheat_idle_snapaim_noise, 1);
+
+// TEMP DEBUG STUFF.
+MEAN_DECLARE(anticheat_idle_snapaim_m2, 2);
+MEAN_DECLARE(anticheat_idle_snapaim_m3, 3);
+MEAN_DECLARE(anticheat_idle_snapaim_m4, 4);
+MEAN_DECLARE(anticheat_idle_snapaim_m7, 7);
+MEAN_DECLARE(anticheat_idle_snapaim_m10, 10);
+
.float anticheat_speedhack_offset;
.float anticheat_speedhack_movetime, anticheat_speedhack_movetime_count, anticheat_speedhack_movetime_frac;
MEAN_DECLARE(anticheat_speedhack, 5);
+.float anticheat_speedhack_accu;
+.float anticheat_speedhack_lasttime;
+MEAN_DECLARE(anticheat_speedhack_m1, 1);
+MEAN_DECLARE(anticheat_speedhack_m2, 2);
+MEAN_DECLARE(anticheat_speedhack_m3, 3);
+MEAN_DECLARE(anticheat_speedhack_m4, 4);
+MEAN_DECLARE(anticheat_speedhack_m5, 5);
+
float movement_oddity(vector m0, vector m1)
{
float cosangle = normalize(m0) * normalize(m1);
if(self.anticheat_div0_evade_offset == 0)
{
f = fabs(anticheat_div0_evade_evasion_delta - floor(anticheat_div0_evade_evasion_delta) - 0.5) * 2; // triangle function
- self.anticheat_div0_evade_offset = time + sys_frametime * (3 * f - 1);
+ self.anticheat_div0_evade_offset = servertime + sys_frametime * (3 * f - 1);
self.anticheat_div0_evade_v_angle = self.v_angle;
self.anticheat_div0_evade_forward_initial = v_forward;
MEAN_ACCUMULATE(anticheat_div0_evade, 0, 1);
MEAN_ACCUMULATE(anticheat_div0_strafebot_old, movement_oddity(self.movement, self.anticheat_div0_strafebot_movement_prev), 1);
self.anticheat_div0_strafebot_movement_prev = self.movement;
- if(vlen(self.anticheat_div0_strafebot_forward_prev))
- MEAN_ACCUMULATE(anticheat_div0_strafebot_new, 0.5 - 0.5 * (self.anticheat_div0_strafebot_forward_prev * v_forward), 1);
+ // Note: this actually tries to detect snap-aim.
+ if(vlen(self.anticheat_div0_strafebot_forward_prev) && time > self.anticheat_fixangle_endtime) {
+ float cosangle = self.anticheat_div0_strafebot_forward_prev * v_forward;
+ float angle = cosangle < -1 ? M_PI : cosangle > 1 ? 0 : acos(cosangle);
+ /*
+ if (angle >= 10 * M_PI / 180)
+ printf("SNAP %s: %f for %f, %f since fixangle\n", self.netname, angle * 180 / M_PI, cosangle, time - self.anticheat_fixangle_endtime);
+ */
+ MEAN_ACCUMULATE(anticheat_div0_strafebot_new, angle / M_PI, 1);
+
+ if (autocvar_slowmo > 0) {
+ // Technically this is a NOP, as the engine should be ensuring
+ // this in the first place. Let's guard against dividing by
+ // zero anyway.
+ float dt = max(0.001, frametime) / autocvar_slowmo;
+
+ float anglespeed = angle / dt;
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_signal, anglespeed, dt);
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_noise, anglespeed, dt);
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_m2, anglespeed, dt);
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_m3, anglespeed, dt);
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_m4, anglespeed, dt);
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_m7, anglespeed, dt);
+ MEAN_ACCUMULATE(anticheat_idle_snapaim_m10, anglespeed, dt);
+ }
+ }
self.anticheat_div0_strafebot_forward_prev = v_forward;
// generic speedhack detection: correlate anticheat_speedhack_movetime (UPDATED BEFORE THIS) and server time
self.anticheat_speedhack_offset += (f - self.anticheat_speedhack_offset) * frametime * 0.1;
}
+ // new generic speedhack detection
+ if (self.anticheat_speedhack_lasttime > 0) {
+ float dt = servertime - self.anticheat_speedhack_lasttime;
+ const float falloff = 0.2;
+ self.anticheat_speedhack_accu *= exp(-dt * falloff);
+ self.anticheat_speedhack_accu += frametime * falloff;
+ // NOTE: at cl_netfps x, this actually averages not to 1, but to 1/x * falloff / (1 - exp(-1/x * falloff))
+ // For 15 netfps (absolute minimum bearable), and 0.2 falloff, this is: 1.0067
+ self.anticheat_speedhack_lasttime = servertime;
+ MEAN_ACCUMULATE(anticheat_speedhack_m1, self.anticheat_speedhack_accu, frametime);
+ MEAN_ACCUMULATE(anticheat_speedhack_m2, self.anticheat_speedhack_accu, frametime);
+ MEAN_ACCUMULATE(anticheat_speedhack_m3, self.anticheat_speedhack_accu, frametime);
+ MEAN_ACCUMULATE(anticheat_speedhack_m4, self.anticheat_speedhack_accu, frametime);
+ MEAN_ACCUMULATE(anticheat_speedhack_m5, self.anticheat_speedhack_accu, frametime);
+ } else {
+ self.anticheat_speedhack_accu = 1;
+ self.anticheat_speedhack_lasttime = servertime;
+ }
+
// race/CTS: force kbd movement for fairness
if(g_race || g_cts)
{
{
if(!autocvar_sv_eventlog)
return;
+ // TODO(divVerent): Use xonstat to acquire good thresholds.
GameLogEcho(strcat(":anticheat:_time:", ftos(self.playerid), ":", ftos(servertime - self.anticheat_jointime)));
GameLogEcho(strcat(":anticheat:speedhack:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack), 240, 0.1, 0.15)));
+ GameLogEcho(strcat(":anticheat:speedhack_m1:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m1), 240, 0.1, 0.15)));
+ GameLogEcho(strcat(":anticheat:speedhack_m2:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m2), 240, 0.1, 0.15)));
+ GameLogEcho(strcat(":anticheat:speedhack_m3:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m3), 240, 0.1, 0.15)));
+ GameLogEcho(strcat(":anticheat:speedhack_m4:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m4), 240, 0.1, 0.15)));
+ GameLogEcho(strcat(":anticheat:speedhack_m5:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m5), 240, 0.1, 0.15)));
GameLogEcho(strcat(":anticheat:div0_strafebot_old:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_strafebot_old), 120, 0.3, 0.4)));
GameLogEcho(strcat(":anticheat:div0_strafebot_new:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_strafebot_new), 120, 0.3, 0.4)));
GameLogEcho(strcat(":anticheat:div0_evade:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_evade), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_signal) - MEAN_EVALUATE(anticheat_idle_snapaim_noise), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_signal:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_signal), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_noise:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_noise), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_m2:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m2), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_m3:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m3), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_m4:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m4), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_m7:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m7), 120, 0.1, 0.2)));
+ GameLogEcho(strcat(":anticheat:idle_snapaim_m10:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m10), 120, 0.1, 0.2)));
+}
+
+float anticheat_getvalue(string id)
+{
+ switch(id) {
+ case "_time": return servertime - self.anticheat_jointime;
+ case "speedhack": return MEAN_EVALUATE(anticheat_speedhack);
+ case "speedhack_m1": return MEAN_EVALUATE(anticheat_speedhack_m1);
+ case "speedhack_m2": return MEAN_EVALUATE(anticheat_speedhack_m2);
+ case "speedhack_m3": return MEAN_EVALUATE(anticheat_speedhack_m3);
+ case "speedhack_m4": return MEAN_EVALUATE(anticheat_speedhack_m4);
+ case "speedhack_m5": return MEAN_EVALUATE(anticheat_speedhack_m5);
+ case "div0_strafebot_old": return MEAN_EVALUATE(anticheat_div0_strafebot_old);
+ case "div0_strafebot_new": return MEAN_EVALUATE(anticheat_div0_strafebot_new);
+ case "div0_evade": return MEAN_EVALUATE(anticheat_div0_evade);
+ case "idle_snapaim": return MEAN_EVALUATE(anticheat_idle_snapaim_signal) - MEAN_EVALUATE(anticheat_idle_snapaim_noise);
+ case "idle_snapaim_signal": return MEAN_EVALUATE(anticheat_idle_snapaim_signal);
+ case "idle_snapaim_noise": return MEAN_EVALUATE(anticheat_idle_snapaim_noise);
+ case "idle_snapaim_m2": return MEAN_EVALUATE(anticheat_idle_snapaim_m2);
+ case "idle_snapaim_m3": return MEAN_EVALUATE(anticheat_idle_snapaim_m3);
+ case "idle_snapaim_m4": return MEAN_EVALUATE(anticheat_idle_snapaim_m4);
+ case "idle_snapaim_m7": return MEAN_EVALUATE(anticheat_idle_snapaim_m7);
+ case "idle_snapaim_m10": return MEAN_EVALUATE(anticheat_idle_snapaim_m10);
+ }
+ return -1;
+}
+
+void anticheat_startframe()
+{
+ anticheat_div0_evade_evasion_delta += frametime * (0.5 + random());
+}
+
+void anticheat_fixangle()
+{
+ self.anticheat_fixangle_endtime = servertime + ANTILAG_LATENCY(self) + 0.2;
}
-void anticheat_serverframe()
+void anticheat_endframe()
{
+ entity oldself = self;
+ FOR_EACH_CLIENT(self)
+ if (self.fixangle)
+ anticheat_fixangle();
+ self = oldself;
anticheat_div0_evade_evasion_delta += frametime * (0.5 + random());
}
void anticheat_spectatecopy(entity spectatee);
void anticheat_prethink();
-void anticheat_serverframe();
+float anticheat_getvalue(string name);
+
+void anticheat_startframe();
+void anticheat_endframe();
+
+void anticheat_fixangle();
float autocvar_g_physical_items_reset;
float autocvar_g_monsters;
float autocvar_g_monsters_edit;
+float autocvar_g_monsters_sounds;
float autocvar_g_monsters_think_delay;
float autocvar_g_monsters_max;
float autocvar_g_monsters_max_perplayer;
float autocvar_g_touchexplode_edgedamage;
float autocvar_g_touchexplode_force;
float autocvar_g_invasion_round_timelimit;
-#define autocvar_g_invasion_round_limit cvar("g_invasion_round_limit")
+float autocvar_g_invasion_teams;
+float autocvar_g_invasion_team_spawns;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
float autocvar_g_invasion_warmup;
float autocvar_g_invasion_monster_count;
float autocvar_g_invasion_zombies_only;
vector dst_ahead, dst_down;
makevectors(self.v_angle_y * '0 1 0');
dst_ahead = self.origin + self.view_ofs + (self.velocity * 0.4) + (v_forward * 32 * 3);
- dst_down = dst_ahead + '0 0 -1500';
+ dst_down = dst_ahead - '0 0 1500';
// Look ahead
- traceline(self.origin + self.view_ofs , dst_ahead, TRUE, world);
+ traceline(self.origin + self.view_ofs, dst_ahead, TRUE, world);
// Check head-banging against walls
if(vlen(self.origin + self.view_ofs - trace_endpos) < 25 && !(self.aistatus & AI_STATUS_OUT_WATER))
}
// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
-entity navigation_findnearestwaypoint_withdist(entity ent, float walkfromwp, float bestdist)
+entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
{
entity waylist, w, best;
vector v, org, pm1, pm2;
while (w)
{
// if object is touching spawnfunc_waypoint
- if(w != ent)
+ if(w != ent && w != except)
if (boxesoverlap(pm1, pm2, w.absmin, w.absmax))
return w;
w = w.chain;
}
entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
{
- return navigation_findnearestwaypoint_withdist(ent, walkfromwp, 1050);
+ entity wp = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, world);
+ if (autocvar_g_waypointeditor_auto)
+ {
+ entity wp2 = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, wp);
+ if (!wp2)
+ wp.wpflags |= WAYPOINTFLAG_PROTECTED;
+ }
+ return wp;
}
// finds the waypoints near the bot initiating a navigation query
dprint(".waypoints.hardwired\n");
}
+entity waypoint_get_link(entity w, float i)
+{
+ switch(i)
+ {
+ case 0:return w.wp00;
+ case 1:return w.wp01;
+ case 2:return w.wp02;
+ case 3:return w.wp03;
+ case 4:return w.wp04;
+ case 5:return w.wp05;
+ case 6:return w.wp06;
+ case 7:return w.wp07;
+ case 8:return w.wp08;
+ case 9:return w.wp09;
+ case 10:return w.wp10;
+ case 11:return w.wp11;
+ case 12:return w.wp12;
+ case 13:return w.wp13;
+ case 14:return w.wp14;
+ case 15:return w.wp15;
+ case 16:return w.wp16;
+ case 17:return w.wp17;
+ case 18:return w.wp18;
+ case 19:return w.wp19;
+ case 20:return w.wp20;
+ case 21:return w.wp21;
+ case 22:return w.wp22;
+ case 23:return w.wp23;
+ case 24:return w.wp24;
+ case 25:return w.wp25;
+ case 26:return w.wp26;
+ case 27:return w.wp27;
+ case 28:return w.wp28;
+ case 29:return w.wp29;
+ case 30:return w.wp30;
+ case 31:return w.wp31;
+ default:return world;
+ }
+}
+
// Save all waypoint links to a file
void waypoint_save_links()
{
for(i=0;i<32;++i)
{
// :S
- link = world;
- switch(i)
- {
- // for i in $(seq -w 0 31); do echo "case $i:link = w.wp$i; break;"; done;
- case 0:link = w.wp00; break;
- case 1:link = w.wp01; break;
- case 2:link = w.wp02; break;
- case 3:link = w.wp03; break;
- case 4:link = w.wp04; break;
- case 5:link = w.wp05; break;
- case 6:link = w.wp06; break;
- case 7:link = w.wp07; break;
- case 8:link = w.wp08; break;
- case 9:link = w.wp09; break;
- case 10:link = w.wp10; break;
- case 11:link = w.wp11; break;
- case 12:link = w.wp12; break;
- case 13:link = w.wp13; break;
- case 14:link = w.wp14; break;
- case 15:link = w.wp15; break;
- case 16:link = w.wp16; break;
- case 17:link = w.wp17; break;
- case 18:link = w.wp18; break;
- case 19:link = w.wp19; break;
- case 20:link = w.wp20; break;
- case 21:link = w.wp21; break;
- case 22:link = w.wp22; break;
- case 23:link = w.wp23; break;
- case 24:link = w.wp24; break;
- case 25:link = w.wp25; break;
- case 26:link = w.wp26; break;
- case 27:link = w.wp27; break;
- case 28:link = w.wp28; break;
- case 29:link = w.wp29; break;
- case 30:link = w.wp30; break;
- case 31:link = w.wp31; break;
- }
-
+ link = waypoint_get_link(w, i);
if(link==world)
continue;
return 1;
}
-float botframe_autowaypoints_createwp(vector v, entity p, .entity fld)
+float botframe_autowaypoints_createwp(vector v, entity p, .entity fld, float f)
{
entity w;
w = find(w, classname, "waypoint");
}
- waypoint_schedulerelink(p.fld = waypoint_spawn(v, v, 0));
+ waypoint_schedulerelink(p.fld = waypoint_spawn(v, v, f));
return 1;
}
if(wp)
{
- // if any WP w fulfills wp -> w -> porg, then switch from wp to w
+ // if any WP w fulfills wp -> w -> porg and w is closer than wp, then switch from wp to w
// if wp -> porg, then OK
float maxdist;
t = (tmin + tmax) * 0.5;
o = antilag_takebackorigin(p, time - t);
if(!botframe_autowaypoints_fixdown(o))
- return -1;
+ return -2;
o = trace_endpos;
if(wp)
}
print("spawning a waypoint for connecting to ", etos(wp), "\n");
- botframe_autowaypoints_createwp(o, p, fld);
+ botframe_autowaypoints_createwp(o, p, fld, 0);
return 1;
}
print("emergency: got no good nearby WP to build a link from, starting a new chain\n");
if(!botframe_autowaypoints_fixdown(p.origin))
return; // shouldn't happen, caught above
- botframe_autowaypoints_createwp(trace_endpos, p, fld);
+ botframe_autowaypoints_createwp(trace_endpos, p, fld, WAYPOINTFLAG_PROTECTED);
+}
+
+void botframe_deleteuselesswaypoints()
+{
+ entity w, w1, w2;
+ float i, j, k;
+ for (w = world; (w = findfloat(w, bot_pickup, TRUE)); )
+ {
+ // NOTE: this protects waypoints if they're the ONLY nearest
+ // waypoint. That's the intention.
+ navigation_findnearestwaypoint(w, FALSE); // Walk TO item.
+ navigation_findnearestwaypoint(w, TRUE); // Walk FROM item.
+ }
+ for (w = world; (w = find(w, classname, "waypoint")); )
+ {
+ w.wpflags &= ~WAYPOINTFLAG_USEFUL;
+ // WP is useful if:
+ if (w.wpflags & WAYPOINTFLAG_ITEM)
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ if (w.wpflags & WAYPOINTFLAG_TELEPORT)
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ if (w.wpflags & WAYPOINTFLAG_PROTECTED)
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ // b) WP is closest WP for an item/spawnpoint/other entity
+ // This has been done above by protecting these WPs.
+ }
+ // c) There are w1, w, w2 so that w1 -> w, w -> w2 and not w1 -> w2.
+ for (w1 = world; (w1 = find(w1, classname, "waypoint")); )
+ {
+ if (w1.wpflags & WAYPOINTFLAG_PERSONAL)
+ continue;
+ for (i = 0; i < 32; ++i)
+ {
+ w = waypoint_get_link(w1, i);
+ if (!w)
+ break;
+ if (w.wpflags & WAYPOINTFLAG_PERSONAL)
+ continue;
+ if (w.wpflags & WAYPOINTFLAG_USEFUL)
+ continue;
+ for (j = 0; j < 32; ++j)
+ {
+ w2 = waypoint_get_link(w1, i);
+ if (!w2)
+ break;
+ if (w2.wpflags & WAYPOINTFLAG_PERSONAL)
+ continue;
+ for (k = 0; k < 32; ++k)
+ {
+ if (waypoint_get_link(w1, k) == w2)
+ continue;
+ // IF WE GET HERE, w is proven useful
+ // to get from w1 to w2!
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ continue;
+ }
+ }
+ }
+ }
+ for (w = world; (w = find(w, classname, "waypoint")); )
+ {
+ if (!(w.wpflags & WAYPOINTFLAG_USEFUL))
+ {
+ printf("Removed a waypoint at %v. Try again for more!\n", w.origin);
+ te_explosion(w.origin);
+ waypoint_remove(w);
+ break;
+ }
+ }
+ for (w = world; (w = find(w, classname, "waypoint")); )
+ w.wpflags &= ~WAYPOINTFLAG_USEFUL; // temp flag
}
void botframe_autowaypoints()
botframe_autowaypoints_fix(p, TRUE, botframe_autowaypoints_lastwp1);
//te_explosion(p.botframe_autowaypoints_lastwp0.origin);
}
+
+ botframe_deleteuselesswaypoints();
}
const float WAYPOINTFLAG_TELEPORT = 2097152;
const float WAYPOINTFLAG_NORELINK = 1048576;
const float WAYPOINTFLAG_PERSONAL = 524288;
+const float WAYPOINTFLAG_PROTECTED = 262144; // Useless WP detection never kills these.
+const float WAYPOINTFLAG_USEFUL = 131072; // Useless WP detection temporary flag.
// fields you can query using prvm_global server to get some statistics about waypoint linking culling
float relink_total, relink_walkculled, relink_pvsculled, relink_lengthculled;
if(teamplay)
{
string s;
- s = Team_ColorName_Lower(self.team);
+ s = Static_Team_ColorName_Lower(self.team);
if(s != "neutral")
{
defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s));
// add a way to see what the items were BEFORE all of these checks for the mutator hook
olditems = self.items;
- if((self.items & IT_USING_JETPACK) && !self.deadflag)
+ if((self.items & IT_USING_JETPACK) && !self.deadflag && !gameover)
self.modelflags |= MF_ROCKET;
else
self.modelflags &= ~MF_ROCKET;
*/
void PlayerJump (void)
{
+ if(self.player_blocked)
+ return; // no jumping while blocked
+
float doublejump = FALSE;
player_multijump = doublejump;
maxspd_mod = 1;
if(self.ballcarried)
- if(g_nexball)
- maxspd_mod *= autocvar_g_nexball_basketball_carrier_highspeed;
- else if(g_keepaway)
+ if(g_keepaway)
maxspd_mod *= autocvar_g_keepaway_ballcarrier_highspeed;
maxspd_mod *= autocvar_g_movement_highspeed;
Portal_ClearAllLater(self);
- if(IS_REAL_CLIENT(self))
- {
- self.fixangle = TRUE;
- //msg_entity = self;
- //WriteByte (MSG_ONE, SVC_SETANGLE);
- //WriteAngle (MSG_ONE, self.v_angle_x);
- //WriteAngle (MSG_ONE, self.v_angle_y);
- //WriteAngle (MSG_ONE, 80);
- }
+ self.fixangle = TRUE;
if(defer_ClientKill_Now_TeamChange)
ClientKill_Now_TeamChange(); // can turn player into spectator
// 0 = reject
// -1 = fake accept
{
- string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr;
+ string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
float flood;
var .float flood_field;
entity head;
else
namestr = source.netname;
+ if(strdecolorize(namestr) == namestr)
+ colorprefix = "^3";
+ else
+ colorprefix = "^7";
+
if(msgin != "")
{
if(privatesay)
{
- msgstr = strcat("\{1}\{13}* ^3", namestr, "^3 tells you: ^7");
+ msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
privatemsgprefixlen = strlen(msgstr);
msgstr = strcat(msgstr, msgin);
- cmsgstr = strcat(colorstr, "^3", namestr, "^3 tells you:\n^7", msgin);
+ cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
if(autocvar_g_chat_teamcolors)
privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
else
}
else if(teamsay)
{
- msgstr = strcat("\{1}\{13}", colorstr, "(^3", namestr, colorstr, ") ^7", msgin);
- cmsgstr = strcat(colorstr, "(^3", namestr, colorstr, ")\n^7", msgin);
+ msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+ cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
}
else
{
- msgstr = strcat("\{1}", namestr, "^7: ", msgin);
+ msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
cmsgstr = "";
}
msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
org = player.origin + player.view_ofs;
traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);
- if(IS_CLIENT(trace_ent))
+ if(IS_CLIENT(trace_ent) || (trace_ent.flags & FL_MONSTER))
{
antilag_takeback(trace_ent, time - lag);
hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);
{
entity e;
string tospawn;
- float moveflag;
+ float moveflag, monstercount = 0;
moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
tospawn = strtolower(argv(1));
return;
}
+ FOR_EACH_MONSTER(e)
+ {
+ if(e.realowner == self)
+ ++monstercount;
+ }
+
if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; }
else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; }
else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; }
else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; }
else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; }
+ else if(self.freezetag_frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; }
else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; }
else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; }
- else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
+ else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; }
else if(tospawn != "")
{
}
}
- if(found)
+ if(found || tospawn == "random")
{
- self.monstercount += 1;
totalspawned += 1;
makevectors(self.v_angle);
WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
//WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
- e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
+ e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, FALSE, moveflag);
sprint(self, strcat("Spawned ", e.monster_name, "\n"));
{
if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
{
+ CheckAllowedTeams(self);
GetTeamCounts(self);
- if(!TeamSmallerEqThanTeam(selection, self.team, self))
+ if(!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(self.team), self))
{
sprint(self, "Cannot change to a larger/better/shinier team\n");
return;
}
MapInfo_ClearTemps();
- return sprintf("^7Maps available%s: %s\n", (newmaps ? " (New maps have asterisks marked in blue)" : ""), lsmaps);
+ return sprintf("^7Maps available (%d)%s: %s\n", tokenize_console(lsmaps), (newmaps ? " (New maps have asterisks marked in blue)" : ""), lsmaps);
}
string getmonsterlist()
++removed_count;
}
- FOR_EACH_PLAYER(head)
- head.monstercount = 0;
-
monsters_total = 0; // reset stats?
monsters_killed = 0;
{
case "debug":
{
+ float hitcount = 0;
print("TEST CASE. If this returns the runaway loop counter error, possibly everything is oaky.\n");
+ float worst_endpos_bug = 0;
for(;;)
{
org = world.mins;
end = stov(vtos(end));
tracebox(start, PL_MIN, PL_MAX, end, MOVE_NOMONSTERS, world);
- if(!trace_startsolid)
+ if(!trace_startsolid && trace_fraction < 1)
{
p = trace_endpos;
tracebox(p, PL_MIN, PL_MAX, p, MOVE_NOMONSTERS, world);
- if(trace_startsolid || trace_fraction == 1)
+ if(trace_startsolid)
{
rint(42); // do an engine breakpoint on VM_rint so you can get the trace that errnoeously returns startsolid
tracebox(start, PL_MIN, PL_MAX, end, MOVE_NOMONSTERS, world);
- if(trace_startsolid)
+ // how much do we need to back off?
+ safe = 1;
+ unsafe = 0;
+ for(;;)
{
- // how much do we need to back off?
- safe = 1;
- unsafe = 0;
- for(;;)
+ pos = p * (1 - (safe + unsafe) * 0.5) + start * ((safe + unsafe) * 0.5);
+ tracebox(pos, PL_MIN, PL_MAX, pos, MOVE_NOMONSTERS, world);
+ if(trace_startsolid)
{
- pos = p * (1 - (safe + unsafe) * 0.5) + start * ((safe + unsafe) * 0.5);
- tracebox(pos, PL_MIN, PL_MAX, pos, MOVE_NOMONSTERS, world);
- if(trace_startsolid)
- {
- if((safe + unsafe) * 0.5 == unsafe)
- break;
- unsafe = (safe + unsafe) * 0.5;
- }
- else
- {
- if((safe + unsafe) * 0.5 == safe)
- break;
- safe = (safe + unsafe) * 0.5;
- }
+ if((safe + unsafe) * 0.5 == unsafe)
+ break;
+ unsafe = (safe + unsafe) * 0.5;
}
-
- print("safe distance to back off: ", ftos(safe * vlen(p - start)), "qu\n");
- print("unsafe distance to back off: ", ftos(unsafe * vlen(p - start)), "qu\n");
-
- tracebox(p, PL_MIN + '0.1 0.1 0.1', PL_MAX - '0.1 0.1 0.1', p, MOVE_NOMONSTERS, world);
- if(trace_startsolid)
- print("trace_endpos much in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
else
- print("trace_endpos just in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
- break;
+ {
+ if((safe + unsafe) * 0.5 == safe)
+ break;
+ safe = (safe + unsafe) * 0.5;
+ }
}
+ print("safe distance to back off: ", ftos(safe * vlen(p - start)), "qu\n");
+ print("unsafe distance to back off: ", ftos(unsafe * vlen(p - start)), "qu\n");
+
+ tracebox(p, PL_MIN + '0.1 0.1 0.1', PL_MAX - '0.1 0.1 0.1', p, MOVE_NOMONSTERS, world);
+ if(trace_startsolid)
+ print("trace_endpos much in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
+ else
+ print("trace_endpos just in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
+ if (++hitcount >= 10)
+ break;
+ }
+ else
+ {
q0 = p;
dq = 0;
dqf = 1;
dqf *= 0.5;
q0 = q;
}
- if(dq > 0)
+ if(dq > worst_endpos_bug)
{
+ worst_endpos_bug = dq;
print("trace_endpos still before solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
print("could go ", ftos(dq), " units further to ", vtos(q), "\n");
- break;
+ if (++hitcount >= 10)
+ break;
}
}
}
e.fire_hitsound = TRUE;
if (!IS_INDEPENDENT_PLAYER(e))
+ if(!e.freezetag_frozen)
FOR_EACH_PLAYER(other) if(e != other)
{
if(IS_PLAYER(other))
// - for this timelimit_overtime needs to be >0 of course
// - also check the winning condition calculated in the previous frame and only add normal overtime
// again, if at the point at which timelimit would be extended again, still no winner was found
- if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
+ if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
{
return 1; // need to call InitiateOvertime later
}
void EndFrame()
{
+ anticheat_endframe();
+
float altime;
FOR_EACH_REALCLIENT(self)
{
if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
dprint("wtf the flag got squashed?\n");
tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
- if(!trace_startsolid) // can we resize it without getting stuck?
+ if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
setsize(self, FLAG_MIN, FLAG_MAX); }
switch(self.ctf_status) // reset flag angles in case warpzones adjust it
// special touch behaviors
if(toucher.vehicle_flags & VHF_ISVEHICLE)
{
- if(autocvar_g_ctf_allow_vehicle_touch)
+ if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
toucher = toucher.owner; // the player is actually the vehicle owner, not other
else
return; // do nothing
-void invasion_spawnpoint()
+void spawnfunc_invasion_spawnpoint()
{
if(!g_invasion) { remove(self); return; }
self.classname = "invasion_spawnpoint";
+
+ if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
+ if(self.monsterid)
+ MON_ACTION(self.monsterid, MR_PRECACHE);
}
float invasion_PickMonster(float supermonster_count)
if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
continue; // flying/swimming monsters not yet supported
- RandomSelection_Add(world, i, "", 1, 1);
+ RandomSelection_Add(world, i, string_null, 1, 1);
}
return RandomSelection_chosen_float;
RandomSelection_Init();
for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
- RandomSelection_Add(e, 0, string_null, 1, 1);
+ {
+ RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+ e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+ }
return RandomSelection_chosen_ent;
}
if(spawn_point == world)
{
- dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
- return;
+ dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
+ entity e = spawn();
+ setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
+
+ if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ monster = spawnmonster("", mon, world, world, e.origin, FALSE, FALSE, 2);
+ else return;
+
+ e.think = SUB_Remove;
+ e.nextthink = time + 0.1;
+ }
+ else
+ monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, FALSE, FALSE, 2);
+
+ if(spawn_point) monster.target2 = spawn_point.target2;
+ monster.spawnshieldtime = time;
+ if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
+
+ if(teamplay)
+ if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+ monster.team = spawn_point.team;
+ else
+ {
+ RandomSelection_Init();
+ if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
+ if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
+ if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
+ if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
+
+ monster.team = RandomSelection_chosen_float;
}
+
+ if(teamplay)
+ {
+ monster_setupcolors(monster);
+
+ if(monster.sprite)
+ {
+ WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
- monster = spawnmonster("", mon, spawn_point, spawn_point, spawn_point.origin, FALSE, 2);
+ monster.sprite.team = 0;
+ monster.sprite.SendFlags |= 1;
+ }
+ }
+
+ monster.monster_attack = FALSE; // it's the player's job to kill all the monsters
if(inv_roundcnt >= inv_maxrounds)
monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
FOR_EACH_MONSTER(head)
monster_remove(head);
- if(inv_roundcnt >= inv_maxrounds)
- {
- NextLevel();
- return 1;
- }
-
Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
return 1;
}
- float total_alive_monsters = 0, supermonster_count = 0;
+ float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;;
FOR_EACH_MONSTER(head) if(head.health > 0)
{
if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
++supermonster_count;
++total_alive_monsters;
+
+ if(teamplay)
+ switch(head.team)
+ {
+ case NUM_TEAM_1: ++red_alive; break;
+ case NUM_TEAM_2: ++blue_alive; break;
+ case NUM_TEAM_3: ++yellow_alive; break;
+ case NUM_TEAM_4: ++pink_alive; break;
+ }
}
- if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < 10) // 10 at a time should be plenty
+ if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
{
if(time >= inv_lastcheck)
{
return 0;
}
- if(inv_numspawned < 1 || inv_numkilled < inv_maxspawned)
- return 0; // nothing has spawned yet, or there are still alive monsters
-
- if(inv_roundcnt >= inv_maxrounds)
+ if(inv_numspawned < 1)
+ return 0; // nothing has spawned yet
+
+ if(teamplay)
{
- NextLevel();
- return 1;
+ if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+ return 0;
}
+ else if(inv_numkilled < inv_maxspawned)
+ return 0;
entity winner = world;
- float winning_score = 0;
+ float winning_score = 0, winner_team = 0;
+
+ if(teamplay)
+ {
+ if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+ if(blue_alive > 0)
+ if(winner_team) { winner_team = 0; }
+ else { winner_team = NUM_TEAM_2; }
+ if(yellow_alive > 0)
+ if(winner_team) { winner_team = 0; }
+ else { winner_team = NUM_TEAM_3; }
+ if(pink_alive > 0)
+ if(winner_team) { winner_team = 0; }
+ else { winner_team = NUM_TEAM_4; }
+ }
+ else
FOR_EACH_PLAYER(head)
{
float cs = PlayerScore_Add(head, SP_KILLS, 0);
FOR_EACH_MONSTER(head)
monster_remove(head);
- if(winner)
+ if(teamplay)
+ {
+ if(winner_team)
+ {
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+ }
+ }
+ else if(winner)
{
Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
++numplayers;
}
- inv_roundcnt += 1;
+ if(inv_roundcnt < inv_maxrounds)
+ inv_roundcnt += 1; // a limiter to stop crazy counts
- inv_monsterskill = inv_roundcnt + (numplayers * 0.3);
+ inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
inv_maxcurrent = 0;
inv_numspawned = 0;
inv_numkilled = 0;
- inv_maxspawned = rint(min(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+ inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+ if(teamplay)
+ {
+ DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+ inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+ inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+ if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+ if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+ }
}
MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
{
inv_numkilled += 1;
inv_maxcurrent -= 1;
+ if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
if(IS_PLAYER(frag_attacker))
+ if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
+ PlayerScore_Add(frag_attacker, SP_KILLS, -1);
+ else
+ {
PlayerScore_Add(frag_attacker, SP_KILLS, +1);
+ if(teamplay)
+ TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+ }
}
return FALSE;
MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
{
if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
- {
- monster_remove(self);
- return FALSE;
- }
+ return TRUE;
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
{
return FALSE;
}
-MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
+MUTATOR_HOOKFUNCTION(invasion_OnEntityPreSpawn)
+{
+ if(startsWith(self.classname, "monster_"))
+ if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
+ return TRUE;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_StartFrame)
{
monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
monsters_killed = inv_numkilled;
return FALSE;
}
+MUTATOR_HOOKFUNCTION(invasion_BotShouldAttack)
+{
+ if(!(checkentity.flags & FL_MONSTER))
+ return TRUE;
+
+ return FALSE;
+}
+
MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
{
start_health = 200;
return TRUE;
}
-void invasion_ScoreRules()
+MUTATOR_HOOKFUNCTION(invasion_GetTeamCount)
{
- ScoreRules_basics(0, 0, 0, FALSE);
- ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+ ret_float = invasion_teams;
+ return FALSE;
+}
+
+void invasion_ScoreRules(float inv_teams)
+{
+ if(inv_teams) { CheckAllowedTeams(world); }
+ ScoreRules_basics(inv_teams, 0, 0, FALSE);
+ if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
ScoreRules_basics_end();
}
-void invasion_Initialize()
+void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
{
+ if(autocvar_g_invasion_teams)
+ invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
+ else
+ invasion_teams = 0;
+
independent_players = 1; // to disable extra useless scores
- invasion_ScoreRules();
+ invasion_ScoreRules(invasion_teams);
independent_players = 0;
allowed_to_spawn = TRUE;
inv_roundcnt = 0;
+ inv_maxrounds = 15; // 15?
+}
+
+void invasion_Initialize()
+{
+ if(autocvar_g_invasion_zombies_only)
+ MON_ACTION(MON_ZOMBIE, MR_PRECACHE);
+ else
+ {
+ float i;
+ entity mon;
+ for(i = MON_FIRST; i <= MON_LAST; ++i)
+ {
+ mon = get_monsterinfo(i);
+ if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
+ continue; // flying/swimming monsters not yet supported
+
+ MON_ACTION(i, MR_PRECACHE);
+ }
+ }
+
+ InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
}
MUTATOR_DEFINITION(gamemode_invasion)
{
MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
- MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(OnEntityPreSpawn, invasion_OnEntityPreSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SV_StartFrame, invasion_StartFrame, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerRegen, invasion_PlayerRegen, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
+ MUTATOR_HOOK(BotShouldAttack, invasion_BotShouldAttack, CBC_ORDER_ANY);
MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
MUTATOR_HOOK(AccuracyTargetValid, invasion_AccuracyTargetValid, CBC_ORDER_ANY);
MUTATOR_HOOK(AllowMobSpawning, invasion_AllowMobSpawning, CBC_ORDER_ANY);
+ MUTATOR_HOOK(GetTeamCount, invasion_GetTeamCount, CBC_ORDER_ANY);
MUTATOR_ONADD
{
float inv_lastcheck;
float inv_maxcurrent;
+float invasion_teams;
+float inv_monsters_perteam[17];
+
float inv_monsterskill;
+
+#define ST_INV_KILLS 1
if(gameover) { return; }
vector oldballorigin = self.origin;
- if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ if(!MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
{
- makevectors(self.angles);
- self.movetype = MOVETYPE_BOUNCE;
- self.velocity = '0 0 200';
- self.angles = '0 0 0';
- self.effects = autocvar_g_keepawayball_effects;
- self.think = ka_RespawnBall;
- self.nextthink = time + autocvar_g_keepawayball_respawntime;
+ entity spot = SelectSpawnPoint(TRUE);
+ setorigin(self, spot.origin);
+ self.angles = spot.angles;
+ }
- pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
- pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
+ makevectors(self.angles);
+ self.movetype = MOVETYPE_BOUNCE;
+ self.velocity = '0 0 200';
+ self.angles = '0 0 0';
+ self.effects = autocvar_g_keepawayball_effects;
+ self.think = ka_RespawnBall;
+ self.nextthink = time + autocvar_g_keepawayball_respawntime;
- WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1');
- WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+ pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
+ pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
- sound(self, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
- }
- else
- {
- ka_RespawnBall(); // finding a location failed, retry
- }
+ WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1');
+ WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+
+ sound(self, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
}
void ka_TimeScoring()
// messages and sounds
ka_EventLog("pickup", other);
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_PICKUP, other.netname);
- Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
+ Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
+ Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
sound(self.owner, CH_TRIGGER, "keepaway/pickedup.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
// scoring
}
}
+// scoreboard setup
+void nb_ScoreRules(float teams)
+{
+ ScoreRules_basics(teams, 0, 0, TRUE);
+ ScoreInfo_SetLabel_TeamScore( ST_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore( SP_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER);
+ ScoreRules_basics_end();
+}
+
void nb_delayedinit(void)
{
if(find(world, classname, "nexball_team") == world)
nb_spawnteams();
- ScoreRules_nexball(nb_teams);
+ nb_ScoreRules(nb_teams);
}
void SpawnBall(void)
{
- if(!g_nexball)
- {
- remove(self);
- return;
- }
+ if(!g_nexball) { remove(self); return; }
// balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine
SpawnBall();
}
+float nb_Goal_Customize()
+{
+ entity e, wp_owner;
+ e = WaypointSprite_getviewentity(other);
+ wp_owner = self.owner;
+ if(SAME_TEAM(e, wp_owner)) { return FALSE; }
+
+ return TRUE;
+}
+
void SpawnGoal(void)
{
- if(!g_nexball)
+ if(!g_nexball) { remove(self); return; }
+
+ EXACTTRIGGER_INIT;
+
+ if(self.team != GOAL_OUT && Team_TeamToNumber(self.team) != -1)
{
- remove(self);
- return;
+ WaypointSprite_SpawnFixed("goal", (self.absmin + self.absmax) * 0.5, self, sprite, RADARICON_NONE, ((self.team) ? Team_ColorRGB(self.team) : '1 0.5 0'));
+ self.sprite.customizeentityforclient = nb_Goal_Customize;
}
- EXACTTRIGGER_INIT;
+
self.classname = "nexball_goal";
if(self.noise == "")
self.noise = "ctf/respawn.wav";
PROJECTILE_TOUCH;
if(attacker.team != other.team || autocvar_g_nexball_basketball_teamsteal)
- if((ball = other.ballcarried) && (IS_PLAYER(attacker)))
+ if((ball = other.ballcarried) && !other.deadflag && (IS_PLAYER(attacker)))
{
other.velocity = other.velocity + normalize(self.velocity) * other.damageforcescale * autocvar_g_balance_nexball_secondary_force;
other.flags &= ~FL_ONGROUND;
LogNB("stole", attacker);
sound(other, CH_TRIGGER, ball.noise2, VOL_BASE, ATTEN_NORM);
- if(attacker.team == other.team && time > attacker.teamkill_complain)
+ if(SAME_TEAM(attacker, other) && time > attacker.teamkill_complain)
{
attacker.teamkill_complain = time + 5;
attacker.teamkill_soundtime = time + 0.4;
missile.movetype = MOVETYPE_FLY;
PROJECTILE_MAKETRIGGER(missile);
- setmodel(missile, "models/elaser.mdl"); // precision set below
+ //setmodel(missile, "models/elaser.mdl"); // precision set below
setsize(missile, '0 0 0', '0 0 0');
setorigin(missile, w_shotorg);
missile.effects = EF_BRIGHTFIELD | EF_LOWPRECISION;
missile.flags = FL_PROJECTILE;
+
+ CSQCProjectile(missile, TRUE, PROJECTILE_ELECTRO, TRUE);
}
float ball_customize()
return 0;
}
-MUTATOR_HOOKFUNCTION(nexball_BuildMutatorsString)
-{
- ret_string = strcat(ret_string, ":NB");
- return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nexball_BuildMutatorsPrettyString)
-{
- ret_string = strcat(ret_string, ", NexBall");
- return 0;
-}
-
MUTATOR_HOOKFUNCTION(nexball_PlayerPreThink)
{
makevectors(self.v_angle);
return FALSE;
}
+MUTATOR_HOOKFUNCTION(nexball_PlayerPhysics)
+{
+ if(self.ballcarried)
+ {
+ self.stat_sv_airspeedlimit_nonqw *= autocvar_g_nexball_basketball_carrier_highspeed;
+ self.stat_sv_maxspeed *= autocvar_g_nexball_basketball_carrier_highspeed;
+ }
+ return FALSE;
+}
+
MUTATOR_HOOKFUNCTION(nexball_SetStartItems)
{
start_items |= IT_UNLIMITED_SUPERWEAPONS; // FIXME BAD BAD BAD BAD HACK, NEXBALL SHOULDN'T ABUSE PORTO'S WEAPON SLOT
return FALSE;
}
+MUTATOR_HOOKFUNCTION(nexball_ForbidThrowing)
+{
+ if(self.weapon == WEP_GRENADE_LAUNCHER)
+ return TRUE;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(nexball_FilterItem)
+{
+ if(self.classname == "droppedweapon")
+ if(self.weapon == WEP_GRENADE_LAUNCHER)
+ return TRUE;
+
+ return FALSE;
+}
+
MUTATOR_DEFINITION(gamemode_nexball)
{
MUTATOR_HOOK(PlayerDies, nexball_BallDrop, CBC_ORDER_ANY);
MUTATOR_HOOK(MakePlayerObserver, nexball_BallDrop, CBC_ORDER_ANY);
MUTATOR_HOOK(ClientDisconnect, nexball_BallDrop, CBC_ORDER_ANY);
- MUTATOR_HOOK(BuildMutatorsPrettyString, nexball_BuildMutatorsPrettyString, CBC_ORDER_ANY);
- MUTATOR_HOOK(BuildMutatorsString, nexball_BuildMutatorsString, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerSpawn, nexball_PlayerSpawn, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerPreThink, nexball_PlayerPreThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPhysics, nexball_PlayerPhysics, CBC_ORDER_ANY);
MUTATOR_HOOK(SetStartItems, nexball_SetStartItems, CBC_ORDER_ANY);
+ MUTATOR_HOOK(ForbidThrowCurrentWeapon, nexball_ForbidThrowing, CBC_ORDER_ANY);
+ MUTATOR_HOOK(FilterItem, nexball_FilterItem, CBC_ORDER_ANY);
MUTATOR_ONADD
{
g_nexball_meter_period = rint(g_nexball_meter_period * 32) / 32; //Round to 1/32ths to send as a byte multiplied by 32
addstat(STAT_NB_METERSTART, AS_FLOAT, metertime);
+ w_porto(WR_PRECACHE); // abuse
+
// General settings
/*
CVTOV(g_nexball_football_boost_forward); //100
MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn)
{
+ // Note: when entering this, fixangle is already set.
if(autocvar_g_spawn_near_teammate_ignore_spawnpoint)
{
if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death)
setorigin(self, trace_endpos);
self.angles = team_mate.angles;
self.angles_z = 0; // never spawn tilted even if the spot says to
- self.fixangle = TRUE; // turn this way immediately
team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
return 0;
}
setorigin(self, best_spot);
self.angles = best_mate.angles;
self.angles_z = 0; // never spawn tilted even if the spot says to
- self.fixangle = TRUE; // turn this way immediately
best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
}
}
self.angles = vectoangles(spawn_spot.msnt_lookat.origin - self.origin);
self.angles_x = -self.angles_x;
self.angles_z = 0; // never spawn tilted even if the spot says to
- self.fixangle = TRUE; // turn this way immediately
/*
sprint(self, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n");
sprint(self, "distance: ", vtos(spawn_spot.msnt_lookat.origin - self.origin), "\n");
.float playerstats_addedglobalinfo;
.string playerstats_id;
+#define ALL_ANTICHEATS \
+ ANTICHEAT("_time"); \
+ ANTICHEAT("speedhack"); \
+ ANTICHEAT("speedhack_m1"); \
+ ANTICHEAT("speedhack_m2"); \
+ ANTICHEAT("speedhack_m3"); \
+ ANTICHEAT("speedhack_m4"); \
+ ANTICHEAT("speedhack_m5"); \
+ ANTICHEAT("div0_strafebot_old"); \
+ ANTICHEAT("div0_strafebot_new"); \
+ ANTICHEAT("div0_evade"); \
+ ANTICHEAT("idle_snapaim"); \
+ ANTICHEAT("idle_snapaim_signal"); \
+ ANTICHEAT("idle_snapaim_noise"); \
+ ANTICHEAT("idle_snapaim_m2"); \
+ ANTICHEAT("idle_snapaim_m3"); \
+ ANTICHEAT("idle_snapaim_m4"); \
+ ANTICHEAT("idle_snapaim_m7"); \
+ ANTICHEAT("idle_snapaim_m10");
+
void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly
{
string uri;
PlayerStats_AddEvent(strcat("acc-", w.netname, "-frags"));
}
+#define ANTICHEAT(name) \
+ PlayerStats_AddEvent("anticheat-" name)
+ ALL_ANTICHEATS
+#undef ANTICHEAT
+
PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
//backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n"));
}
+void PlayerStats_Anticheat(entity p)
+{
+ entity oldself = self;
+ self = p;
+
+#define ANTICHEAT(name) \
+ PlayerStats_Event(p, "anticheat-" name, anticheat_getvalue(name))
+ ALL_ANTICHEATS
+#undef ANTICHEAT
+ self = oldself;
+}
+
void PlayerStats_AddGlobalInfo(entity p)
{
if(playerstats_db < 0)
PlayerStats_Accuracy(p);
+ PlayerStats_Anticheat(p);
+
if(IS_REAL_CLIENT(p))
{
if(p.latency_cnt)
}
}
}
+
+#undef ALL_ANTICHEATS
FOR_EACH_PLAYER(self)
self.porto_forbidden = max(0, self.porto_forbidden - 1);
+ anticheat_startframe();
+
MUTATOR_CALLHOOK(SV_StartFrame);
}
void WarpZone_PostTeleportPlayer_Callback(entity pl)
{
UpdateCSQCProjectileAfterTeleport(pl);
+ {
+ entity oldself = self;
+ self = pl;
+ anticheat_fixangle();
+ self = oldself;
+ }
// "disown" projectiles after teleport
if(pl.owner)
if(pl.owner == pl.realowner)
{
if(!activator)
return;
+ if(!IS_REAL_CLIENT(activator))
+ return;
msg_entity = activator;
target_music_sendto(MSG_ONE, 1);
+ entity head;
+ FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); }
}
void spawnfunc_target_music()
{
if(g_invasion)
{
- timelimit_override = 0; // no timelimit in invasion, round based
- fraglimit_override = autocvar_g_invasion_round_limit;
+ fraglimit_override = autocvar_g_invasion_point_limit;
+ if(autocvar_g_invasion_teams >= 2)
+ {
+ ActivateTeamplay();
+ if(autocvar_g_invasion_team_spawns)
+ have_team_spawns = -1; // request team spawns
+ }
MUTATOR_ADD(gamemode_invasion);
}
else
g_race_qualifying = 0;
}
-
- if(g_invasion)
- {
- inv_maxrounds = cvar("fraglimit");
- cvar_set("fraglimit", "0");
- }
if(g_race || g_cts)
{
if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
lag = 0; // only do hitscan, but no antilag
if(lag)
+ {
FOR_EACH_PLAYER(pl)
if(pl != self)
antilag_takeback(pl, time - lag);
+ FOR_EACH_MONSTER(pl)
+ antilag_takeback(pl, time - lag);
+ }
WarpZone_trace_forent = self;
}
if(lag)
+ {
FOR_EACH_PLAYER(pl)
if(pl != self)
antilag_restore(pl);
+ FOR_EACH_MONSTER(pl)
+ antilag_restore(pl);
+ }
}
float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
// a player's mines shall explode if he disconnects or dies
// TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams?
- if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO)
+ if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.freezetag_frozen)
{
other = world;
self.projectiledeathtype |= HITTYPE_BOUNCE;
head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius);
while(head)
{
- if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
+ if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.freezetag_frozen)
if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates
if(!self.mine_time)
{