set g_norecoil 0 "if set to 1 shooting weapons won't make you crosshair to move upwards (recoil)"
set g_maplist_mostrecent "" "contains the name of the maps that were most recently played"
set g_maplist_mostrecent_count 3 "number of most recent maps that are blocked from being played again"
-set g_maplist "" "the list of maps to be cycled among (is autogenerated if empty)"
set g_maplist_index 0 "this is used internally for saving position in maplist cycle"
set g_maplist_selectrandom 0 "if 1, a random map will be chosen as next map - DEPRECATED in favor of g_maplist_shuffle"
set g_maplist_shuffle 1 "new randomization method: like selectrandom, but avoid playing the same maps in short succession. This works by taking out the first element and inserting it into g_maplist with a bias to the end of the list"
set _campaign_name ""
set _campaign_testrun 0 "To verify the campaign file, set this to 1, then start the first campaign level from the menu. If you end up in the menu again, it's good, if you get a QC crash, it's bad."
+// used by both server and menu to maintain the available list of maps
+seta g_maplist "" "the list of maps to be cycled among (is autogenerated if empty)"
+
// we must change its default from 1.0 to 1 to be consistent with menuqc
set slowmo 1
set g_instagib_ammo_convert_cells 0 "convert normal cell ammo packs to insta cell ammo packs"
set g_instagib_ammo_convert_rockets 0 "convert rocket ammo packs to insta cell ammo packs"
set g_instagib_ammo_convert_shells 0 "convert shell ammo packs to insta cell ammo packs"
-set g_instagib_invisibility_time 30 "Time of ivisibility powerup in seconds."
+set g_instagib_invisibility_time 30 "Time of invisibility powerup in seconds."
set g_instagib_invis_alpha 0.15
set g_instagib_speed_time 30 "Time of speed powerup in seconds."
set g_instagib_speed_highspeed 1.5 "speed-multiplier that applies while you carry the invincibility powerup"
#include <common/vehicles/all.qh>
#include <common/weapons/_all.qh>
#include <common/viewloc.qh>
+#include <common/triggers/trigger/viewloc.qh>
#include <common/minigames/cl_minigames.qh>
#include <common/minigames/cl_minigames_hud.qh>
HitSound();
}
+void ViewLocation_Mouse()
+{
+ if(spectatee_status)
+ return; // don't draw it as spectator!
+
+ viewloc_mousepos += getmousepos() * autocvar_menu_mouse_speed;
+ viewloc_mousepos.x = bound(0, viewloc_mousepos.x, vid_conwidth);
+ viewloc_mousepos.y = bound(0, viewloc_mousepos.y, vid_conheight);
+
+ float cursor_alpha = 1 - autocvar__menu_alpha;
+ draw_cursor(viewloc_mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+}
+
bool ov_enabled;
float oldr_nearclip;
float oldr_farclip_base;
button_zoom = false;
}
+ // abused multiple places below
+ entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
+ if(!local_player)
+ local_player = this; // fall back!
+
// event chase camera
if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
{
}
eventchase_running = true;
- entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
- if(!local_player)
- local_player = this; // fall back!
-
// 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);
if (custom_eventchase)
// reticle_type is changed to the item we are zooming / aiming with, to decide which reticle to use
// It must be a persisted float for fading out to work properly (you let go of the zoom button for
// the view to go back to normal, so reticle_type would become 0 as we fade out)
- if(spectatee_status || is_dead || hud != HUD_NORMAL)
+ if(spectatee_status || is_dead || hud != HUD_NORMAL || local_player.viewloc)
{
// no zoom reticle while dead
reticle_type = 0;
HUD_Minigame_Mouse();
else if(QuickMenu_IsOpened())
QuickMenu_Mouse();
+ else if(local_player.viewloc && (local_player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+ ViewLocation_Mouse(); // NOTE: doesn't use cursormode
else
HUD_Radar_Mouse();
vector crosshair_getcolor(entity this, float health_stat);
entity viewmodels[MAX_WEAPONSLOTS];
+
+vector viewloc_mousepos;
REGISTER_ITEM(Invisibility, Powerup) {
this.m_canonical_spawnfunc = "item_invisibility";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_INSTAGIB;
+ this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_Invisibility_ITEM;
this.m_sound = SND_Invisibility;
this.m_glow = true;
REGISTER_ITEM(Speed, Powerup) {
this.m_canonical_spawnfunc = "item_speed";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_INSTAGIB;
+ this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_Speed_ITEM;
this.m_sound = SND_Speed;
this.m_glow = true;
REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball)
{
- MUTATOR_ONADD
- {
- ITEM_VaporizerCells.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
- }
- MUTATOR_ONROLLBACK_OR_REMOVE
- {
- ITEM_VaporizerCells.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
- }
+ MUTATOR_ONADD
+ {
+ ITEM_VaporizerCells.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_Invisibility.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_Speed.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+ }
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ ITEM_VaporizerCells.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_Invisibility.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_Speed.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ }
}
void instagib_invisibility(entity this)
{
- this.strength_finished = autocvar_g_balance_powerup_strength_time;
+ this.strength_finished = autocvar_g_instagib_invisibility_time;
StartItem(this, ITEM_Invisibility);
}
void instagib_speed(entity this)
{
- this.invincible_finished = autocvar_g_balance_powerup_invincible_time;
+ this.invincible_finished = autocvar_g_instagib_speed_time;
StartItem(this, ITEM_Speed);
}
void Item_RespawnCountdown (entity this)
{
- if(this.count >= ITEM_RESPAWN_TICKS)
+ if(this.item_respawncounter >= ITEM_RESPAWN_TICKS)
{
if(this.waypointsprite_attached)
WaypointSprite_Kill(this.waypointsprite_attached);
else
{
this.nextthink = time + 1;
- this.count += 1;
- if(this.count == 1)
+ this.item_respawncounter += 1;
+ if(this.item_respawncounter == 1)
{
do {
{
});
WaypointSprite_Ping(this.waypointsprite_attached);
- //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.count);
+ //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.item_respawncounter);
}
}
}
setthink(e, Item_RespawnCountdown);
e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
e.scheduledrespawntime = e.nextthink + ITEM_RESPAWN_TICKS;
- e.count = 0;
+ e.item_respawncounter = 0;
if(Item_ItemsTime_Allow(e.itemdef) || (e.weapons & WEPSET_SUPERWEAPONS))
{
t = Item_ItemsTime_UpdateTime(e, e.scheduledrespawntime);
.float max_armorvalue;
.float pickup_anyway;
+.float item_respawncounter;
+
void Item_Show (entity e, float mode);
void Item_Respawn (entity this);
centerprint(targ, this.message);
}
else
+ {
targ.lastteleporttime = time;
+ targ.lastteleport_origin = targ.origin;
+ }
if (!IS_DEAD(targ))
animdecide_setaction(targ, ANIMACTION_JUMP, true);
if(trace_startsolid)
return false;
- if(!jp.height)
+ if (!jp.height)
{
// since tracetoss starting from jumppad's origin often fails when target
// is very close to real destination, start it directly from target's
// origin instead
+ vector ofs = '0 0 0';
+ if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed))
+ ofs = stepheightvec;
+
tracetest_ent.velocity.z = 0;
- setorigin(tracetest_ent, targ.origin + stepheightvec);
+ setorigin(tracetest_ent, targ.origin + ofs);
tracetoss(tracetest_ent, tracetest_ent);
- if(trace_startsolid)
+ if (trace_startsolid && ofs.z)
{
- setorigin(tracetest_ent, targ.origin + stepheightvec / 2);
+ setorigin(tracetest_ent, targ.origin + ofs / 2);
tracetoss(tracetest_ent, tracetest_ent);
- if(trace_startsolid)
+ if (trace_startsolid && ofs.z)
{
setorigin(tracetest_ent, targ.origin);
tracetoss(tracetest_ent, tracetest_ent);
- if(trace_startsolid)
+ if (trace_startsolid)
return false;
}
}
{
// first calculate a typical start point for the jump
vector org = (this.absmin + this.absmax) * 0.5;
- org.z = this.absmax.z - PL_MIN_CONST.z;
+ org.z = this.absmax.z - PL_MIN_CONST.z - 10;
if (this.target)
{
void viewloc_think(entity this)
{
- entity e;
-
// we abuse this method, rather than using normal .touch, because touch isn't reliable with multiple clients inside the same trigger, and can't "untouch" entities
// set myself as current viewloc where possible
+#if 1
+ FOREACH_CLIENT(it.viewloc == this,
+ {
+ it.viewloc = NULL;
+ });
+#else
+ entity e;
for(e = NULL; (e = findentity(e, viewloc, this)); )
e.viewloc = NULL;
+#endif
+
+#if 1
+ FOREACH_CLIENT(!it.viewloc && IS_PLAYER(it),
+ {
+ vector emin = it.absmin;
+ vector emax = it.absmax;
+ if(this.solid == SOLID_BSP)
+ {
+ emin -= '1 1 1';
+ emax += '1 1 1';
+ }
+ if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
+ {
+ if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
+ it.viewloc = this;
+ }
+ });
+#else
for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain)
if(!e.viewloc)
if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, e)) // accurate
e.viewloc = this;
}
+#endif
this.nextthink = time;
}
// CSQC doesn't need to know our origin (yet), as we're only available for referencing
WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC_TRIGGER);
+ WriteByte(MSG_ENTITY, this.spawnflags);
+
WriteEntity(MSG_ENTITY, this.enemy);
WriteEntity(MSG_ENTITY, this.goalentity);
NET_HANDLE(ENT_CLIENT_VIEWLOC_TRIGGER, bool isnew)
{
+ this.spawnflags = ReadByte();
+
float point1 = ReadShort();
float point2 = ReadShort();
.entity viewloc;
+const int VIEWLOC_NOSIDESCROLL = BIT(0); // NOTE: currently unimplemented
+const int VIEWLOC_FREEAIM = BIT(1);
+
#ifdef CSQC
.entity goalentity;
.entity enemy;
PHYS_CS(this).movement_x = old_movement_y;
PHYS_CS(this).movement_y = 0;
- if(PHYS_CS(this).movement_x < 0)
- PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
-
vector level_start, level_end;
level_start = this.viewloc.enemy.origin;
level_end = this.viewloc.goalentity.origin;
- vector forward, backward;
- forward = vectoangles(normalize(level_end - level_start));
- backward = vectoangles(normalize(level_start - level_end));
+ vector forward = vectoangles(normalize(level_end - level_start));
+ vector backward = vectoangles(normalize(level_start - level_end));
- if(PHYS_CS(this).movement_x < 0) // left
- this.angles_y = backward_y;
- if(PHYS_CS(this).movement_x > 0) // right
- this.angles_y = forward_y;
+ if(this.viewloc.spawnflags & VIEWLOC_FREEAIM)
+ {
+ if(this.angles_y > 0)
+ PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
+ }
+ else
+ {
+ if(PHYS_CS(this).movement_x < 0)
+ PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
- if(old_movement_x > 0)
-#ifdef CSQC
- input_angles_x =
-#endif
- this.v_angle_x = this.angles_x = -50;
- else if(old_movement_x < 0)
-#ifdef CSQC
- input_angles_x =
-#endif
- this.v_angle_x = this.angles_x = 50;
+ if(PHYS_CS(this).movement_x < 0) // left
+ this.angles_y = backward.y;
+ if(PHYS_CS(this).movement_x > 0) // right
+ this.angles_y = forward.y;
+ }
//if(!PHYS_INPUT_BUTTON_CROUCH(this) && !IS_DUCKED(this))
#ifdef SVQC
this.viewloc = findfloat(NULL, entnum, this.tag_networkviewloc);
}
+vector CursorToWorldCoord(vector mpos)
+{
+ vector wnear = cs_unproject(vec2(mpos)); // determine the world coordinate for the mouse cursor upon the near clip plane
+ vector wfar = cs_unproject(vec3(mpos.x, mpos.y, max_shot_distance)); // determine the world coordinate for the mouse cursor upon the far clip plane, with an outrageously large value as a workaround for dp.
+ traceline(wnear, wfar, MOVE_NOMONSTERS, NULL);
+ return trace_endpos;
+}
+
vector old_camera_angle = '0 0 0';
bool autocvar_cam_snap_close;
bool autocvar_cam_track;
if(view.viewloc && !wasfreed(view.viewloc) && view.viewloc.enemy && view.viewloc.goalentity)
{
bool have_sidescroll = (view.viewloc.enemy != view.viewloc.goalentity);
- vector position_a, position_b, camera_position, camera_angle = '0 0 0', forward, backward;
- //vector scratch;
-
- position_a = view.viewloc.enemy.origin;
- position_b = view.viewloc.goalentity.origin;
+ vector position_a = view.viewloc.enemy.origin;
+ vector position_b = view.viewloc.goalentity.origin;
+ vector camera_angle = '0 0 0';
+ vector camera_position;
-#if 0
/*TODO: have the camera only move when a player moves too much from the center of the camera
- * basically the player can move around in a "box" in the center of th screen with out changing the camera position or angles
- */
- if (cvar("cam_box")) {
- camera_position = vec_bounds_in(view.origin, position_a, position_b);
- }
- else
-#endif
- camera_position = vec_bounds_in(view.origin, position_a, position_b);
+ * basically the player would move around in a small "box" in the center of the screen with out changing the camera position or angles */
+ camera_position = vec_bounds_in(view.origin, position_a, position_b);
// a tracking camera follows the player when it leaves the world box
}
// hard snap changes the angle as soon as it crosses over the nearest 90 degree mark
- if (autocvar_cam_snap_hard){
+ if (autocvar_cam_snap_hard) {
camera_angle = angle_snap_vec(aim_vec(camera_position, view.origin), 90);
}
// tries to avoid snapping unless it *really* needs to
- if (autocvar_cam_snap_close){
-
+ if (autocvar_cam_snap_close) {
// like hard snap, but don't snap angles yet.
camera_angle = aim_vec(camera_position, view.origin);
* NOTE: bug/feature: this will use non-snaped angles for one frame.
* doing this resualts in less code, faster code, and a smoother transisition between angles.
*/
- float camera_angle_diff = max(camera_angle_y, old_camera_angle_y) - min(camera_angle_y, old_camera_angle_y);
+ float camera_angle_diff = max(camera_angle.y, old_camera_angle.y) - min(camera_angle.y, old_camera_angle.y);
- if ( camera_angle_diff >= 60)
- old_camera_angle_y = angle_snap_f(camera_angle_y, 90);
- else
- camera_angle_y = old_camera_angle_y;
+ if (60 <= camera_angle_diff) { // use new angles
+ old_camera_angle.y = angle_snap_f(camera_angle.y, 90);
+ } else { // use old angles
+ camera_angle.y = old_camera_angle.y;
+ }
}
//unlocking this allows the camera to look up and down. this also allows a top-down view.
if (!autocvar_cam_snap_unlock) {
- camera_angle_x = 0;
- camera_angle_z = 0;
+ camera_angle.x = 0;
+ camera_angle.z = 0;
}
#if 0
setproperty(VF_ORIGIN, camera_position);
setproperty(VF_ANGLES, camera_angle);
- if(have_sidescroll)
- {
- forward = vectoangles(normalize(vec_to_min(position_b, position_a) - vec_to_max(position_b, position_a)));
- backward = vectoangles(normalize(vec_to_max(position_b, position_a) - vec_to_min(position_b, position_a)));
-
- if(input_movevalues_y < 0) // left
- view.angles_y = backward.y;
- if(input_movevalues_y > 0) // favour right
- view.angles_y = forward.y;
-
- setproperty(VF_CL_VIEWANGLES, view.angles);
+ if(spectatee_status)
+ return; // if spectating, don't replace angles or inputs!
+
+ if (have_sidescroll) {
+ vector view_angle = view.angles;
+ if (!(view.viewloc.spawnflags & VIEWLOC_FREEAIM)) {
+ vector avatar_facing_dir;
+ // get the player's forward-facing direction, based on positions a and b
+ if (0 == input_movevalues.y) {
+ avatar_facing_dir = view_angle; // default to the previous values
+ } else if (0 > input_movevalues.y) { // left is forward
+ avatar_facing_dir = vectoangles(normalize(vec_to_max(position_b, position_a) - vec_to_min(position_b, position_a)));
+ } else { // right is forward
+ avatar_facing_dir = vectoangles(normalize(vec_to_min(position_b, position_a) - vec_to_max(position_b, position_a)));
+ }
+ view_angle.y = avatar_facing_dir.y; // snap avatar to look on along the correct axis
+
+ // if (0 == input_movevalues.x) look straight ahead
+ if (0 > input_movevalues.x) { // look up
+ view_angle.x = 50;
+ } else if (0 < input_movevalues.x) { // look down
+ view_angle.x = -50;
+ }
+ } else {
+ vector mpos = CursorToWorldCoord(viewloc_mousepos);
+ mpos.x = view.origin.x; // replace the cursor's x position with the player's
+ view_angle = aim_vec(view.origin, mpos); // get new angles
+ }
+ view.angles_y = view_angle.y;
+ setproperty(VF_CL_VIEWANGLES, view_angle);
}
}
}
PROJECTILE_TOUCH(this, toucher);
if(toucher.takedamage == DAMAGE_AIM)
{ if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this, toucher); } }
- else
+ else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
{
//UpdateCSQCProjectile(this);
spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
{
default:
case NET_ENT_CLIENT_HOOK:
- if(autocvar_chase_active > 0)
- a = csqcplayer.origin;
+ if(autocvar_chase_active)
+ a = csqcplayer.origin + csqcplayer.view_ofs;
else
a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
b = this.origin;
/* spawnfunc */ ATTRIB(Mortar, m_canonical_spawnfunc, string, "weapon_mortar");
/* ammotype */ ATTRIB(Mortar, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Mortar, impulse, int, 4);
-/* flags */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
+/* flags */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Mortar, bot_pickupbasevalue, float, 7000);
/* color */ ATTRIB(Mortar, wpcolor, vector, '1 0 0');
/* modelname */ ATTRIB(Mortar, mdl, string, "gl");
/* spawnfunc */ ATTRIB(Rifle, m_canonical_spawnfunc, string, "weapon_rifle");
/* ammotype */ ATTRIB(Rifle, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(Rifle, impulse, int, 7);
-/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
+/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Rifle, bot_pickupbasevalue, float, 7000);
/* color */ ATTRIB(Rifle, wpcolor, vector, '0.5 1 0');
/* modelname */ ATTRIB(Rifle, mdl, string, "campingrifle");
.void(entity this) havocbot_previous_role;
.float isbot; // true if this client is actually a bot
.float lastteleporttime;
+.vector lastteleport_origin;
.float navigation_hasgoals;
.float nearestwaypointtimeout;
.entity nearestwaypoint;
{
botframe_spawnedwaypoints = true;
waypoint_loadall();
- if(!waypoint_load_links())
- waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
+ waypoint_load_links();
}
if (bot_list)
if(this.lastteleporttime > 0
&& time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
{
+ if (this.jumppadcount && !boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax,
+ this.lastteleport_origin + STAT(PL_MIN, this), this.lastteleport_origin + STAT(PL_MAX, this)))
+ {
+ return;
+ }
+
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
{
.entity wp_goal_prev1;
.float lastteleporttime;
+.vector lastteleport_origin;
.float blacklisted;
wp.model = "";
}
-// create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
-// them back to it as well
-// (suitable for spawnfunc_waypoint editor)
entity waypoint_spawn(vector m1, vector m2, float f)
{
- if(!(f & WAYPOINTFLAG_PERSONAL))
+ if(!(f & (WAYPOINTFLAG_PERSONAL | WAYPOINTFLAG_GENERATED)) && m1 == m2)
{
- vector em1 = m1, em2 = m2;
- if (!(f & WAYPOINTFLAG_GENERATED) && m1 == m2)
- {
- em1 = m1 - '8 8 8';
- em2 = m2 + '8 8 8';
- }
+ vector em1 = m1 - '8 8 8';
+ vector em2 = m2 + '8 8 8';
IL_EACH(g_waypoints, boxesoverlap(em1, em2, it.absmin, it.absmax),
{
return it;
waypoint_load_links_hardwired();
}
+#define GET_GAMETYPE_EXTENSION() ((g_race) ? ".race" : "")
+
// Load waypoint links from file
bool waypoint_load_links()
{
- string filename, s;
+ string s;
float file, tokens, c = 0, found;
entity wp_from = NULL, wp_to;
vector wp_to_pos, wp_from_pos;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.cache");
+
+ string gt_ext = GET_GAMETYPE_EXTENSION();
+
+ string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
file = fopen(filename, FILE_READ);
+ if (gt_ext != "" && file < 0)
+ {
+ // if race waypoint file doesn't exist load the default one
+ filename = sprintf("maps/%s.waypoints.cache", mapname);
+ file = fopen(filename, FILE_READ);
+ }
+
if (file < 0)
{
- LOG_TRACE("waypoint links load from ");
- LOG_TRACE(filename);
- LOG_TRACE(" failed");
+ LOG_TRACE("waypoint links load from ", filename, " failed");
+ waypoint_schedulerelinkall();
return false;
}
else
{
if(ver < WAYPOINT_VERSION)
- return false;
+ {
+ LOG_TRACE("waypoint links for this map are outdated.");
+ if (g_assault)
+ {
+ LOG_TRACE("Assault waypoint links need to be manually updated in the editor");
+ }
+ else
+ {
+ LOG_TRACE("automatically updating...");
+ waypoint_schedulerelinkall();
+ fclose(file);
+ return false;
+ }
+ }
parse_comments = false;
}
}
{
// bad file format
fclose(file);
+ waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
return false;
}
LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from_pos));
continue;
}
-
}
// Search "to" waypoint
fclose(file);
- LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.cache");
+ LOG_TRACE("loaded ", ftos(c), " waypoint links from ", filename);
+
+ bool scheduled = false;
+ IL_EACH(g_waypoints, it.wpflags & WAYPOINTFLAG_ITEM,
+ {
+ if (!it.wp00)
+ {
+ waypoint_schedulerelink(it);
+ scheduled = true;
+ }
+ });
+ if (scheduled)
+ return false;
botframe_cachedwaypointlinks = true;
return true;
void waypoint_load_or_remove_links_hardwired(bool removal_mode)
{
- string filename, s;
+ string s;
float file, tokens, c = 0, found;
entity wp_from = NULL, wp_to;
vector wp_to_pos, wp_from_pos;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.hardwired");
+
+ string gt_ext = GET_GAMETYPE_EXTENSION();
+
+ string filename = sprintf("maps/%s.waypoints.hardwired", strcat(mapname, gt_ext));
file = fopen(filename, FILE_READ);
+ if (gt_ext != "" && file < 0)
+ {
+ // if race waypoint file doesn't exist load the default one
+ filename = sprintf("maps/%s.waypoints.hardwired", mapname);
+ file = fopen(filename, FILE_READ);
+ }
+
botframe_loadedforcedlinks = true;
if (file < 0)
fclose(file);
- if(!removal_mode)
- LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
+ LOG_TRACE(((removal_mode) ? "unloaded " : "loaded "),
+ ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
}
entity waypoint_get_link(entity w, float i)
// temporarily remove hardwired links so they don't get saved among normal links
waypoint_remove_links_hardwired();
- string filename = sprintf("maps/%s.waypoints.cache", mapname);
+ string gt_ext = GET_GAMETYPE_EXTENSION();
+
+ string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
int file = fopen(filename, FILE_WRITE);
if (file < 0)
{
}
});
fclose(file);
+
botframe_cachedwaypointlinks = true;
- LOG_INFOF("saved %d waypoint links to maps/%s.waypoints.cache", c, mapname);
+ LOG_INFOF("saved %d waypoint links to %s", c, filename);
waypoint_load_links_hardwired();
}
// save waypoints to gamedir/data/maps/mapname.waypoints
void waypoint_saveall()
{
- string filename = sprintf("maps/%s.waypoints", mapname);
+ string gt_ext = GET_GAMETYPE_EXTENSION();
+
+ string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
int file = fopen(filename, FILE_WRITE);
if (file < 0)
{
waypoint_save_links();
botframe_loadedforcedlinks = false;
- LOG_INFOF("saved %d waypoints to maps/%s.waypoints", c, mapname);
+ LOG_INFOF("saved %d waypoints to %s", c, filename);
}
// load waypoints from file
float waypoint_loadall()
{
- string filename, s;
+ string s;
float file, cwp, cwb, fl;
vector m1, m2;
cwp = 0;
cwb = 0;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints");
+
+ string gt_ext = GET_GAMETYPE_EXTENSION();
+
+ string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
file = fopen(filename, FILE_READ);
- bool parse_comments = true;
- float ver = 0;
+ if (gt_ext != "" && file < 0)
+ {
+ // if race waypoint file doesn't exist load the default one
+ filename = sprintf("maps/%s.waypoints", mapname);
+ file = fopen(filename, FILE_READ);
+ }
if (file < 0)
{
return 0;
}
+ bool parse_comments = true;
+ float ver = 0;
+
while ((s = fgets(file)))
{
if(parse_comments)
else
{
if(floor(ver) < floor(WAYPOINT_VERSION))
+ {
LOG_TRACE("waypoints for this map are outdated");
+ LOG_TRACE("please update them in the editor");
+ }
parse_comments = false;
}
}
Weapon w = this.(weaponentity).m_weapon;
w.wr_reload(w, actor, weaponentity);
- if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
- break;
+ // allow reloading all active slots?
+ //if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
+ //break;
}
}
});
if(!sp)
{
+ int items_checked = 0;
IL_EACH(g_items, checkpvs(mstart, it),
{
if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
sp = it;
break;
}
+
+ ++items_checked;
+ if(items_checked >= attempts)
+ break; // sanity
});
if(!sp)
{
navigation_goalrating_start(this);
+ bool raw_touch_check = true;
+ int cp = this.race_checkpoint;
+
+ LABEL(search_racecheckpoints)
IL_EACH(g_racecheckpoints, true,
{
- if(it.cnt == this.race_checkpoint)
- navigation_routerating(this, it, 1000000, 5000);
- else if(this.race_checkpoint == -1)
+ if(it.cnt == cp || cp == -1)
+ {
+ // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+ // e.g. checkpoint in front of Stormkeep's warpzone
+ // the same workaround is applied in Race game mode
+ if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+ {
+ cp = race_NextCheckpoint(cp);
+ raw_touch_check = false;
+ goto search_racecheckpoints;
+ }
navigation_routerating(this, it, 1000000, 5000);
+ }
});
navigation_goalrating_end(this);
{
navigation_goalrating_start(this);
+ bool raw_touch_check = true;
+ int cp = this.race_checkpoint;
+
+ LABEL(search_racecheckpoints)
IL_EACH(g_racecheckpoints, true,
{
- if(it.cnt == this.race_checkpoint)
- navigation_routerating(this, it, 1000000, 5000);
- else if(this.race_checkpoint == -1)
+ if(it.cnt == cp || cp == -1)
+ {
+ // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+ // e.g. checkpoint in front of Stormkeep's warpzone
+ // the same workaround is applied in CTS game mode
+ if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+ {
+ cp = race_NextCheckpoint(cp);
+ raw_touch_check = false;
+ goto search_racecheckpoints;
+ }
navigation_routerating(this, it, 1000000, 5000);
});
#include <common/util.qh>
#include <common/weapons/_all.qh>
+#include <common/wepent.qh>
#include <common/state.qh>
#include <lib/warpzone/common.qh>
ent.punchangle_x = recoil * -1;
if (snd != SND_Null) {
- sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
+ int held_weapons = 0; // HACK: muffle weapon sounds slightly while dual wielding!
+ for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+ {
+ .entity wep_ent = weaponentities[slot];
+ if(ent.(wep_ent) && ent.(wep_ent).m_switchweapon != WEP_Null)
+ ++held_weapons;
+ }
+ sound (ent, chan, snd, ((held_weapons > 1) ? VOL_BASE * 0.7 : VOL_BASE), ATTN_NORM);
W_PlayStrengthSound(ent);
}