- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- make
- - EXPECT=ed9be8d1b1a544f89bcdd7d36876fede
+ - EXPECT=29a3c5d84ed37810d674c2c176b21e04
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
set sv_jumpspeedcap_max "" "upper bound on the baseline velocity of a jump; final velocity will be <= (jumpheight * max + jumpheight)"
set sv_jumpspeedcap_max_disable_on_ramps 0 "disable upper baseline velocity bound on ramps to preserve the old rampjump style"
set sv_track_canjump 0 "track if the player released the jump key between 2 jumps to decide if they are able to jump or not"
+set sv_jumpvelocity_crouch 0 "jump height while crouching, set to 0 to use regular jump height"
set sv_precacheplayermodels 1
set sv_precacheweapons 0
set sv_precacheitems 0
set sv_spectator_speed_multiplier 1.5
+set sv_spectator_speed_multiplier_min 1
+set sv_spectator_speed_multiplier_max 5
set sv_spectate 1 "if set to 1, new clients are allowed to spectate or observe the game, if set to 0 joining clients spawn as players immediately (no spectating)"
set sv_defaultcharacter 0 "master switch, if set to 1 the further configuration for replacing all player models, skins and colors is taken from the sv_defaultplayermodel, sv_defaultplayerskin and sv_defaultplayercolors variables"
set sv_defaultcharacterskin 0 "if set to 1 the further configuration for replacing all skins is taken from the sv_defaultplayerskin variables"
set bot_wander_enable 1 "Have bots wander around if they are unable to reach any useful goal. Disable only for debugging purposes."
// general bot AI cvars
set bot_ai_thinkinterval 0.05
-set bot_ai_strategyinterval 5 "How often a new objective is chosen"
+set bot_ai_strategyinterval 7 "How often a new objective is chosen"
+set bot_ai_strategyinterval_movingtarget 5.5 "How often a new objective is chosen when current objective can move"
set bot_ai_enemydetectioninterval 2 "How often bots pick a new target"
set bot_ai_enemydetectionradius 10000 "How far bots can see enemies"
set bot_ai_dodgeupdateinterval 0.2 "How often scan for items to dodge. Currently not in use."
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 sv_minigames_pong_ai_tolerance 0.33 "Distance of the ball relative to the paddle size"
-// Snake? Snake! SNAAAAKE!!
-set sv_minigames_snake_wrap 0 "Wrap around the edges of the screen instead of dying on touch"
-set sv_minigames_snake_delay_initial 0.7 "Initial delay between snake movement"
-set sv_minigames_snake_delay_multiplier 50 "Multiplier of incremental of movement speed (player_score / cvar)"
-set sv_minigames_snake_delay_min 0.1 "Minimum delay between snake movement (at fastest rate)"
-set sv_minigames_snake_lives 3
-
-
// Bulldozer
set sv_minigames_bulldozer_startlevel "level1"
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 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"
set g_instagib_damagedbycontents 1 "allow damage from lava pits in instagib"
set g_instagib_blaster_keepdamage 0 "allow secondary fire to hurt players"
set g_dynamic_handicap_exponent 1 "The exponent used to calculate handicap. 1 means linear scale. Values more than 1 mean stronger non-linear handicap. Values less than 1 mean weaker non-linear handicap"
set g_dynamic_handicap_min 0 "The minimum value of the handicap."
set g_dynamic_handicap_max 0 "The maximum value of the handicap."
+
+// ===============
+// kick teamkiller
+// ===============
+set g_kick_teamkiller_rate 0 "Limit for teamkills per minute before the client gets dropped. 0 means that the teamkillers don't get kicked automatically"
+set g_kick_teamkiller_lower_limit 5 "Minimum number of teamkills before the teamkill rate is considered"
+
+// =====================
+// stale-move negation
+// =====================
+set g_smneg 0 "Stale-move negation: penalize repeated use of the same weapon"
+set g_smneg_bonus 1 "Stale-move negation: allow weapons to become stronger than their baseline"
+set g_smneg_bonus_asymptote 4 "Stale-move negation: damage = infinity at this bonus level"
+set g_smneg_cooldown_factor 0.25 "Stale-move negation: penalty cooldown factor"
+
+// ==============
+// random items
+// ==============
+set g_random_items 0 "Whether to enable random items."
+set g_random_loot 0 "Whether to enable random loot."
+exec randomitems-xonotic.cfg
set g_physics_xonotic_airspeedlimit_nonqw 900
set g_physics_xonotic_maxspeed 360
set g_physics_xonotic_jumpvelocity 260
+set g_physics_xonotic_jumpvelocity_crouch 0
set g_physics_xonotic_maxairstrafespeed 100
set g_physics_xonotic_maxairspeed 360
set g_physics_xonotic_airstrafeaccelerate 18
set g_physics_nexuiz_airspeedlimit_nonqw 0
set g_physics_nexuiz_maxspeed 400
set g_physics_nexuiz_jumpvelocity 300 "333 to match xonotic physics"
+set g_physics_nexuiz_jumpvelocity_crouch 0 "333 to match xonotic physics"
set g_physics_nexuiz_maxairstrafespeed 0
set g_physics_nexuiz_maxairspeed 220
set g_physics_nexuiz_airstrafeaccelerate 0
set g_physics_quake_airspeedlimit_nonqw 0
set g_physics_quake_maxspeed 320
set g_physics_quake_jumpvelocity 270
+set g_physics_quake_jumpvelocity_crouch 0
set g_physics_quake_maxairstrafespeed 0
set g_physics_quake_maxairspeed 30
set g_physics_quake_airstrafeaccelerate 0
set g_physics_warsow_airspeedlimit_nonqw 0
set g_physics_warsow_maxspeed 320
set g_physics_warsow_jumpvelocity 280
+set g_physics_warsow_jumpvelocity_crouch 0
set g_physics_warsow_maxairstrafespeed 30
set g_physics_warsow_maxairspeed 320
set g_physics_warsow_airstrafeaccelerate 70
set g_physics_defrag_airspeedlimit_nonqw 0
set g_physics_defrag_maxspeed 320
set g_physics_defrag_jumpvelocity 270
+set g_physics_defrag_jumpvelocity_crouch 0
set g_physics_defrag_maxairstrafespeed 30
set g_physics_defrag_maxairspeed 320
set g_physics_defrag_airstrafeaccelerate 70
set g_physics_quake3_airspeedlimit_nonqw 0
set g_physics_quake3_maxspeed 320
set g_physics_quake3_jumpvelocity 270
+set g_physics_quake3_jumpvelocity_crouch 0
set g_physics_quake3_maxairstrafespeed 0
set g_physics_quake3_maxairspeed 320
set g_physics_quake3_airstrafeaccelerate 0
set g_physics_vecxis_airspeedlimit_nonqw 0
set g_physics_vecxis_maxspeed 400
set g_physics_vecxis_jumpvelocity 300 "333 to match xonotic physics"
+set g_physics_vecxis_jumpvelocity_crouch 0 "333 to match xonotic physics"
set g_physics_vecxis_maxairstrafespeed 0
set g_physics_vecxis_maxairspeed 220
set g_physics_vecxis_airstrafeaccelerate 0
set g_physics_quake2_airspeedlimit_nonqw 0
set g_physics_quake2_maxspeed 300
set g_physics_quake2_jumpvelocity 270
+set g_physics_quake2_jumpvelocity_crouch 0
set g_physics_quake2_maxairstrafespeed 0
set g_physics_quake2_maxairspeed 300
set g_physics_quake2_airstrafeaccelerate 0
set g_physics_bones_airspeedlimit_nonqw 0
set g_physics_bones_maxspeed 320
set g_physics_bones_jumpvelocity 270
+set g_physics_bones_jumpvelocity_crouch 0
set g_physics_bones_maxairstrafespeed 30
set g_physics_bones_maxairspeed 320
set g_physics_bones_airstrafeaccelerate 70
set g_physics_overkill_airspeedlimit_nonqw 900
set g_physics_overkill_maxspeed 400
set g_physics_overkill_jumpvelocity 260
+set g_physics_overkill_jumpvelocity_crouch 0
set g_physics_overkill_maxairstrafespeed 100
set g_physics_overkill_maxairspeed 360
set g_physics_overkill_airstrafeaccelerate 24
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 4
sv_waterfriction 1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0 // breaks strafing?
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.65
// actually, what we want is 266.6666 for 180bpm
// but 260 takes same amount of frames and is nicer to mappers
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0 // breaks strafing?
// actually, what we want is 266.6666 for 180bpm
// but 260 takes same amount of frames and is nicer to mappers
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1 // div0 says no! lol
sv_stepheight 26
sv_jumpvelocity 304
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0 // pain in the ass to tweak without screwing up the strafing
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 310
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.35
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.3
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.35
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.35
// actually, what we want is 266.6666 for 180bpm
// but 260 takes same amount of frames and is nicer to mappers
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
// this is smaller than 112 qu, so a 112 qu high corridor (7 of 8 grid units in
// the 16 grid, and the 8th unit used for wall/floor) just lets a player jump!
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction 1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction 1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 4
sv_waterfriction 1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.8
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.5
edgefriction 1
sv_stepheight 34
sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0.3
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 280
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 10
sv_waterfriction 1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 280
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 10
sv_waterfriction 1
sv_airaccel_sideways_friction 0
edgefriction 1
sv_stepheight 18
sv_jumpvelocity 280
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 10
sv_waterfriction 1
sv_airaccel_sideways_friction 0
// this is smaller than 112 qu, so a 112 qu high corridor (7 of 8 grid units in
// the 16 grid, and the 8th unit used for wall/floor) just lets a player jump!
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
// actually, what we want is 266.6666 for 180bpm
// but 260 takes same amount of frames and is nicer to mappers
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
// this is smaller than 112 qu, so a 112 qu high corridor (7 of 8 grid units in
// the 16 grid, and the 8th unit used for wall/floor) just lets a player jump!
sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
sv_wateraccelerate -1
sv_waterfriction -1
sv_airaccel_sideways_friction 0
sv_stepheight 26
// CPMA: 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 4
sv_waterfriction 1
sv_airaccel_sideways_friction 0
sv_stepheight 26
// CPMA: 18
sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
sv_wateraccelerate 4
sv_waterfriction 1
sv_airaccel_sideways_friction 0
const int acc_decimals = 2;
if(time > physics_update_time)
{
+ discrete_acceleration = acceleration;
// workaround for ftos_decimals returning a negative 0
if(discrete_acceleration > -1 / (10 ** acc_decimals) && discrete_acceleration < 0)
discrete_acceleration = 0;
- discrete_acceleration = acceleration;
discrete_speed = speed;
physics_update_time += autocvar_hud_panel_physics_update_interval;
if(physics_update_time < time)
case "kd": return CTX(_("SCO^k/d"));
case "kdr": return CTX(_("SCO^kdr"));
case "kills": return CTX(_("SCO^kills"));
+ case "teamkills": return CTX(_("SCO^teamkills"));
case "laps": return CTX(_("SCO^laps"));
case "lives": return CTX(_("SCO^lives"));
case "losses": return CTX(_("SCO^losses"));
LOG_INFO(_("^3deaths^7 Number of deaths"));
LOG_INFO(_("^3suicides^7 Number of suicides"));
LOG_INFO(_("^3frags^7 kills - suicides"));
+ LOG_INFO(_("^3teamkills^7 Number of teamkills"));
LOG_INFO(_("^3kd^7 The kill-death ratio"));
LOG_INFO(_("^3dmg^7 The total damage done"));
LOG_INFO(_("^3dmgtaken^7 The total damage taken"));
"ping pl name |" \
" -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
" -teams,lms/deaths +ft,tdm/deaths" \
+" +tdm/sum" \
" -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
" -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
+" +tdm,ft,dom,ons,as/teamkills"\
" -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
-" +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
+" +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
" +lms/lives +lms/rank" \
-" +kh/caps +kh/pushes +kh/destroyed" \
+" +kh/kckills +kh/losses +kh/caps" \
" ?+rc/laps ?+rc/time +rc,cts/fastest" \
" +as/objectives +nb/faults +nb/goals" \
" +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
+" +dom/ticks +dom/takes" \
" -lms,rc,cts,inv,nb/score"
void Cmd_Scoreboard_SetFields(int argc)
#include "mutators/events.qh"
#include <common/animdecide.qh>
+#include <common/deathtypes/all.qh>
#include <common/ent_cs.qh>
#include <common/anim.qh>
#include <common/constants.qh>
#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>
case 2: // crosshair_color_by_health
{
- float hp = health_stat;
+ vector v = healtharmor_maxdamage(health_stat, STAT(ARMOR), armorblockpercent, DEATH_WEAPON.m_id);
+ float hp = floor(v.x + 1);
//x = red
//y = green
float f, i, j;
vector v;
if(!scoreboard_active && !camera_active && intermission != 2 && !STAT(GAME_STOPPED) &&
- spectatee_status != -1 && !csqcplayer.viewloc && !MUTATOR_CALLHOOK(DrawCrosshair) &&
+ spectatee_status != -1 && (!csqcplayer.viewloc || (!spectatee_status && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM))) && !MUTATOR_CALLHOOK(DrawCrosshair) &&
!HUD_MinigameMenu_IsOpened() )
{
if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
float shottype;
// wcross_origin = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
- wcross_origin = project_3d_to_2d(view_origin + max_shot_distance * view_forward);
+ if(csqcplayer.viewloc && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM))
+ wcross_origin = viewloc_mousepos;
+ else
+ wcross_origin = project_3d_to_2d(view_origin + max_shot_distance * view_forward);
wcross_origin.z = 0;
if(autocvar_crosshair_hittest)
{
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_NET_TEMP(globalsound)
REGISTER_NET_TEMP(playersound)
- string GlobalSound_sample(string pair, float r);
-
#ifdef SVQC
/**
* @param from the source entity, its position is sent
//#endif
entity GetVoiceMessage(string type);
+string GlobalSound_sample(string pair, float r);
+
#ifdef SVQC
void _GlobalSound(entity this, entity gs, entity ps, string sample, float chan, float vol, float voicetype, bool fake);
{
entity item = M_ARGV(0, entity);
- if(item.classname == "droppedweapon")
+ if(Item_IsLoot(item))
if(item.weapon == WEP_NEXBALL.m_id)
return true;
void W_Nexball_Attack(entity actor, .entity weaponentity, float t);
void W_Nexball_Attack2(entity actor, .entity weaponentity);
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
METHOD(BallStealer, wr_think, void(BallStealer thiswep, entity actor, .entity weaponentity, int fire))
{
{
entity _ball = actor.ballcarried;
W_SetupShot(actor, weaponentity, false, 4, SND_NB_SHOOT1, CH_WEAPON_A, 0);
- DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32));
+ DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32, _ball));
setthink(_ball, W_Nexball_Think);
_ball.nextthink = time;
return;
if(this.havocbot_attack_time>time)
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
navigation_goalrating_start(this);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
navigation_goalrating_end(this);
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_timeout_set(this);
}
}
#pragma once
#include "all.qh"
-#include "item/pickup.qh"
CLASS(Inventory, Object)
/** Stores counts of items, the id being the index */
if (!(minorBits & BIT(j))) { \
continue; \
} \
- const GameItem it = Items_from(Inventory_groups_minor * maj + j); \
+ const entity it = Items_from(Inventory_groups_minor * maj + j); \
WriteByte(MSG_ENTITY, data.inv_items[it.m_id]); \
} \
} \
#pragma once
-#include <common/t_items.qh>
#ifdef GAMEQC
+#include <common/models/all.qh>
#include <common/sounds/all.qh>
#include <common/sounds/all.inc>
+#include <common/stats.qh>
#endif
const int IT_UNLIMITED_WEAPON_AMMO = BIT(0); // when this bit is set, using a weapon does not reduce ammo. Checkpoints can give this powerup.
#ifdef SVQC
.float strength_finished = _STAT(STRENGTH_FINISHED);
.float invincible_finished = _STAT(INVINCIBLE_FINISHED);
+
+#define SPAWNFUNC_ITEM(name, item) \
+ spawnfunc(name) { StartItem(this, item); }
+
+#else
+
+#define SPAWNFUNC_ITEM(name, item)
+
#endif
+enum
+{
+ ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay.
+ ITEM_FLAG_INSTAGIB = BIT(1), ///< Item is usable in instagib.
+ ITEM_FLAG_OVERKILL = BIT(2), ///< Item is usable in overkill.
+ ITEM_FLAG_MUTATORBLOCKED = BIT(3)
+};
+
#define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__)
CLASS(GameItem, Object)
ATTRIB(GameItem, m_id, int, 0);
+ /** the canonical spawnfunc name */
+ ATTRIB(GameItem, m_canonical_spawnfunc, string);
+ METHOD(GameItem, m_spawnfunc_hookreplace, GameItem(GameItem this, entity e)) { return this; }
ATTRIB(GameItem, m_name, string);
ATTRIB(GameItem, m_icon, string);
ATTRIB(GameItem, m_color, vector, '1 1 1');
#include "ammo.qh"
+
+#ifdef SVQC
+
+METHOD(Bullets, m_spawnfunc_hookreplace, GameItem(Bullets this, entity e))
+{
+ if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
+ {
+ return ITEM_Shells;
+ }
+ return this;
+}
+
+METHOD(Shells, m_spawnfunc_hookreplace, GameItem(Shells this, entity e))
+{
+ if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
+ {
+ return ITEM_Bullets;
+ }
+ return this;
+}
+
+#endif
#pragma once
#include "pickup.qh"
+#ifdef SVQC
+ #include <common/t_items.qh>
+#endif
+
+.int ammo_none;
+.int ammo_shells;
+.int ammo_nails;
+.int ammo_rockets;
+.int ammo_cells;
+#ifdef SVQC
+.int ammo_plasma = _STAT(PLASMA);
+.int ammo_fuel = _STAT(FUEL);
+#else
+.int ammo_plasma;
+.int ammo_fuel;
+#endif
+
#ifdef SVQC
PROPERTY(float, g_pickup_ammo_anyway);
#endif
#endif
ENDCLASS(Ammo)
-#ifdef SVQC
- #include <common/t_items.qh>
-#endif
#ifdef GAMEQC
MODEL(Bullets_ITEM, Item_Model("a_bullets.mdl"));
item.ammo_nails = g_pickup_nails;
}
#endif
-REGISTER_ITEM(Bullets, Ammo) {
+
+CLASS(Bullets, Ammo)
+ENDCLASS(Bullets)
+
+REGISTER_ITEM(Bullets, Bullets) {
+ this.m_canonical_spawnfunc = "item_bullets";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Bullets_ITEM;
#endif
this.netname = "bullets";
#endif
}
+SPAWNFUNC_ITEM(item_bullets, ITEM_Bullets)
+
#ifdef GAMEQC
MODEL(Cells_ITEM, Item_Model("a_cells.md3"));
#endif
}
#endif
REGISTER_ITEM(Cells, Ammo) {
+ this.m_canonical_spawnfunc = "item_cells";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Cells_ITEM;
#endif
this.netname = "cells";
#endif
}
+SPAWNFUNC_ITEM(item_cells, ITEM_Cells)
+
#ifdef GAMEQC
MODEL(Plasma_ITEM, Item_Model("a_cells.md3"));
#endif
}
#endif
REGISTER_ITEM(Plasma, Ammo) {
+ this.m_canonical_spawnfunc = "item_plasma";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Plasma_ITEM;
#endif
this.netname = "plasma";
#endif
}
+SPAWNFUNC_ITEM(item_plasma, ITEM_Plasma)
+
#ifdef GAMEQC
MODEL(Rockets_ITEM, Item_Model("a_rockets.md3"));
#endif
}
#endif
REGISTER_ITEM(Rockets, Ammo) {
+ this.m_canonical_spawnfunc = "item_rockets";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Rockets_ITEM;
#endif
this.netname = "rockets";
#endif
}
+SPAWNFUNC_ITEM(item_rockets, ITEM_Rockets)
+
#ifdef GAMEQC
MODEL(Shells_ITEM, Item_Model("a_shells.md3"));
#endif
item.ammo_shells = g_pickup_shells;
}
#endif
-REGISTER_ITEM(Shells, Ammo) {
+
+CLASS(Shells, Ammo)
+ENDCLASS(Shells)
+
+REGISTER_ITEM(Shells, Shells) {
+ this.m_canonical_spawnfunc = "item_shells";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Shells_ITEM;
#endif
this.netname = "shells";
this.m_iteminit = ammo_shells_init;
#endif
}
+
+SPAWNFUNC_ITEM(item_shells, ITEM_Shells)
#endif
REGISTER_ITEM(ArmorSmall, Armor) {
+ this.m_canonical_spawnfunc = "item_armor_small";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
this.m_model = MDL_ArmorSmall_ITEM;
this.m_sound = SND_ArmorSmall;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_armor_small, ITEM_ArmorSmall)
+
#ifdef GAMEQC
MODEL(ArmorMedium_ITEM, Item_Model("item_armor_medium.md3"));
SOUND(ArmorMedium, Item_Sound("armor10"));
#endif
REGISTER_ITEM(ArmorMedium, Armor) {
+ this.m_canonical_spawnfunc = "item_armor_medium";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
this.m_model = MDL_ArmorMedium_ITEM;
this.m_sound = SND_ArmorMedium;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_armor_medium, ITEM_ArmorMedium)
+
#ifdef GAMEQC
MODEL(ArmorBig_ITEM, Item_Model("item_armor_big.md3"));
SOUND(ArmorBig, Item_Sound("armor17_5"));
#endif
REGISTER_ITEM(ArmorBig, Armor) {
+ this.m_canonical_spawnfunc = "item_armor_big";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
this.m_model = MDL_ArmorBig_ITEM;
this.m_sound = SND_ArmorBig;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_armor_big, ITEM_ArmorBig)
+
#ifdef GAMEQC
MODEL(ArmorMega_ITEM, Item_Model("item_armor_large.md3"));
SOUND(ArmorMega, Item_Sound("armor25"));
#endif
REGISTER_ITEM(ArmorMega, Armor) {
+ this.m_canonical_spawnfunc = "item_armor_mega";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
this.m_model = MDL_ArmorMega_ITEM;
this.m_sound = SND_ArmorMega;
#endif
this.m_iteminit = item_armormega_init;
#endif
}
+
+SPAWNFUNC_ITEM(item_armor_mega, ITEM_ArmorMega)
#endif
REGISTER_ITEM(HealthSmall, Health) {
+ this.m_canonical_spawnfunc = "item_health_small";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_HealthSmall_ITEM;
this.m_sound = SND_HealthSmall;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_health_small, ITEM_HealthSmall)
+
#ifdef GAMEQC
MODEL(HealthMedium_ITEM, Item_Model("g_h25.md3"));
SOUND(HealthMedium, Item_Sound("mediumhealth"));
#endif
REGISTER_ITEM(HealthMedium, Health) {
+ this.m_canonical_spawnfunc = "item_health_medium";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_HealthMedium_ITEM;
this.m_sound = SND_HealthMedium;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_health_medium, ITEM_HealthMedium)
+
#ifdef GAMEQC
MODEL(HealthBig_ITEM, Item_Model("g_h50.md3"));
SOUND(HealthBig, Item_Sound("mediumhealth"));
#endif
REGISTER_ITEM(HealthBig, Health) {
+ this.m_canonical_spawnfunc = "item_health_big";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_HealthBig_ITEM;
this.m_sound = SND_HealthBig;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_health_big, ITEM_HealthBig)
+
#ifdef GAMEQC
MODEL(HealthMega_ITEM, Item_Model("g_h100.md3"));
SOUND(HealthMega, Item_Sound("megahealth"));
#endif
REGISTER_ITEM(HealthMega, Health) {
+ this.m_canonical_spawnfunc = "item_health_mega";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
this.m_model = MDL_HealthMega_ITEM;
this.m_sound = SND_HealthMega;
#endif
this.m_iteminit = item_healthmega_init;
#endif
}
+
+SPAWNFUNC_ITEM(item_health_mega, ITEM_HealthMega)
#include "jetpack.qh"
+
+#ifdef SVQC
+
+METHOD(Jetpack, m_spawnfunc_hookreplace, GameItem(Jetpack this, entity e))
+{
+ if(start_items & ITEM_Jetpack.m_itemid)
+ {
+ return ITEM_JetpackFuel;
+ }
+ return this;
+}
+
+METHOD(JetpackRegen, m_spawnfunc_hookreplace, GameItem(JetpackRegen this, entity e))
+{
+ if (start_items & ITEM_JetpackRegen.m_itemid)
+ {
+ return ITEM_JetpackFuel;
+ }
+ return this;
+}
+
+#endif
item.ammo_fuel = g_pickup_fuel_jetpack;
}
#endif
+
+CLASS(Jetpack, Powerup)
+ENDCLASS(Jetpack)
+
REGISTER_ITEM(Jetpack, Powerup) {
+ this.m_canonical_spawnfunc = "item_jetpack";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Jetpack_ITEM;
this.m_itemid = IT_JETPACK;
#endif
#endif
}
+SPAWNFUNC_ITEM(item_jetpack, ITEM_Jetpack)
+
#ifdef GAMEQC
MODEL(JetpackFuel_ITEM, Item_Model("g_fuel.md3"));
#endif
}
#endif
REGISTER_ITEM(JetpackFuel, Ammo) {
+ this.m_canonical_spawnfunc = "item_fuel";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_JetpackFuel_ITEM;
#endif
this.netname = "fuel";
#endif
}
+SPAWNFUNC_ITEM(item_fuel, ITEM_JetpackFuel)
+
#ifdef GAMEQC
MODEL(JetpackRegen_ITEM, Item_Model("g_fuelregen.md3"));
#endif
-REGISTER_ITEM(JetpackRegen, Powerup) {
+CLASS(JetpackRegen, Powerup)
+ENDCLASS(JetpackRegen)
+
+REGISTER_ITEM(JetpackRegen, JetpackRegen) {
+ this.m_canonical_spawnfunc = "item_fuel_regen";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_JetpackRegen_ITEM;
#endif
this.netname = "fuel_regen";
this.m_pickupevalfunc = ammo_pickupevalfunc;
#endif
}
+
+SPAWNFUNC_ITEM(item_fuel_regen, ITEM_JetpackRegen)
#include "pickup.qh"
+#include <common/items/inventory.qh>
#ifdef SVQC
bool ITEM_HANDLE(Pickup, entity this, entity item, entity player) {
return this.giveTo(this, item, player);
}
+
+METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player))
+{
+ TC(Pickup, this);
+ bool b = Item_GiveTo(item, player);
+ if (b) {
+ LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
+ player.inventory.inv_items[this.m_id]++;
+ Inventory_update(player);
+ }
+ return b;
+}
+
#endif
PROPERTY(float, g_pickup_respawntimejitter_powerup)
#endif
-#include <common/items/inventory.qh>
#include <common/items/item.qh>
-#include <common/t_items.qh>
-
-#ifdef GAMEQC
-#include <common/models/all.qh>
-#include <common/sounds/all.qh>
-#include <common/sounds/all.inc>
-#endif
CLASS(Pickup, GameItem)
#ifdef GAMEQC
ATTRIB(Pickup, m_pickupanyway, float());
ATTRIB(Pickup, m_iteminit, void(entity item));
float Item_GiveTo(entity item, entity player);
- METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player))
- {
- TC(Pickup, this);
- bool b = Item_GiveTo(item, player);
- if (b) {
- LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
- player.inventory.inv_items[this.m_id]++;
- Inventory_update(player);
- }
- return b;
- }
+ METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player));
bool ITEM_HANDLE(Pickup, Pickup this, entity item, entity player);
#endif
ENDCLASS(Pickup)
}
#endif
REGISTER_ITEM(Strength, Powerup) {
+ this.m_canonical_spawnfunc = "item_strength";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Strength_ITEM;
this.m_sound = SND_Strength;
this.m_glow = true;
#endif
}
+SPAWNFUNC_ITEM(item_strength, ITEM_Strength)
+
#ifdef GAMEQC
MODEL(Shield_ITEM, Item_Model("g_invincible.md3"));
SOUND(Shield, Item_Sound("powerup_shield"));
}
#endif
REGISTER_ITEM(Shield, Powerup) {
+ this.m_canonical_spawnfunc = "item_shield";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_NORMAL;
this.m_model = MDL_Shield_ITEM;
this.m_sound = SND_Shield;
this.m_glow = true;
this.m_iteminit = powerup_shield_init;
#endif
}
+
+SPAWNFUNC_ITEM(item_shield, ITEM_Shield)
+SPAWNFUNC_ITEM(item_invincible, ITEM_Shield)
return;
vector org = CENTER_OR_VIEWOFS(this);
- entity e = new(droppedweapon); // use weapon handling to remove it on touch
+ entity e = spawn();
+ Item_SetLoot(e, true);
e.spawnfunc_checked = true;
e.monster_loot = this.monster_loot;
e.noalign = true;
StartItem(e, e.monster_loot);
e.gravity = 1;
- set_movetype(e, MOVETYPE_TOSS);
- e.reset = SUB_Remove;
setorigin(e, org);
e.velocity = randomvec() * 175 + '0 0 325';
e.item_spawnshieldtime = time + 0.7;
if(delaytoo)
if(time < this.msound_delay)
return; // too early
- GlobalSound_string(this, this.(samplefield), chan, VOL_BASE, VOICETYPE_PLAYERSOUND);
+ string sample = this.(samplefield);
+ if (sample != "") sample = GlobalSound_sample(sample, random());
+ float myscale = ((this.scale) ? this.scale : 1); // safety net
+ float scale_inverse = 1 / myscale;
+ // TODO: change volume depending on size too?
+ sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, scale_inverse * 100, 0);
this.msound_delay = time + sound_delay;
}
#include <common/mutators/mutator/instagib/_mod.inc>
#include <common/mutators/mutator/invincibleproj/_mod.inc>
#include <common/mutators/mutator/itemstime/_mod.inc>
+#include <common/mutators/mutator/kick_teamkiller/_mod.inc>
#include <common/mutators/mutator/melee_only/_mod.inc>
#include <common/mutators/mutator/midair/_mod.inc>
#include <common/mutators/mutator/multijump/_mod.inc>
#include <common/mutators/mutator/physical_items/_mod.inc>
#include <common/mutators/mutator/pinata/_mod.inc>
#include <common/mutators/mutator/random_gravity/_mod.inc>
+#include <common/mutators/mutator/random_items/_mod.inc>
#include <common/mutators/mutator/rocketflying/_mod.inc>
#include <common/mutators/mutator/rocketminsta/_mod.inc>
#include <common/mutators/mutator/running_guns/_mod.inc>
#include <common/mutators/mutator/sandbox/_mod.inc>
#include <common/mutators/mutator/spawn_near_teammate/_mod.inc>
+#include <common/mutators/mutator/stale_move_negation/_mod.inc>
#include <common/mutators/mutator/superspec/_mod.inc>
#include <common/mutators/mutator/touchexplode/_mod.inc>
#include <common/mutators/mutator/vampire/_mod.inc>
#include <common/mutators/mutator/instagib/_mod.qh>
#include <common/mutators/mutator/invincibleproj/_mod.qh>
#include <common/mutators/mutator/itemstime/_mod.qh>
+#include <common/mutators/mutator/kick_teamkiller/_mod.qh>
#include <common/mutators/mutator/melee_only/_mod.qh>
#include <common/mutators/mutator/midair/_mod.qh>
#include <common/mutators/mutator/multijump/_mod.qh>
#include <common/mutators/mutator/physical_items/_mod.qh>
#include <common/mutators/mutator/pinata/_mod.qh>
#include <common/mutators/mutator/random_gravity/_mod.qh>
+#include <common/mutators/mutator/random_items/_mod.qh>
#include <common/mutators/mutator/rocketflying/_mod.qh>
#include <common/mutators/mutator/rocketminsta/_mod.qh>
#include <common/mutators/mutator/running_guns/_mod.qh>
#include <common/mutators/mutator/sandbox/_mod.qh>
#include <common/mutators/mutator/spawn_near_teammate/_mod.qh>
+#include <common/mutators/mutator/stale_move_negation/_mod.qh>
#include <common/mutators/mutator/superspec/_mod.qh>
#include <common/mutators/mutator/touchexplode/_mod.qh>
#include <common/mutators/mutator/vampire/_mod.qh>
switch(ent.classname)
{
case "item_strength":
- case "item_invincible":
+ case "item_shield":
{
entity e = spawn();
buff_SpawnReplacement(e, ent);
// generated file; do not modify
#include <common/mutators/mutator/instagib/items.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/instagib/sv_items.qc>
+#endif
#ifdef SVQC
#include <common/mutators/mutator/instagib/sv_instagib.qc>
#endif
}
#endif
REGISTER_ITEM(VaporizerCells, Ammo) {
+ this.m_canonical_spawnfunc = "item_vaporizer_cells";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_VaporizerCells_ITEM;
this.m_sound = SND_VaporizerCells;
#endif
- this.netname = "minst_cells";
+ this.netname = "vaporizer_cells";
this.m_name = "Vaporizer Ammo";
this.m_icon = "ammo_supercells";
#ifdef SVQC
#endif
}
+SPAWNFUNC_ITEM(item_vaporizer_cells, ITEM_VaporizerCells)
+SPAWNFUNC_ITEM(item_minst_cells, ITEM_VaporizerCells)
+
#ifdef GAMEQC
MODEL(ExtraLife_ITEM, Item_Model("g_h100.md3"));
SOUND(ExtraLife, Item_Sound("megahealth"));
#endif
REGISTER_ITEM(ExtraLife, Powerup) {
+ this.m_canonical_spawnfunc = "item_extralife";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_INSTAGIB;
this.m_model = MDL_ExtraLife_ITEM;
this.m_sound = SND_ExtraLife;
#endif
- this.netname = "health_mega";
+ this.netname = "extralife";
this.m_name = "Extra life";
this.m_icon = "item_mega_health";
this.m_color = '1 0 0';
this.m_itemid = IT_NAILS;
}
+SPAWNFUNC_ITEM(item_extralife, ITEM_ExtraLife)
+
#ifdef GAMEQC
MODEL(Invisibility_ITEM, Item_Model("g_strength.md3"));
SOUND(Invisibility, Item_Sound("powerup"));
#endif
+#ifdef SVQC
+/// \brief Initializes the invisibility powerup.
+/// \param[in,out] item Item to initialize.
+/// \return No return.
+void powerup_invisibility_init(entity item);
+#endif
+
REGISTER_ITEM(Invisibility, Powerup) {
+ this.m_canonical_spawnfunc = "item_invisibility";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_Invisibility_ITEM;
this.m_sound = SND_Invisibility;
+ this.m_glow = true;
+ this.m_respawnsound = SND_STRENGTH_RESPAWN;
#endif
- this.netname = "strength";
+ this.netname = "invisibility";
this.m_name = "Invisibility";
this.m_icon = "strength";
this.m_color = '0 0 1';
this.m_waypoint = _("Invisibility");
this.m_waypointblink = 2;
this.m_itemid = IT_STRENGTH;
+#ifdef SVQC
+ this.m_iteminit = powerup_invisibility_init;
+#endif
}
+SPAWNFUNC_ITEM(item_invisibility, ITEM_Invisibility)
+
#ifdef GAMEQC
MODEL(Speed_ITEM, Item_Model("g_invincible.md3"));
SOUND(Speed, Item_Sound("powerup_shield"));
#endif
+#ifdef SVQC
+/// \brief Initializes the speed powerup.
+/// \param[in,out] item Item to initialize.
+/// \return No return.
+void powerup_speed_init(entity item);
+#endif
+
REGISTER_ITEM(Speed, Powerup) {
+ this.m_canonical_spawnfunc = "item_speed";
#ifdef GAMEQC
+ this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_Speed_ITEM;
this.m_sound = SND_Speed;
+ this.m_glow = true;
+ this.m_respawnsound = SND_SHIELD_RESPAWN;
#endif
- this.netname = "invincible";
+ this.netname = "speed";
this.m_name = "Speed";
this.m_icon = "shield";
this.m_color = '1 0 1';
this.m_waypoint = _("Speed");
this.m_waypointblink = 2;
this.m_itemid = IT_INVINCIBLE;
+#ifdef SVQC
+ this.m_iteminit = powerup_speed_init;
+#endif
}
+
+SPAWNFUNC_ITEM(item_speed, ITEM_Speed)
#include <common/items/_mod.qh>
-REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball);
-
-spawnfunc(item_minst_cells)
+REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball)
{
- if (!g_instagib) { delete(this); return; }
- StartItem(this, ITEM_VaporizerCells);
+ 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);
}
instagib_stop_countdown(player);
}
+MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidRandomStartWeapons)
+{
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn)
{
entity player = M_ARGV(0, entity);
void replace_with_insta_cells(entity item)
{
- entity e = spawn();
+ entity e = new(item_vaporizer_cells);
setorigin(e, item.origin);
- e.noalign = item.noalign;
+ e.noalign = Item_ShouldKeepPosition(item);
e.cnt = item.cnt;
e.team = item.team;
e.spawnfunc_checked = true;
- spawnfunc_item_minst_cells(e);
+ spawnfunc_item_vaporizer_cells(e);
}
MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
return true;
}
- if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon")
+ if(item.weapon == WEP_VAPORIZER.m_id && Item_IsLoot(item))
{
SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
return false;
return false;
float cells = GetResourceAmount(item, RESOURCE_CELLS);
- if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells")
+ if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_vaporizer_cells")
SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
if(cells && !item.weapon)
if (!autocvar_g_powerups) { return; }
entity ent = M_ARGV(0, entity);
// Can't use .itemdef here
- if (!(ent.classname == "item_strength" || ent.classname == "item_invincible" || ent.classname == "item_health_mega"))
+ if (!(ent.classname == "item_strength" || ent.classname == "item_shield" || ent.classname == "item_health_mega"))
return;
entity e = spawn();
float r = random();
if (r < 0.3)
+ {
+ e.classname = "item_invisibility";
setthink(e, instagib_invisibility);
+ }
else if (r < 0.6)
+ {
+ e.classname = "item_extralife";
setthink(e, instagib_extralife);
+ }
else
+ {
+ e.classname = "item_speed";
setthink(e, instagib_speed);
+ }
e.nextthink = time + 0.1;
e.spawnflags = ent.spawnflags;
#include "items.qh"
float autocvar_g_instagib_invis_alpha;
+
+void instagib_invisibility(entity this);
+void instagib_extralife(entity this);
+void instagib_speed(entity this);
--- /dev/null
+#include "items.qh"
+
+/// \brief Time of ivisibility powerup in seconds.
+float autocvar_g_instagib_invisibility_time;
+/// \brief Time of speed powerup in seconds.
+float autocvar_g_instagib_speed_time;
+
+void powerup_invisibility_init(entity item)
+{
+ if(!item.strength_finished)
+ {
+ item.strength_finished = autocvar_g_instagib_invisibility_time;
+ }
+}
+
+
+void powerup_speed_init(entity item)
+{
+ if(!item.invincible_finished)
+ {
+ item.invincible_finished = autocvar_g_instagib_speed_time;
+ }
+}
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/kick_teamkiller/sv_kick_teamkiller.qc>
+#endif
--- /dev/null
+// generated file; do not modify
--- /dev/null
+
+float autocvar_g_kick_teamkiller_rate;
+float autocvar_g_kick_teamkiller_lower_limit;
+
+REGISTER_MUTATOR(kick_teamkiller, (autocvar_g_kick_teamkiller_rate > 0));
+
+MUTATOR_HOOKFUNCTION(kick_teamkiller, PlayerDies)
+{
+ if (!teamplay)
+ {
+ return;
+ }
+ if (warmup_stage)
+ {
+ return;
+ }
+ entity attacker = M_ARGV(1, entity);
+ if (!IS_REAL_CLIENT(attacker))
+ {
+ return;
+ }
+
+ int teamkills = PlayerScore_Get(attacker, SP_TEAMKILLS);
+ // use the players actual playtime
+ float playtime = time - CS(attacker).startplaytime;
+ // rate is in teamkills/minutes, playtime in seconds
+ if (teamkills >= autocvar_g_kick_teamkiller_lower_limit &&
+ teamkills >= autocvar_g_kick_teamkiller_rate*playtime/60.0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_TEAMKILL, attacker.netname);
+ dropclient(attacker);
+ }
+}
M_ARGV(0, string) = "off";
}
+MUTATOR_HOOKFUNCTION(melee_only, ForbidRandomStartWeapons)
+{
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon)
{
return true;
}
}
- float n = 0;
+ int n = 0;
entity o = NULL;
if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
n = -1;
- else
+ else if(STAT(FROZEN, player) == 3)
{
vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
n = 0;
FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
- if(!IS_DEAD(it))
- if(STAT(FROZEN, it) == 0)
- if(SAME_TEAM(it, player))
+ if(!IS_DEAD(it) && STAT(FROZEN, it) == 0 && SAME_TEAM(it, player))
if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
{
if(!o)
o = it;
- if(STAT(FROZEN, player) == 1)
- it.reviving = true;
+ it.reviving = true;
++n;
}
});
}
- if(n && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us
+ if(n > 0 && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us
{
player.revive_progress = bound(0, player.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
SetResourceAmount(player, RESOURCE_HEALTH, max(1, player.revive_progress * start_health));
#include "sv_new_toys.qh"
+#include "../random_items/sv_random_items.qh"
+
/*
CORE laser vortex lg rl cry gl elec hagar fireb hook
MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace)
{
+ if (autocvar_g_random_items)
+ {
+ // Do not replace weapons when random items are enabled.
+ return;
+ }
entity wep = M_ARGV(0, entity);
entity wepinfo = M_ARGV(1, entity);
string ret_string = M_ARGV(2, string);
NIX_GiveCurrentWeapon(player);
}
+MUTATOR_HOOKFUNCTION(nix, ForbidRandomStartWeapons)
+{
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(nix, PlayerSpawn)
{
entity player = M_ARGV(0, entity);
M_ARGV(3, float) /* damage */ = (M_ARGV(0, entity)).max_health * 0.1;
}
-spawnfunc(weapon_hmg) { weapon_defaultspawnfunc(this, WEP_HMG); }
-
void W_HeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
if (!PHYS_INPUT_BUTTON_ATCK(actor))
#pragma once
+#include <common/weapons/all.qh>
+
CLASS(HeavyMachineGun, Weapon)
+/* spawnfunc */ ATTRIB(HeavyMachineGun, m_canonical_spawnfunc, string, "weapon_hmg");
/* ammotype */ ATTRIB(HeavyMachineGun, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(HeavyMachineGun, impulse, int, 3);
/* flags */ ATTRIB(HeavyMachineGun, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON);
ENDCLASS(HeavyMachineGun)
REGISTER_WEAPON(HMG, hmg, NEW(HeavyMachineGun));
+
+SPAWNFUNC_WEAPON(weapon_hmg, WEP_HMG)
#include "rpc.qh"
#ifdef SVQC
-spawnfunc(weapon_rpc) { weapon_defaultspawnfunc(this, WEP_RPC); }
void W_RocketPropelledChainsaw_Explode(entity this, entity directhitentity)
{
#pragma once
+#include <common/weapons/all.qh>
+
CLASS(RocketPropelledChainsaw, Weapon)
+/* spawnfunc */ ATTRIB(RocketPropelledChainsaw, m_canonical_spawnfunc, string, "weapon_rpc");
/* ammotype */ ATTRIB(RocketPropelledChainsaw, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(RocketPropelledChainsaw, impulse, int, 9);
/* flags */ ATTRIB(RocketPropelledChainsaw, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON);
ENDCLASS(RocketPropelledChainsaw)
REGISTER_WEAPON(RPC, rpc, NEW(RocketPropelledChainsaw));
+
+SPAWNFUNC_WEAPON(weapon_rpc, WEP_RPC)
bool autocvar_g_overkill_itemwaypoints = true;
-bool autocvar_g_overkill_filter_healthmega;
-bool autocvar_g_overkill_filter_armormedium;
-bool autocvar_g_overkill_filter_armorbig;
-bool autocvar_g_overkill_filter_armormega;
-
-.float ok_item;
-
.Weapon ok_lastwep[MAX_WEAPONSLOTS];
-void ok_Initialize();
-
REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
{
MUTATOR_ONADD
{
- ok_Initialize();
+ precache_all_playermodels("models/ok_player/*.dpm");
+
+ if (autocvar_g_overkill_filter_healthmega)
+ {
+ ITEM_HealthMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ }
+ if (autocvar_g_overkill_filter_armormedium)
+ {
+ ITEM_ArmorMedium.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ }
+ if (autocvar_g_overkill_filter_armorbig)
+ {
+ ITEM_ArmorBig.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ }
+ if (autocvar_g_overkill_filter_armormega)
+ {
+ ITEM_ArmorMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+ }
+
+ WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+ WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+
+ WEP_SHOTGUN.mdl = "ok_shotgun";
+ WEP_MACHINEGUN.mdl = "ok_mg";
+ WEP_VORTEX.mdl = "ok_sniper";
}
MUTATOR_ONREMOVE
{
+ ITEM_HealthMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_ArmorMedium.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_ArmorBig.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+ ITEM_ArmorMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+
WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
}
}
void W_Blaster_Attack(entity, .entity, float, float, float, float, float, float, float, float, float, float);
-spawnfunc(weapon_hmg);
-spawnfunc(weapon_rpc);
MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST)
{
void ok_DropItem(entity this, entity targ)
{
- entity e = new(droppedweapon); // hax
+ entity e = spawn();
e.ok_item = true;
- e.noalign = true;
- e.pickup_anyway = true;
- e.spawnfunc_checked = true;
- spawnfunc_item_armor_small(e);
- if (!wasfreed(e)) { // might have been blocked by a mutator
- set_movetype(e, MOVETYPE_TOSS);
- e.gravity = 1;
- e.reset = SUB_Remove;
- setorigin(e, this.origin + '0 0 32');
- e.velocity = '0 0 200' + normalize(targ.origin - this.origin) * 500;
- SUB_SetFade(e, time + 5, 1);
- }
+ Item_InitializeLoot(e, "item_armor_small", this.origin + '0 0 32',
+ '0 0 200' + normalize(targ.origin - this.origin) * 500, 5);
}
MUTATOR_HOOKFUNCTION(ok, PlayerDies)
PHYS_INPUT_BUTTON_ATCK2(player) = false;
}
+MUTATOR_HOOKFUNCTION(ok, ForbidRandomStartWeapons)
+{
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(ok, PlayerWeaponSelect)
{
entity player = M_ARGV(0, entity);
}
}
-void self_spawnfunc_weapon_hmg(entity this) { spawnfunc_weapon_hmg(this); }
-void self_spawnfunc_weapon_rpc(entity this) { spawnfunc_weapon_rpc(this); }
-
-MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn)
-{
- entity ent = M_ARGV(0, entity);
-
- if(autocvar_g_powerups)
- if(autocvar_g_overkill_powerups_replace)
- {
- if(ent.classname == "item_strength")
- {
- entity wep = new(weapon_hmg);
- setorigin(wep, ent.origin);
- setmodel(wep, MDL_OK_HMG);
- wep.ok_item = true;
- wep.noalign = ent.noalign;
- wep.cnt = ent.cnt;
- wep.team = ent.team;
- wep.respawntime = g_pickup_respawntime_superweapon;
- wep.pickup_anyway = true;
- wep.spawnfunc_checked = true;
- setthink(wep, self_spawnfunc_weapon_hmg);
- wep.nextthink = time + 0.1;
- return true;
- }
- else if(ent.classname == "item_invincible")
- {
- entity wep = new(weapon_rpc);
- setorigin(wep, ent.origin);
- setmodel(wep, MDL_OK_RPC);
- wep.ok_item = true;
- wep.noalign = ent.noalign;
- wep.cnt = ent.cnt;
- wep.team = ent.team;
- wep.respawntime = g_pickup_respawntime_superweapon;
- wep.pickup_anyway = true;
- wep.spawnfunc_checked = true;
- setthink(wep, self_spawnfunc_weapon_rpc);
- wep.nextthink = time + 0.1;
- return true;
- }
- }
-}
-
bool ok_HandleItemWaypoints(entity e)
{
if(!autocvar_g_overkill_itemwaypoints)
{
entity item = M_ARGV(0, entity);
- if(item.ok_item)
+ if (item.ok_item)
+ {
return false;
-
- switch(item.itemdef)
+ }
+ if (!autocvar_g_powerups || !autocvar_g_overkill_powerups_replace)
{
- case ITEM_HealthMega: return autocvar_g_overkill_filter_healthmega;
- case ITEM_ArmorMedium: return autocvar_g_overkill_filter_armormedium;
- case ITEM_ArmorBig: return autocvar_g_overkill_filter_armorbig;
- case ITEM_ArmorMega: return autocvar_g_overkill_filter_armormega;
+ return true;
+ }
+ if (item.classname == "item_strength")
+ {
+ entity wep = new(weapon_hmg);
+ setorigin(wep, item.origin);
+ wep.ok_item = true;
+ wep.noalign = Item_ShouldKeepPosition(item);
+ wep.cnt = item.cnt;
+ wep.team = item.team;
+ wep.respawntime = g_pickup_respawntime_superweapon;
+ wep.pickup_anyway = true;
+ wep.spawnfunc_checked = true;
+ Item_Initialize(wep, "weapon_hmg");
+ return true;
+ }
+ else if (item.classname == "item_shield")
+ {
+ entity wep = new(weapon_rpc);
+ setorigin(wep, item.origin);
+ wep.ok_item = true;
+ wep.noalign = Item_ShouldKeepPosition(item);
+ wep.cnt = item.cnt;
+ wep.team = item.team;
+ wep.respawntime = g_pickup_respawntime_superweapon;
+ wep.pickup_anyway = true;
+ wep.spawnfunc_checked = true;
+ Item_Initialize(wep, "weapon_rpc");
+ return true;
}
-
return true;
}
M_ARGV(0, string) = "Overkill";
return true;
}
-
-void ok_Initialize()
-{
- precache_all_playermodels("models/ok_player/*.dpm");
-
- WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
- WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-
- WEP_SHOTGUN.mdl = "ok_shotgun";
- WEP_MACHINEGUN.mdl = "ok_mg";
- WEP_VORTEX.mdl = "ok_sniper";
-}
#pragma once
+
+bool autocvar_g_overkill_filter_healthmega;
+bool autocvar_g_overkill_filter_armormedium;
+bool autocvar_g_overkill_filter_armorbig;
+bool autocvar_g_overkill_filter_armormega;
+
+.float ok_item;
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/random_items/sv_random_items.qc>
+#endif
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/random_items/sv_random_items.qh>
+#endif
--- /dev/null
+#include "sv_random_items.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the random items mutator.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+//============================ Constants ======================================
+
+enum
+{
+ RANDOM_ITEM_TYPE_HEALTH = 1,
+ RANDOM_ITEM_TYPE_ARMOR,
+ RANDOM_ITEM_TYPE_RESOURCE,
+ RANDOM_ITEM_TYPE_WEAPON,
+ RANDOM_ITEM_TYPE_POWERUP
+};
+
+//======================= Global variables ====================================
+
+// Replace cvars
+
+/// \brief Classnames to replace %s with.
+/// string autocvar_g_random_items_replace_%s;
+
+// Map probability cvars
+
+/// \brief Probability of random %s spawning in the map.
+/// float autocvar_g_random_items_%s_probability;
+
+/// \brief Probability of random %s spawning in the map during overkill.
+/// float autocvar_g_random_items_overkill_%s_probability;
+
+// Loot
+
+bool autocvar_g_random_loot; ///< Whether to enable random loot.
+
+float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
+float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
+float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
+float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
+
+// Loot probability cvars
+
+/// \brief Probability of random %s spawning as loot.
+/// float autocvar_g_random_loot_%s_probability;
+
+/// \brief Probability of random %s spawning as loot during overkill.
+/// float autocvar_g_random_loot_overkill_%s_probability;
+
+/// \brief Holds whether random item is spawning. Used to prevent infinite
+/// recursion.
+bool random_items_is_spawning = false;
+
+//====================== Forward declarations =================================
+
+/// \brief Returns a random classname of the item with specific property.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the item.
+string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
+ .bool item_property);
+
+//=========================== Public API ======================================
+
+string RandomItems_GetRandomItemClassName(string prefix)
+{
+ if (autocvar_g_instagib)
+ {
+ return RandomItems_GetRandomInstagibItemClassName(prefix);
+ }
+ if (expr_evaluate(autocvar_g_overkill))
+ {
+ return RandomItems_GetRandomOverkillItemClassName(prefix);
+ }
+ return RandomItems_GetRandomVanillaItemClassName(prefix);
+}
+
+string RandomItems_GetRandomVanillaItemClassName(string prefix)
+{
+ RandomSelection_Init();
+ string cvar_name = sprintf("g_%s_health_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH, cvar(cvar_name), 1);
+ }
+ cvar_name = sprintf("g_%s_armor_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR, cvar(cvar_name), 1);
+ }
+ cvar_name = sprintf("g_%s_resource_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE, cvar(cvar_name), 1);
+ }
+ cvar_name = sprintf("g_%s_weapon_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON, cvar(cvar_name), 1);
+ }
+ cvar_name = sprintf("g_%s_powerup_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP, cvar(cvar_name), 1);
+ }
+ int item_type = RandomSelection_chosen_float;
+ switch (item_type)
+ {
+ case RANDOM_ITEM_TYPE_HEALTH:
+ {
+ return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+ instanceOfHealth);
+ }
+ case RANDOM_ITEM_TYPE_ARMOR:
+ {
+ return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+ instanceOfArmor);
+ }
+ case RANDOM_ITEM_TYPE_RESOURCE:
+ {
+ return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+ instanceOfAmmo);
+ }
+ case RANDOM_ITEM_TYPE_WEAPON:
+ {
+ RandomSelection_Init();
+ FOREACH(Weapons, it != WEP_Null &&
+ !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
+ {
+ cvar_name = sprintf("g_%s_%s_probability", prefix,
+ it.m_canonical_spawnfunc);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.",
+ cvar_name);
+ continue;
+ }
+ RandomSelection_AddString(it.m_canonical_spawnfunc,
+ cvar(cvar_name), 1);
+ });
+ return RandomSelection_chosen_string;
+ }
+ case RANDOM_ITEM_TYPE_POWERUP:
+ {
+ return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+ instanceOfPowerup);
+ }
+ }
+ return "";
+}
+
+string RandomItems_GetRandomInstagibItemClassName(string prefix)
+{
+ RandomSelection_Init();
+ FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB,
+ {
+ string cvar_name = sprintf("g_%s_%s_probability", prefix,
+ it.m_canonical_spawnfunc);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ continue;
+ }
+ RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+ });
+ return RandomSelection_chosen_string;
+}
+
+string RandomItems_GetRandomOverkillItemClassName(string prefix)
+{
+ RandomSelection_Init();
+ FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) &&
+ !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED),
+ {
+ string cvar_name = sprintf("g_%s_overkill_%s_probability", prefix,
+ it.m_canonical_spawnfunc);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ continue;
+ }
+ RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+ });
+ string cvar_name = sprintf("g_%s_overkill_weapon_hmg_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddString("weapon_hmg", cvar(cvar_name), 1);
+ }
+ cvar_name = sprintf("g_%s_overkill_weapon_rpc_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddString("weapon_rpc", cvar(cvar_name), 1);
+ }
+ return RandomSelection_chosen_string;
+}
+
+//========================= Free functions ====================================
+
+/// \brief Returns list of classnames to replace a map item with.
+/// \param[in] item Item to inspect.
+/// \return List of classnames to replace a map item with.
+string RandomItems_GetItemReplacementClassNames(entity item)
+{
+ string cvar_name = sprintf("g_random_items_replace_%s", item.classname);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ return "";
+ }
+ return cvar_string(cvar_name);
+}
+
+string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
+ .bool item_property)
+{
+ RandomSelection_Init();
+ FOREACH(Items, it.item_property && (it.spawnflags & ITEM_FLAG_NORMAL),
+ {
+ string cvar_name = sprintf("g_%s_%s_probability", prefix,
+ it.m_canonical_spawnfunc);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ continue;
+ }
+ RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+ });
+ return RandomSelection_chosen_string;
+}
+
+/// \brief Replaces a map item.
+/// \param[in] item Item to replace.
+/// \return Spawned item on success, NULL otherwise.
+entity RandomItems_ReplaceMapItem(entity item)
+{
+ //PrintToChatAll(strcat("Replacing ", item.classname));
+ string new_classnames = RandomItems_GetItemReplacementClassNames(item);
+ if (new_classnames == "")
+ {
+ return NULL;
+ }
+ string new_classname;
+ if (new_classnames == "random")
+ {
+ new_classname = RandomItems_GetRandomItemClassName("random_items");
+ if (new_classname == "")
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ int num_new_classnames = tokenize_console(new_classnames);
+ if (num_new_classnames == 1)
+ {
+ new_classname = new_classnames;
+ }
+ else
+ {
+ int classname_index = floor(random() * num_new_classnames);
+ new_classname = argv(classname_index);
+ }
+ }
+ //PrintToChatAll(strcat("Replacing with ", new_classname));
+ if (new_classname == item.classname)
+ {
+ return NULL;
+ }
+ random_items_is_spawning = true;
+ entity new_item;
+ if (!expr_evaluate(autocvar_g_overkill))
+ {
+ new_item = Item_Create(strzone(new_classname), item.origin,
+ Item_ShouldKeepPosition(item));
+ random_items_is_spawning = false;
+ if (new_item == NULL)
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ new_item = spawn();
+ new_item.classname = strzone(new_classname);
+ new_item.spawnfunc_checked = true;
+ new_item.noalign = Item_ShouldKeepPosition(item);
+ new_item.ok_item = true;
+ Item_Initialize(new_item, new_classname);
+ random_items_is_spawning = false;
+ if (wasfreed(new_item))
+ {
+ return NULL;
+ }
+ setorigin(new_item, item.origin);
+ }
+ if (item.team)
+ {
+ new_item.team = item.team;
+ }
+ return new_item;
+}
+
+/// \brief Spawns a random loot item.
+/// \param[in] position Position of the item.
+/// \return No return.
+void RandomItems_SpawnLootItem(vector position)
+{
+ string class_name = RandomItems_GetRandomItemClassName("random_loot");
+ if (class_name == "")
+ {
+ return;
+ }
+ vector spread = '0 0 0';
+ spread.z = autocvar_g_random_loot_spread / 2;
+ spread += randomvec() * autocvar_g_random_loot_spread;
+ random_items_is_spawning = true;
+ if (!expr_evaluate(autocvar_g_overkill))
+ {
+ Item_CreateLoot(class_name, position, spread,
+ autocvar_g_random_loot_time);
+ }
+ else
+ {
+ entity item = spawn();
+ item.ok_item = true;
+ item.classname = class_name;
+ Item_InitializeLoot(item, class_name, position, spread,
+ autocvar_g_random_loot_time);
+ }
+ random_items_is_spawning = false;
+}
+
+//============================= Hooks ========================================
+
+REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
+ autocvar_g_random_loot));
+
+MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
+{
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
+}
+
+MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
+{
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
+}
+
+/// \brief Hook that is called when an item is about to spawn.
+MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
+{
+ //PrintToChatAll("FilterItem");
+ if (!autocvar_g_random_items)
+ {
+ return false;
+ }
+ if (random_items_is_spawning == true)
+ {
+ return false;
+ }
+ entity item = M_ARGV(0, entity);
+ if (Item_IsLoot(item))
+ {
+ return false;
+ }
+ if (RandomItems_ReplaceMapItem(item) == NULL)
+ {
+ return false;
+ }
+ return true;
+}
+
+/// \brief Hook that is called after the player has touched an item.
+MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
+{
+ //PrintToChatAll("ItemTouched");
+ if (!autocvar_g_random_items)
+ {
+ return;
+ }
+ entity item = M_ARGV(0, entity);
+ if (Item_IsLoot(item))
+ {
+ return;
+ }
+ entity new_item = RandomItems_ReplaceMapItem(item);
+ if (new_item == NULL)
+ {
+ return;
+ }
+ Item_ScheduleRespawn(new_item);
+ delete(item);
+}
+
+/// \brief Hook which is called when the player dies.
+MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
+{
+ //PrintToChatAll("PlayerDies");
+ if (!autocvar_g_random_loot)
+ {
+ return;
+ }
+ entity victim = M_ARGV(2, entity);
+ vector loot_position = victim.origin + '0 0 32';
+ int num_loot_items = floor(autocvar_g_random_loot_min + random() *
+ autocvar_g_random_loot_max);
+ for (int item_index = 0; item_index < num_loot_items; ++item_index)
+ {
+ RandomItems_SpawnLootItem(loot_position);
+ }
+}
--- /dev/null
+#pragma once
+
+/// \file
+/// \brief Header file that describes the random items mutator.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+bool autocvar_g_random_items; ///< Whether to enable random items.
+
+/// \brief Returns a random classname of the item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the item.
+/// \note This function will automatically detect gamemode and use cvars from
+/// that gamemode.
+string RandomItems_GetRandomItemClassName(string prefix);
+
+/// \brief Returns a random classname of the vanilla item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the vanilla item.
+/// \note This includes mutator items that don't change gameplay a lot such as
+/// jetpack and new toys.
+string RandomItems_GetRandomVanillaItemClassName(string prefix);
+
+/// \brief Returns a random classname of the instagib item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the instagib item.
+string RandomItems_GetRandomInstagibItemClassName(string prefix);
+
+/// \brief Returns a random classname of the overkill item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the overkill item.
+string RandomItems_GetRandomOverkillItemClassName(string prefix);
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qc>
+#endif
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qh>
+#endif
--- /dev/null
+#include "sv_stale_move_negation.qh"
+
+AUTOCVAR(g_smneg, bool, false, "Stale-move negation: penalize repeated use of the same weapon");
+AUTOCVAR(g_smneg_bonus, bool, true, "Stale-move negation: allow weapons to become stronger than their baseline");
+AUTOCVAR(g_smneg_bonus_asymptote, float, 4, "Stale-move negation: damage = infinity at this bonus level");
+AUTOCVAR(g_smneg_cooldown_factor, float, 1 / 4, "Stale-move negation: penalty cooldown factor");
+REGISTER_MUTATOR(mutator_smneg, autocvar_g_smneg);
+
+MUTATOR_HOOKFUNCTION(mutator_smneg, BuildMutatorsString) {
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ":StaleMoveNegation");
+}
+
+MUTATOR_HOOKFUNCTION(mutator_smneg, BuildMutatorsPrettyString) {
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Stale-move negation");
+}
+
+.float x_smneg_weight[Weapons_MAX];
+
+float smneg_multiplier(float weight) {
+ float a = autocvar_g_smneg_bonus_asymptote;
+ float x = max(
+ (!autocvar_g_smneg_bonus ? 0 : (-a + .1)),
+ weight / start_health
+ );
+ float z = (M_PI / 5) * a;
+ float f = (x > 0)
+ ? (atan(z / x) / (M_PI / 2))
+ : (tan(-(x / z)) + 1);
+ return f;
+}
+
+MUTATOR_HOOKFUNCTION(mutator_smneg, Damage_Calculate) {
+ float deathtype = M_ARGV(3, float);
+ Weapon w = DEATH_WEAPONOF(deathtype);
+ if (w == WEP_Null) return;
+
+ entity frag_attacker = M_ARGV(1, entity);
+ entity c = CS(frag_attacker);
+ float weight = c.x_smneg_weight[w.m_id];
+ float f = smneg_multiplier(weight);
+ float frag_damage = M_ARGV(4, float) = f * M_ARGV(4, float);
+ M_ARGV(6, vector) = f * M_ARGV(6, vector); // force
+
+ c.x_smneg_weight[w.m_id] = weight + frag_damage;
+ float restore = frag_damage * autocvar_g_smneg_cooldown_factor;
+ FOREACH(Weapons, it != WEP_Null && it != w, {
+ c.x_smneg_weight[it.m_id] -= restore;
+ });
+}
--- /dev/null
+#pragma once
MSG_INFO_NOTIF(QUIT_DISCONNECT, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 disconnected"), "")
MSG_INFO_NOTIF(QUIT_KICK_IDLING, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 was kicked for idling"), "")
MSG_INFO_NOTIF(QUIT_KICK_SPECTATING, N_CONSOLE, 0, 0, "", "", "", _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "")
+ MSG_INFO_NOTIF(QUIT_KICK_TEAMKILL, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 was kicked for excessive teamkilling"), "")
MSG_INFO_NOTIF(QUIT_SPECTATE, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^F3 is now spectating"), "")
MSG_INFO_NOTIF(RACE_ABANDONED, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^BG has abandoned the race"), "")
_Movetype_CheckWater(this);
this.origin = this.origin + movedt * this.velocity;
this.angles = this.angles + movedt * this.avelocity;
- _Movetype_LinkEdict(this, false);
break;
case MOVETYPE_STEP:
_Movetype_Physics_Step(this, movedt);
case MOVETYPE_PHYSICS:
break;
}
+
+ //_Movetype_CheckVelocity(this);
+
+ _Movetype_LinkEdict(this, true);
+
+ //_Movetype_CheckVelocity(this);
}
void Movetype_Physics_NoMatchTicrate(entity this, float movedt, bool isclient) // to be run every move frame
if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
return cvar(s);
}
- if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default)
+ if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "")
{
string s = strcat("g_physics_", autocvar_g_physics_clientselect_default, "_", option);
if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate", autocvar_sv_airaccelerate);
STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate", autocvar_sv_airstopaccelerate);
STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity", autocvar_sv_jumpvelocity);
+ STAT(MOVEVARS_JUMPVELOCITY_CROUCH, this) = Physics_ClientOption(this, "jumpvelocity_crouch", autocvar_sv_jumpvelocity_crouch);
STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump", autocvar_sv_track_canjump);
}
#endif
#endif
bool doublejump = false;
- float mjumpheight = PHYS_JUMPVELOCITY(this);
+ float mjumpheight = ((PHYS_JUMPVELOCITY_CROUCH(this) && IS_DUCKED(this)) ? PHYS_JUMPVELOCITY_CROUCH(this) : PHYS_JUMPVELOCITY(this));
bool track_jump = PHYS_CL_TRACK_CANJUMP(this);
if (MUTATOR_CALLHOOK(PlayerJump, this, mjumpheight, doublejump))
#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS)
#define PHYS_JUMPVELOCITY(s) STAT(MOVEVARS_JUMPVELOCITY, s)
+#define PHYS_JUMPVELOCITY_CROUCH(s) STAT(MOVEVARS_JUMPVELOCITY_CROUCH, s)
#define PHYS_MAXAIRSPEED(s) STAT(MOVEVARS_MAXAIRSPEED, s)
#define PHYS_MAXAIRSTRAFESPEED(s) STAT(MOVEVARS_MAXAIRSTRAFESPEED, s)
REGISTER_SP(KILLS);
REGISTER_SP(DEATHS);
REGISTER_SP(SUICIDES);
+REGISTER_SP(TEAMKILLS);
REGISTER_SP(FRAGS);
REGISTER_SP(ELO);
#ifdef SVQC
#include "physics/movetypes/movetypes.qh"
+float warmup_limit;
#endif
REGISTER_STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, float)
// FIXME: Was 0 on server, 1 on client. Still want that?
REGISTER_STAT(MOVEVARS_ENTGRAVITY, float, (this.gravity) ? this.gravity : 1)
REGISTER_STAT(MOVEVARS_JUMPVELOCITY, float)
+REGISTER_STAT(MOVEVARS_JUMPVELOCITY_CROUCH, float)
REGISTER_STAT(MOVEVARS_MAXAIRSPEED, float)
REGISTER_STAT(MOVEVARS_STEPHEIGHT, float, autocvar_sv_stepheight)
REGISTER_STAT(MOVEVARS_AIRACCEL_QW, float)
#ifdef CSQC
bool autocvar_cl_ghost_items_vehicle = true;
.vector item_glowmod;
+.bool item_simple; // probably not really needed, but better safe than sorry
void Item_SetAlpha(entity this)
{
bool veh_hud = (hud && autocvar_cl_ghost_items_vehicle);
{
if(this.ItemStatus & ITS_ANIMATE1)
{
- this.angles += this.avelocity * frametime;
+ if(!this.item_simple)
+ this.angles += this.avelocity * frametime;
float fade_in = bound(0, time - this.onground_time, 1);
setorigin(this, this.oldorigin + fade_in * ('0 0 10' + '0 0 8' * sin((time - this.onground_time) * 2)));
}
if(this.ItemStatus & ITS_ANIMATE2)
{
- this.angles += this.avelocity * frametime;
+ if(!this.item_simple)
+ this.angles += this.avelocity * frametime;
float fade_in = bound(0, time - this.onground_time, 1);
setorigin(this, this.oldorigin + fade_in * ('0 0 8' + '0 0 4' * sin((time - this.onground_time) * 3)));
}
Item_SetAlpha(this);
}
-void ItemDrawSimple(entity this)
-{
- if(this.gravity)
- {
- Movetype_Physics_MatchServer(this, false);
-
- if(IS_ONGROUND(this))
- this.gravity = 0;
- }
-
- Item_SetAlpha(this);
-}
-
void Item_PreDraw(entity this)
{
if(warpzone_warpzones_exist)
this.mdl = "";
string _fn = ReadString();
+ this.item_simple = false; // reset it!
if(autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI))
{
string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
- this.draw = ItemDrawSimple;
+ this.item_simple = true;
if(fexists(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".md3")))
this.mdl = strzone(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".md3"));
this.mdl = strzone(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".mdl"));
else
{
- this.draw = ItemDraw;
+ this.item_simple = false;
LOG_TRACE("Simple item requested for ", _fn, " but no model exists for it");
}
}
- if(this.draw != ItemDrawSimple)
+ if(!this.item_simple)
this.mdl = strzone(_fn);
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);
void Item_Touch(entity this, entity toucher)
{
-
// remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
- if (this.classname == "droppedweapon")
+ if (Item_IsLoot(this))
{
if (ITEM_TOUCH_NEEDKILL())
{
toucher = M_ARGV(1, entity);
- if (this.classname == "droppedweapon")
+ if (Item_IsExpiring(this))
{
this.strength_finished = max(0, this.strength_finished - time);
this.invincible_finished = max(0, this.invincible_finished - time);
this.superweapons_finished = max(0, this.superweapons_finished - time);
}
- entity it = this.itemdef;
- bool gave = ITEM_HANDLE(Pickup, it, this, toucher);
+ bool gave = ITEM_HANDLE(Pickup, this.itemdef, this, toucher);
if (!gave)
{
- if (this.classname == "droppedweapon")
+ if (Item_IsExpiring(this))
{
// undo what we did above
this.strength_finished += time;
Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
_sound (toucher, (this.itemdef.instanceOfPowerup ? CH_TRIGGER_SINGLE : CH_TRIGGER), (this.item_pickupsound ? this.item_pickupsound : Sound_fixpath(this.item_pickupsound_ent)), VOL_BASE, ATTEN_NORM);
- if (this.classname == "droppedweapon")
+ MUTATOR_CALLHOOK(ItemTouched, this, toucher);
+ if (wasfreed(this))
+ {
+ return;
+ }
+
+ if (Item_IsLoot(this))
+ {
delete(this);
- else if (this.spawnshieldtime)
+ return;
+ }
+ if (!this.spawnshieldtime)
{
- entity e;
- if(this.team)
+ return;
+ }
+ entity e;
+ if (this.team)
+ {
+ RandomSelection_Init();
+ IL_EACH(g_items, it.team == this.team,
{
- RandomSelection_Init();
- IL_EACH(g_items, it.team == this.team,
+ if (it.itemdef) // is a registered item
{
- if(it.itemdef) // is a registered item
- {
- Item_Show(it, -1);
- it.scheduledrespawntime = 0;
- RandomSelection_AddEnt(it, it.cnt, 0);
- }
- });
- e = RandomSelection_chosen_ent;
- Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
- }
- else
- e = this;
- Item_ScheduleRespawn(e);
+ Item_Show(it, -1);
+ it.scheduledrespawntime = 0;
+ RandomSelection_AddEnt(it, it.cnt, 0);
+ }
+ });
+ e = RandomSelection_chosen_ent;
+ Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
}
+ else
+ e = this;
+ Item_ScheduleRespawn(e);
}
void Item_Reset(entity this)
Item_Show(this, !this.state);
setorigin(this, this.origin);
- if (this.classname != "droppedweapon")
+ if (Item_IsLoot(this))
{
- setthink(this, Item_Think);
- this.nextthink = time;
-
- if (this.waypointsprite_attached)
- WaypointSprite_Kill(this.waypointsprite_attached);
-
- if (this.itemdef.instanceOfPowerup || (this.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
- Item_ScheduleInitialRespawn(this);
+ return;
+ }
+ setthink(this, Item_Think);
+ this.nextthink = time;
+ if (this.waypointsprite_attached)
+ {
+ WaypointSprite_Kill(this.waypointsprite_attached);
+ }
+ if (this.itemdef.instanceOfPowerup || (this.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
+ {
+ Item_ScheduleInitialRespawn(this);
}
}
return rating;
}
-.int item_group;
-.int item_group_count;
float healtharmor_pickupevalfunc(entity player, entity item)
{
float c = 0;
return;
}
- // is it a dropped weapon?
- if (this.classname == "droppedweapon")
+ if (Item_IsLoot(this))
{
this.reset = SUB_Remove;
- // it's a dropped weapon
set_movetype(this, MOVETYPE_TOSS);
// Savage: remove thrown items after a certain period of time ("garbage collection")
this.takedamage = DAMAGE_YES;
this.event_damage = Item_Damage;
- if(this.strength_finished || this.invincible_finished || this.superweapons_finished)
+ if (Item_IsExpiring(this))
{
// if item is worthless after a timer, have it expire then
this.nextthink = max(this.strength_finished, this.invincible_finished, this.superweapons_finished);
if(def.instanceOfWeaponPickup)
{
- if (this.classname != "droppedweapon") // if dropped, colormap is already set up nicely
+ if (!Item_IsLoot(this)) // if dropped, colormap is already set up nicely
this.colormap = 1024; // color shirt=0 pants=0 grey
else
this.gravity = 1;
void StartItem(entity this, GameItem def)
{
+ def = def.m_spawnfunc_hookreplace(def, this);
+ if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
+ {
+ delete(this);
+ return;
+ }
+ this.classname = def.m_canonical_spawnfunc;
_StartItem(
this,
this.itemdef = def,
}
}
-spawnfunc(item_rockets)
-{
- StartItem(this, ITEM_Rockets);
-}
-
-spawnfunc(item_bullets)
-{
- if(!weaponswapping && autocvar_sv_q3acompat_machineshotgunswap &&
- (this.classname != "droppedweapon"))
- {
- weaponswapping = true;
- spawnfunc_item_shells(this);
- weaponswapping = false;
- return;
- }
-
- StartItem(this, ITEM_Bullets);
-}
-
-spawnfunc(item_cells)
-{
- StartItem(this, ITEM_Cells);
-}
-
-spawnfunc(item_plasma)
-{
- StartItem(this, ITEM_Plasma);
-}
-
-spawnfunc(item_shells)
-{
- if(!weaponswapping && autocvar_sv_q3acompat_machineshotgunswap &&
- (this.classname != "droppedweapon"))
- {
- weaponswapping = true;
- spawnfunc_item_bullets(this);
- weaponswapping = false;
- return;
- }
-
- StartItem(this, ITEM_Shells);
-}
-
-spawnfunc(item_armor_small)
-{
- StartItem(this, ITEM_ArmorSmall);
-}
-
-spawnfunc(item_armor_medium)
-{
- StartItem(this, ITEM_ArmorMedium);
-}
-
-spawnfunc(item_armor_big)
-{
- StartItem(this, ITEM_ArmorBig);
-}
-
-spawnfunc(item_armor_mega)
-{
- StartItem(this, ITEM_ArmorMega);
-}
-
-spawnfunc(item_health_small)
-{
- StartItem(this, ITEM_HealthSmall);
-}
-
-spawnfunc(item_health_medium)
-{
- StartItem(this, ITEM_HealthMedium);
-}
-
-spawnfunc(item_health_big)
-{
- StartItem(this, ITEM_HealthBig);
-}
-
-spawnfunc(item_health_mega)
-{
- StartItem(this, ITEM_HealthMega);
-}
-
-// support old misnamed entities
-spawnfunc(item_armor1) { spawnfunc_item_armor_small(this); } // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
-spawnfunc(item_armor25) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_armor_large) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_health1) { spawnfunc_item_health_small(this); }
-spawnfunc(item_health25) { spawnfunc_item_health_medium(this); }
-spawnfunc(item_health_large) { spawnfunc_item_health_big(this); }
-spawnfunc(item_health100) { spawnfunc_item_health_mega(this); }
-
-spawnfunc(item_strength)
-{
- StartItem(this, ITEM_Strength);
-}
-
-spawnfunc(item_invincible)
-{
- StartItem(this, ITEM_Shield);
-}
-
-// compatibility:
-spawnfunc(item_quad) { this.classname = "item_strength";spawnfunc_item_strength(this);}
-
void target_items_use(entity this, entity actor, entity trigger)
{
- if(actor.classname == "droppedweapon")
+ if(Item_IsLoot(actor))
{
EXACTTRIGGER_TOUCH(this, trigger);
delete(actor);
EXACTTRIGGER_TOUCH(this, trigger);
}
- IL_EACH(g_items, it.enemy == actor && it.classname == "droppedweapon",
+ IL_EACH(g_items, it.enemy == actor && Item_IsLoot(it),
{
delete(it);
});
}
}
-spawnfunc(item_fuel)
-{
- StartItem(this, ITEM_JetpackFuel);
-}
-
-spawnfunc(item_fuel_regen)
-{
- if(start_items & ITEM_JetpackRegen.m_itemid)
- {
- spawnfunc_item_fuel(this);
- return;
- }
- StartItem(this, ITEM_JetpackRegen);
-}
-
-spawnfunc(item_jetpack)
-{
- if(start_items & ITEM_Jetpack.m_itemid)
- {
- spawnfunc_item_fuel(this);
- return;
- }
- StartItem(this, ITEM_Jetpack);
-}
-
float GiveWeapon(entity e, float wpn, float op, float val)
{
WepSet v0, v1;
#pragma once
-#ifdef SVQC
-#include <server/defs.qh>
-#endif
-
const int AMMO_COUNT = 4; // amount of ammo types to show in the inventory panel
// item networking
#ifdef SVQC
void StartItem(entity this, entity a);
+.int item_group;
+.int item_group_count;
#endif
#ifdef CSQC
.vector colormod;
void ItemDraw(entity this);
-void ItemDrawSimple(entity this);
#endif
#ifdef SVQC
-spawnfunc(item_strength);
-spawnfunc(item_invincible);
-spawnfunc(item_armor_small);
-spawnfunc(item_shells);
-spawnfunc(item_bullets);
-spawnfunc(item_rockets);
float autocvar_sv_simple_items;
bool ItemSend(entity this, entity to, int sf);
-
bool have_pickup_item(entity this);
const float ITEM_RESPAWN_TICKS = 10;
.float max_armorvalue;
.float pickup_anyway;
+.float item_respawncounter;
+
void Item_Show (entity e, float mode);
void Item_Respawn (entity this);
return -1;
}
+/// \brief Returns whether team is valid.
+/// \param[in] team_ Team to check.
+/// \return True if team is valid, false otherwise.
+bool Team_IsValidTeam(int team_)
+{
+ switch (team_)
+ {
+ case NUM_TEAM_1:
+ case NUM_TEAM_2:
+ case NUM_TEAM_3:
+ case NUM_TEAM_4:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// \brief Returns whether team number is valid.
+/// \param[in] number Team number to check.
+/// \return True if team number is valid, false otherwise.
+bool Team_IsValidNumber(int number)
+{
+ switch (number)
+ {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
float Team_NumberToTeam(float number)
{
switch(number)
dbr.velocity_x = this.debrisvelocity.x + this.debrisvelocityjitter.x * crandom();
dbr.velocity_y = this.debrisvelocity.y + this.debrisvelocityjitter.y * crandom();
dbr.velocity_z = this.debrisvelocity.z + this.debrisvelocityjitter.z * crandom();
- this.velocity = this.velocity + force * this.debrisdamageforcescale;
+ dbr.velocity = dbr.velocity + force * this.debrisdamageforcescale;
+ dbr.angles = this.angles;
dbr.avelocity_x = random()*this.debrisavelocityjitter.x;
dbr.avelocity_y = random()*this.debrisavelocityjitter.y;
dbr.avelocity_z = random()*this.debrisavelocityjitter.z;
if(this.spawnflags & DOOR_NOSPLASH)
if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
return;
- this.health = this.health - damage;
- if (this.health <= 0)
+ if (this.spawnflags & BUTTON_DONTACCUMULATEDMG)
{
- this.enemy = attacker;
- button_fire(this);
+ if (this.health <= damage)
+ {
+ this.enemy = attacker;
+ button_fire(this);
+ }
+ }
+ else
+ {
+ this.health = this.health - damage;
+ if (this.health <= 0)
+ {
+ this.enemy = attacker;
+ button_fire(this);
+ }
}
}
#pragma once
+
+const int BUTTON_DONTACCUMULATEDMG = 128;
if (this.health)
{
+ //this.canteamdamage = true; // TODO
this.takedamage = DAMAGE_YES;
this.event_damage = door_damage;
}
if (this.health)
{
+ //this.canteamdamage = true; // TODO
this.takedamage = DAMAGE_YES;
this.event_damage = door_damage;
}
if (this.spawnflags & SECRET_YES_SHOOT)
{
+ //this.canteamdamage = true; // TODO
this.health = 10000;
this.takedamage = DAMAGE_YES;
this.event_damage = fd_secret_damage;
void func_ladder_init(entity this)
{
settouch(this, func_ladder_touch);
-
trigger_init(this);
func_ladder_link(this);
+
+ if(min(this.absmax.x - this.absmin.x, this.absmax.y - this.absmin.y) > 100)
+ return;
+
+ entity tracetest_ent = spawn();
+ setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+ tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+
+ vector top_min = (this.absmin + this.absmax) / 2;
+ top_min.z = this.absmax.z;
+ vector top_max = top_min;
+ top_max.z += PL_MAX_CONST.z - PL_MIN_CONST.z;
+ tracebox(top_max + jumpstepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+ if(trace_startsolid)
+ {
+ tracebox(top_max + stepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+ if(trace_startsolid)
+ {
+ tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+ if(trace_startsolid)
+ {
+ if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x
+ && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x)
+ {
+ // move top on one side
+ top_max.y = top_min.y = this.absmin.y + (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75;
+ }
+ else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y
+ && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y)
+ {
+ // move top on one side
+ top_max.x = top_min.x = this.absmin.x + (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75;
+ }
+ tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+ if(trace_startsolid)
+ {
+ if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x
+ && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x)
+ {
+ // alternatively on the other side
+ top_max.y = top_min.y = this.absmax.y - (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75;
+ }
+ else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y
+ && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y)
+ {
+ // alternatively on the other side
+ top_max.x = top_min.x = this.absmax.x - (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75;
+ }
+ tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+ }
+ }
+ }
+ }
+ if(trace_startsolid || trace_endpos.z < this.absmax.z)
+ {
+ delete(tracetest_ent);
+ return;
+ }
+
+ this.bot_pickup = true; // allow bots to make use of this ladder
+ float cost = waypoint_getlinearcost(trace_endpos.z - this.absmin.z);
+ top_min = trace_endpos;
+ waypoint_spawnforteleporter_boxes(this, WAYPOINTFLAG_LADDER, this.absmin, this.absmax, top_min, top_min, cost);
}
spawnfunc(func_ladder)
spawnfunc_info_teleport_destination(this);
}
-spawnfunc(target_teleporter)
-{
- spawnfunc_info_teleport_destination(this);
-}
-
#elif defined(CSQC)
void teleport_dest_remove(entity this)
}
player.lastteleporttime = time;
+ player.lastteleport_origin = from;
}
#endif
}
void teleport_findtarget(entity this)
{
+ bool istrigger = (this.solid == SOLID_TRIGGER);
+
int n = 0;
- entity e;
- for(e = NULL; (e = find(e, targetname, this.target)); )
+ for(entity e = NULL; (e = find(e, targetname, this.target)); )
{
++n;
#ifdef SVQC
if(e.move_movetype == MOVETYPE_NONE)
- waypoint_spawnforteleporter(this, e.origin, 0);
+ {
+ entity tracetest_ent = spawn();
+ setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+ tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ waypoint_spawnforteleporter(this, e.origin, 0, tracetest_ent);
+ delete(tracetest_ent);
+ }
if(e.classname != "info_teleport_destination")
LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.");
#endif
else if(n == 1)
{
// exactly one dest - bots love that
- this.enemy = find(e, targetname, this.target);
+ this.enemy = find(NULL, targetname, this.target);
}
else
{
}
// now enable touch
- settouch(this, Teleport_Touch);
+ if(istrigger)
+ settouch(this, Teleport_Touch);
#ifdef SVQC
- trigger_teleport_link(this);
+ if(istrigger)
+ trigger_teleport_link(this);
#endif
}
#ifdef SVQC
pl.oldvelocity = pl.velocity;
#endif
- // reset teleport time tracking too (or multijump can cause insane speeds)
- pl.lastteleporttime = time;
}
}
#ifdef CSQC
.entity realowner;
-.float lastteleporttime;
#endif
#include "counter.qh"
#ifdef SVQC
+void counter_reset(entity this);
+
void counter_use(entity this, entity actor, entity trigger)
{
this.count -= 1;
if (this.count < 0)
return;
+ bool doactivate = (this.spawnflags & 4);
+
if (this.count == 0)
{
- if(IS_PLAYER(actor) && (this.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
+ if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
- this.enemy = actor;
- multi_trigger(this);
+ doactivate = true;
+
+ if(this.respawntime)
+ {
+ setthink(this, counter_reset);
+ this.nextthink = time + this.respawntime;
+ }
}
else
{
- if(IS_PLAYER(actor) && (this.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
- if(this.count >= 4)
- Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
- else
- Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count);
+ if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
+ {
+ if(this.count >= 4)
+ Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
+ else
+ Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count);
+ }
}
+
+ if(doactivate)
+ SUB_UseTargets(this, actor, trigger);
}
void counter_reset(entity this)
{
+ setthink(this, func_null);
+ this.nextthink = 0;
this.count = this.cnt;
- multi_reset(this);
}
/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
Acts as an intermediary for an action that takes multiple inputs.
-If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+If nomessage is not set, it will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+If respawntime is set, it will re-enable itself after the time once the sequence has been completed
After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
*/
spawnfunc(trigger_counter)
{
- this.wait = -1;
if (!this.count)
this.count = 2;
this.cnt = this.count;
#include "delay.qh"
#ifdef SVQC
+void delay_delayeduse(entity this)
+{
+ SUB_UseTargets(this, this.enemy, this.goalentity);
+ this.enemy = this.goalentity = NULL;
+}
+
void delay_use(entity this, entity actor, entity trigger)
{
- setthink(this, SUB_UseTargets_self);
- this.nextthink = time + this.wait;
+ this.enemy = actor;
+ this.goalentity = trigger;
+ setthink(this, delay_delayeduse);
+ this.nextthink = time + this.wait;
}
void delay_reset(entity this)
{
+ this.enemy = this.goalentity = NULL;
setthink(this, func_null);
this.nextthink = 0;
}
if (!IS_DEAD(toucher))
if (toucher.triggerhealtime < time)
{
- EXACTTRIGGER_TOUCH(this, toucher);
- toucher.triggerhealtime = time + 1;
+ bool is_trigger = !boolean(!this.nottargeted && this.targetname != "");
+ if(is_trigger)
+ EXACTTRIGGER_TOUCH(this, toucher);
+ if(this.delay > 0)
+ toucher.triggerhealtime = time + this.delay;
+ bool playthesound = (this.spawnflags & 4);
if (toucher.health < this.max_health)
{
+ playthesound = true;
toucher.health = min(toucher.health + this.health, this.max_health);
toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
- _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
}
+
+ if(playthesound)
+ _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
}
}
}
-spawnfunc(trigger_heal)
+void trigger_heal_use(entity this, entity actor, entity trigger)
{
- this.active = ACTIVE_ACTIVE;
+ trigger_heal_touch(this, actor);
+}
- EXACTTRIGGER_INIT;
- settouch(this, trigger_heal_touch);
- if (!this.health)
+void trigger_heal_init(entity this)
+{
+ this.active = ACTIVE_ACTIVE;
+ if(!this.delay)
+ this.delay = 1;
+ if(!this.health)
this.health = 10;
- if (!this.max_health)
- this.max_health = 200; //Max health topoff for field
+ if(!this.max_health)
+ this.max_health = 200; // max health topoff for field
if(this.noise == "")
this.noise = "misc/mediumhealth.wav";
precache_sound(this.noise);
}
+
+spawnfunc(trigger_heal)
+{
+ EXACTTRIGGER_INIT;
+ settouch(this, trigger_heal_touch);
+ trigger_heal_init(this);
+}
+
+spawnfunc(target_heal)
+{
+ this.use = trigger_heal_use;
+ trigger_heal_init(this);
+}
#endif
tgt - target entity (can be either a point or a model entity; if it is
the latter, its midpoint is used)
ht - jump height, measured from the higher one of org and tgt's midpoint
+ pushed_entity - object that is to be pushed
Returns: velocity for the jump
- the global trigger_push_calculatevelocity_flighttime is set to the total
- jump time
*/
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
{
float grav, sdist, zdist, vs, vz, jumpheight;
vector sdir, torg;
torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
- grav = PHYS_GRAVITY(other);
- if(PHYS_ENTGRAVITY(other))
- grav *= PHYS_ENTGRAVITY(other);
+ grav = PHYS_GRAVITY(NULL);
+ if(pushed_entity && PHYS_ENTGRAVITY(pushed_entity))
+ grav *= PHYS_ENTGRAVITY(pushed_entity);
zdist = torg.z - org.z;
sdist = vlen(torg - org - zdist * '0 0 1');
if(zdist == 0)
solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
+ float flighttime;
if(zdist < 0)
{
// down-jump
// almost straight line type
// jump apex is before the jump
// we must take the larger one
- trigger_push_calculatevelocity_flighttime = solution.y;
+ flighttime = solution.y;
}
else
{
// regular jump
// jump apex is during the jump
// we must take the larger one too
- trigger_push_calculatevelocity_flighttime = solution.y;
+ flighttime = solution.y;
}
}
else
// almost straight line type
// jump apex is after the jump
// we must take the smaller one
- trigger_push_calculatevelocity_flighttime = solution.x;
+ flighttime = solution.x;
}
else
{
// regular jump
// jump apex is during the jump
// we must take the larger one
- trigger_push_calculatevelocity_flighttime = solution.y;
+ flighttime = solution.y;
}
}
- vs = sdist / trigger_push_calculatevelocity_flighttime;
+ vs = sdist / flighttime;
// finally calculate the velocity
return sdir * vs + '0 0 1' * vz;
if(this.enemy)
{
- targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height);
+ targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ);
}
else if(this.target && this.target != "")
{
else
RandomSelection_AddEnt(e, 1, 1);
}
- targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height);
+ targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ);
}
else
{
centerprint(targ, this.message);
}
else
+ {
targ.lastteleporttime = time;
+ targ.lastteleport_origin = targ.origin;
+ }
if (!IS_DEAD(targ))
animdecide_setaction(targ, ANIMACTION_JUMP, true);
}
else
- targ.jumppadcount = true;
+ targ.jumppadcount = 1;
// reset tracking of who pushed you into a hazard (for kill credit)
targ.pushltime = 0;
#ifdef SVQC
void trigger_push_link(entity this);
void trigger_push_updatelink(entity this);
+bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org)
+{
+ setorigin(tracetest_ent, org);
+ tracetoss(tracetest_ent, tracetest_ent);
+ if(trace_startsolid)
+ return false;
+
+ 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 + ofs);
+ tracetoss(tracetest_ent, tracetest_ent);
+ if (trace_startsolid && ofs.z)
+ {
+ setorigin(tracetest_ent, targ.origin + ofs / 2);
+ tracetoss(tracetest_ent, tracetest_ent);
+ if (trace_startsolid && ofs.z)
+ {
+ setorigin(tracetest_ent, targ.origin);
+ tracetoss(tracetest_ent, tracetest_ent);
+ if (trace_startsolid)
+ return false;
+ }
+ }
+ }
+ tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent);
+ return true;
+}
#endif
-void trigger_push_findtarget(entity this)
+
+/// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise
+/// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter)
+bool trigger_push_test(entity this, entity item)
{
// 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)
{
int n = 0;
+#ifdef SVQC
+ vector vel = '0 0 0';
+#endif
for(entity t = NULL; (t = find(t, targetname, this.target)); )
{
++n;
#ifdef SVQC
+ if(t.move_movetype != MOVETYPE_NONE)
+ continue;
+
entity e = spawn();
- setorigin(e, org);
setsize(e, PL_MIN_CONST, PL_MAX_CONST);
- e.velocity = trigger_push_calculatevelocity(org, t, this.height);
- tracetoss(e, e);
- if(e.move_movetype == MOVETYPE_NONE)
- waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+ e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ e.velocity = trigger_push_calculatevelocity(org, t, this.height, e);
+
+ if(item)
+ {
+ setorigin(e, org);
+ tracetoss(e, e);
+ bool r = (trace_ent == item);
+ delete(e);
+ return r;
+ }
+
+ vel = e.velocity;
+ vector best_target = '0 0 0';
+ vector best_org = '0 0 0';
+ vector best_vel = '0 0 0';
+ bool valid_best_target = false;
+ if (trigger_push_testorigin(e, t, this, org))
+ {
+ best_target = trace_endpos;
+ best_org = org;
+ best_vel = e.velocity;
+ valid_best_target = true;
+ }
+
+ vector new_org;
+ vector dist = t.origin - org;
+ if (dist.x || dist.y) // if not perfectly vertical
+ {
+ // test trajectory with different starting points, sometimes the trajectory
+ // starting from the jumppad origin can't reach the real destination
+ // and destination waypoint ends up near the jumppad itself
+ vector flatdir = normalize(dist - eZ * dist.z);
+ vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y));
+ new_org = org + ofs;
+ e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e);
+ vel = e.velocity;
+ if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed))
+ e.velocity = autocvar_sv_maxspeed * flatdir;
+ if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
+ {
+ best_target = trace_endpos;
+ best_org = new_org;
+ best_vel = vel;
+ valid_best_target = true;
+ }
+ new_org = org - ofs;
+ e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e);
+ vel = e.velocity;
+ if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed))
+ e.velocity = autocvar_sv_maxspeed * flatdir;
+ if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
+ {
+ best_target = trace_endpos;
+ best_org = new_org;
+ best_vel = vel;
+ valid_best_target = true;
+ }
+ }
+
+ if (valid_best_target)
+ {
+ if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST)))
+ {
+ float velxy = vlen(vec2(best_vel));
+ float cost = vlen(vec2(t.origin - best_org)) / velxy;
+ if(velxy < autocvar_sv_maxspeed)
+ velxy = autocvar_sv_maxspeed;
+ cost += vlen(vec2(best_target - t.origin)) / velxy;
+ waypoint_spawnforteleporter(this, best_target, cost, e);
+ }
+ }
delete(e);
#endif
}
+ if(item)
+ return false;
+
if(!n)
{
// no dest!
#ifdef SVQC
objerror (this, "Jumppad with nonexistant target");
#endif
- return;
+ return false;
}
else if(n == 1)
{
else
{
entity e = spawn();
- setorigin(e, org);
setsize(e, PL_MIN_CONST, PL_MAX_CONST);
+ e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ setorigin(e, org);
e.velocity = this.movedir;
tracetoss(e, e);
- waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+ if(item)
+ {
+ bool r = (trace_ent == item);
+ delete(e);
+ return r;
+ }
+ if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST)))
+ waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity), e);
delete(e);
}
defer(this, 0.1, trigger_push_updatelink);
#endif
+ return true;
+}
+
+void trigger_push_findtarget(entity this)
+{
+ trigger_push_test(this, NULL);
}
#ifdef SVQC
trigger_push_link(this); // link it now
+ IL_PUSH(g_jumppads, this);
+
// this must be called to spawn the teleport waypoints for bots
InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET);
}
#pragma once
+IntrusiveList g_jumppads;
+STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); }
+
const float PUSH_ONCE = 1;
const float PUSH_SILENT = 2;
.float jumppadcount;
.entity jumppadsused[NUM_JUMPPADSUSED];
-float trigger_push_calculatevelocity_flighttime;
-
#ifdef SVQC
void SUB_UseTargets(entity this, entity actor, entity trigger);
void trigger_push_use(entity this, entity actor, entity trigger);
+bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org);
#endif
/*
tgt - target entity (can be either a point or a model entity; if it is
the latter, its midpoint is used)
ht - jump height, measured from the higher one of org and tgt's midpoint
+ pushed_entity - object that is to be pushed
Returns: velocity for the jump
- the global trigger_push_calculatevelocity_flighttime is set to the total
- jump time
*/
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
void trigger_push_touch(entity this, entity toucher);
.vector dest;
+bool trigger_push_test(entity this, entity item);
void trigger_push_findtarget(entity this);
/*
if(this.spawnflags & DOOR_NOSPLASH)
if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
return;
+ if(this.team)
+ if(((this.spawnflags & 4) == 0) == (this.team != attacker.team))
+ return;
this.health = this.health - damage;
if (this.health <= 0)
{
{
if (this.spawnflags & SPAWNFLAG_NOTOUCH)
objerror (this, "health and notouch don't make sense\n");
+ this.canteamdamage = true;
this.max_health = this.health;
this.event_damage = multi_eventdamage;
this.takedamage = DAMAGE_YES;
this.use = SUB_UseTargets;
this.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
}
+
+spawnfunc(target_relay) { spawnfunc_trigger_relay(this); }
#endif
}
#endif
-void Teleport_Touch(entity this, entity toucher)
+bool Teleport_Active(entity this, entity player)
{
if (this.active != ACTIVE_ACTIVE)
- return;
+ return false;
#ifdef SVQC
- if (!toucher.teleportable)
- return;
+ if (!player.teleportable)
+ return false;
- if(toucher.vehicle)
- if(!toucher.vehicle.teleportable)
- return;
+ if(player.vehicle)
+ if(!player.vehicle.teleportable)
+ return false;
- if(IS_TURRET(toucher))
- return;
+ if(IS_TURRET(player))
+ return false;
#elif defined(CSQC)
- if(!IS_PLAYER(toucher))
- return;
+ if(!IS_PLAYER(player))
+ return false;
#endif
- if(IS_DEAD(toucher))
- return;
+ if(IS_DEAD(player))
+ return false;
if(this.team)
- if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, toucher)))
- return;
+ if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, player)))
+ return false;
- EXACTTRIGGER_TOUCH(this, toucher);
+ return true;
+}
+
+void Teleport_Touch(entity this, entity toucher)
+{
+ entity player = toucher;
+
+ if(!Teleport_Active(this, player))
+ return;
+
+ EXACTTRIGGER_TOUCH(this, player);
#ifdef SVQC
- if(IS_PLAYER(toucher))
- RemoveGrapplingHooks(toucher);
+ if(IS_PLAYER(player))
+ RemoveGrapplingHooks(player);
#endif
entity e;
- e = Simple_TeleportPlayer(this, toucher);
+ e = Simple_TeleportPlayer(this, player);
#ifdef SVQC
string s = this.target; this.target = string_null;
- SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for trigger too?
+ SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too?
if (!this.target) this.target = s;
- SUB_UseTargets(e, toucher, toucher);
+ SUB_UseTargets(e, player, player);
#endif
}
+#ifdef SVQC
+void target_teleport_use(entity this, entity actor, entity trigger)
+{
+ entity player = actor;
+
+ if(!Teleport_Active(this, player))
+ return;
+
+ if(IS_PLAYER(player))
+ RemoveGrapplingHooks(player);
+
+ entity e = Simple_TeleportPlayer(this, player);
+
+ string s = this.target; this.target = string_null;
+ SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too?
+ if (!this.target) this.target = s;
+
+ SUB_UseTargets(e, player, player);
+}
+#endif
+
#ifdef SVQC
float trigger_teleport_send(entity this, entity to, float sf)
{
IL_PUSH(g_teleporters, this);
}
+
+spawnfunc(target_teleporter)
+{
+ if(this.target == "")
+ {
+ // actually a destination!
+ spawnfunc_info_teleport_destination(this);
+ return;
+ }
+
+ this.active = ACTIVE_ACTIVE;
+
+ this.use = target_teleport_use;
+
+ if(this.noise != "")
+ FOREACH_WORD(this.noise, true, precache_sound(it));
+
+ InitializeEntity(this, teleport_findtarget, INITPRIO_FINDTARGET);
+}
#elif defined(CSQC)
NET_HANDLE(ENT_CLIENT_TRIGGER_TELEPORT, bool isnew)
{
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);
+const int VIEWLOC_FREEMOVE = BIT(2);
+
#ifdef CSQC
.entity goalentity;
.entity enemy;
void SUB_UseTargets(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, false); }
void SUB_UseTargets_PreventReuse(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, true); }
-
-void SUB_UseTargets_self(entity this)
-{
- SUB_UseTargets(this, NULL, NULL);
-}
vector old_movement = PHYS_CS(this).movement;
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;
+ if((this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && !(time < this.ladder_time))
+ PHYS_CS(this).movement_y = old_movement_x;
+ else
+ PHYS_CS(this).movement_y = 0;
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_FREEMOVE) && this.angles_y < 0 && !(time < this.ladder_time))
+ PHYS_CS(this).movement_y = -PHYS_CS(this).movement_y;
- 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(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(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))
+ if(!(this.viewloc.spawnflags & VIEWLOC_FREEMOVE))
+ {
#ifdef SVQC
//PHYS_INPUT_BUTTON_CROUCH(this) = (old_movement_x < 0);
if (old_movement.x < 0)
}
//else { input_buttons &= ~16; this.flags &= ~FL_DUCKED; }
#endif
+ }
}
}
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;
bool autocvar_cam_snap_hard;
bool autocvar_cam_snap_unlock;
+bool autocvar_cam_useangle = true;
void viewloc_SetViewLocation()
{
entity view = CSQCModel_server2csqc(player_localentnum - 1);
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;
+ vector position_a = view.viewloc.enemy.origin;
+ vector position_b = view.viewloc.goalentity.origin;
+ vector camera_angle = '0 0 0';
+ vector camera_position;
- position_a = view.viewloc.enemy.origin;
- position_b = view.viewloc.goalentity.origin;
-
-#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);
+ // use camera's angle when possible
+ if (autocvar_cam_useangle) {
+ camera_angle = view.viewloc.enemy.movedir;
+ }
// a tracking camera follows the player when it leaves the world box
if (autocvar_cam_track || !have_sidescroll) {
}
// 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);
/* if the difference between the old and new angle is 60 degrees or more, switch angles.
* 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.
+ * doing this results 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 (!(view.viewloc.spawnflags & VIEWLOC_FREEMOVE)) {
+ 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 + view.view_ofs, mpos); // get new angles
+ }
+ view.angles_y = view_angle.y;
+ setproperty(VF_CL_VIEWANGLES, view_angle);
}
}
}
/** idle frame */
const int WS_READY = 4;
-#ifdef SVQC
-.int ammo_shells;
-.int ammo_nails;
-.int ammo_rockets;
-.int ammo_cells;
-.int ammo_plasma = _STAT(PLASMA);
-.int ammo_fuel = _STAT(FUEL);
-.int ammo_none;
-#else
-.int ammo_shells;
-.int ammo_nails;
-.int ammo_rockets;
-.int ammo_cells;
-.int ammo_plasma;
-.int ammo_fuel;
-.int ammo_none;
-#endif
-
/** fields which are explicitly/manually set are marked with "M", fields set automatically are marked with "A" */
CLASS(Weapon, Object)
ATTRIB(Weapon, m_id, int, 0);
+ /** the canonical spawnfunc name */
+ ATTRIB(Weapon, m_canonical_spawnfunc, string);
+ /** control what happens when this weapon is spawned */
+ METHOD(Weapon, m_spawnfunc_hookreplace, Weapon(Weapon this, entity e)) { return this; }
/** A: WEPSET_id : WEPSET_... */
ATTRIB(Weapon, weapons, WepSet, '0 0 0');
/** M: ammotype : main ammo type */
}
ENDCLASS(Weapon)
+#ifdef SVQC
+
+void weapon_defaultspawnfunc(entity this, Weapon e);
+#define SPAWNFUNC_WEAPON(name, weapon) \
+ spawnfunc(name) { weapon_defaultspawnfunc(this, weapon); }
+
+#else
+
+#define SPAWNFUNC_WEAPON(name, weapon)
+
+#endif
+
#include <common/items/_mod.qh>
CLASS(WeaponPickup, Pickup)
ATTRIB(WeaponPickup, m_weapon, Weapon);
#include "arc.qh"
#ifdef SVQC
-spawnfunc(weapon_arc) { weapon_defaultspawnfunc(this, WEP_ARC); }
bool W_Arc_Beam_Send(entity this, entity to, int sf)
{
#pragma once
CLASS(Arc, Weapon)
+/* spawnfunc */ ATTRIB(Arc, m_canonical_spawnfunc, string, "weapon_arc");
/* ammotype */ ATTRIB(Arc, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Arc, impulse, int, 3);
/* flags */ ATTRIB(Arc, spawnflags, int, WEP_TYPE_HITSCAN);
ENDCLASS(Arc)
REGISTER_WEAPON(ARC, arc, NEW(Arc));
+SPAWNFUNC_WEAPON(weapon_arc, WEP_ARC)
#ifdef GAMEQC
const float ARC_MAX_SEGMENTS = 20;
#include "blaster.qh"
#ifdef SVQC
-spawnfunc(weapon_blaster) { weapon_defaultspawnfunc(this, WEP_BLASTER); }
-spawnfunc(weapon_laser) { spawnfunc_weapon_blaster(this); }
void W_Blaster_Touch(entity this, entity toucher)
{
#pragma once
CLASS(Blaster, Weapon)
+/* spawnfunc */ ATTRIB(Blaster, m_canonical_spawnfunc, string, "weapon_blaster");
/* ammotype */ //ATTRIB(Blaster, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(Blaster, impulse, int, 1);
/* flags */ ATTRIB(Blaster, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
ENDCLASS(Blaster)
REGISTER_WEAPON(BLASTER, blaster, NEW(Blaster));
+SPAWNFUNC_WEAPON(weapon_blaster, WEP_BLASTER)
+SPAWNFUNC_WEAPON(weapon_laser, WEP_BLASTER)
+
#ifdef SVQC
.float blaster_damage;
.float blaster_edgedamage;
.float blaster_radius;
.float blaster_force;
.float blaster_lifetime;
+
#endif
#include "crylink.qh"
#ifdef SVQC
-spawnfunc(weapon_crylink) { weapon_defaultspawnfunc(this, WEP_CRYLINK); }
void W_Crylink_CheckLinks(entity e)
{
#pragma once
CLASS(Crylink, Weapon)
+/* spawnfunc */ ATTRIB(Crylink, m_canonical_spawnfunc, string, "weapon_crylink");
/* ammotype */ ATTRIB(Crylink, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Crylink, impulse, int, 6);
-/* flags */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_CANCLIMB | WEP_FLAG_NODUAL);
+/* flags */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_CANCLIMB);
/* rating */ ATTRIB(Crylink, bot_pickupbasevalue, float, 6000);
/* color */ ATTRIB(Crylink, wpcolor, vector, '1 0.5 1');
/* modelname */ ATTRIB(Crylink, mdl, string, "crylink");
ENDCLASS(Crylink)
REGISTER_WEAPON(CRYLINK, crylink, NEW(Crylink));
+SPAWNFUNC_WEAPON(weapon_crylink, WEP_CRYLINK)
+
#ifdef SVQC
.float gravity;
.float crylink_waitrelease;
#include "devastator.qh"
#ifdef SVQC
-spawnfunc(weapon_devastator) { weapon_defaultspawnfunc(this, WEP_DEVASTATOR); }
-spawnfunc(weapon_rocketlauncher) { spawnfunc_weapon_devastator(this); }
.entity lastrocket;
#pragma once
CLASS(Devastator, Weapon)
+/* spawnfunc */ ATTRIB(Devastator, m_canonical_spawnfunc, string, "weapon_devastator");
/* ammotype */ ATTRIB(Devastator, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Devastator, impulse, int, 9);
/* flags */ ATTRIB(Devastator, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
ENDCLASS(Devastator)
REGISTER_WEAPON(DEVASTATOR, devastator, NEW(Devastator));
+SPAWNFUNC_WEAPON(weapon_devastator, WEP_DEVASTATOR)
+SPAWNFUNC_WEAPON(weapon_rocketlauncher, WEP_DEVASTATOR)
+
#ifdef SVQC
.float rl_release;
.float rl_detonate_later;
#include "electro.qh"
#ifdef SVQC
-spawnfunc(weapon_electro) { weapon_defaultspawnfunc(this, WEP_ELECTRO); }
void W_Electro_TriggerCombo(vector org, float rad, entity own)
{
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);
#pragma once
CLASS(Electro, Weapon)
+/* spawnfunc */ ATTRIB(Electro, m_canonical_spawnfunc, string, "weapon_electro");
/* ammotype */ ATTRIB(Electro, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Electro, impulse, int, 5);
/* flags */ ATTRIB(Electro, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
ENDCLASS(Electro)
REGISTER_WEAPON(ELECTRO, electro, NEW(Electro));
+SPAWNFUNC_WEAPON(weapon_electro, WEP_ELECTRO)
#ifdef SVQC
.float electro_count;
#include "fireball.qh"
#ifdef SVQC
-spawnfunc(weapon_fireball) { weapon_defaultspawnfunc(this, WEP_FIREBALL); }
void W_Fireball_Explode(entity this, entity directhitentity)
{
#pragma once
CLASS(Fireball, Weapon)
+/* spawnfunc */ ATTRIB(Fireball, m_canonical_spawnfunc, string, "weapon_fireball");
/* ammotype */ //ATTRIB(Fireball, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(Fireball, impulse, int, 9);
/* flags */ ATTRIB(Fireball, spawnflags, int, WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
ENDCLASS(Fireball)
REGISTER_WEAPON(FIREBALL, fireball, NEW(Fireball));
+SPAWNFUNC_WEAPON(weapon_fireball, WEP_FIREBALL)
+
#ifdef SVQC
.float bot_primary_fireballmooth; // whatever a mooth is
.vector fireball_impactvec;
#include "hagar.qh"
#ifdef SVQC
-spawnfunc(weapon_hagar) { weapon_defaultspawnfunc(this, WEP_HAGAR); }
// NO bounce protection, as bounces are limited!
#pragma once
CLASS(Hagar, Weapon)
+/* spawnfunc */ ATTRIB(Hagar, m_canonical_spawnfunc, string, "weapon_hagar");
/* ammotype */ ATTRIB(Hagar, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Hagar, impulse, int, 8);
/* flags */ ATTRIB(Hagar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
ENDCLASS(Hagar)
REGISTER_WEAPON(HAGAR, hagar, NEW(Hagar));
+
+SPAWNFUNC_WEAPON(weapon_hagar, WEP_HAGAR)
#include "hlac.qh"
#ifdef SVQC
-spawnfunc(weapon_hlac) { weapon_defaultspawnfunc(this, WEP_HLAC); }
void W_HLAC_Touch(entity this, entity toucher)
{
#pragma once
CLASS(HLAC, Weapon)
+/* spawnfunc */ ATTRIB(HLAC, m_canonical_spawnfunc, string, "weapon_hlac");
/* ammotype */ ATTRIB(HLAC, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(HLAC, impulse, int, 6);
/* flags */ ATTRIB(HLAC, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
ENDCLASS(HLAC)
REGISTER_WEAPON(HLAC, hlac, NEW(HLAC));
+
+SPAWNFUNC_WEAPON(weapon_hlac, WEP_HLAC)
#ifdef SVQC
-spawnfunc(weapon_hook) { weapon_defaultspawnfunc(this, WEP_HOOK); }
-
void W_Hook_ExplodeThink(entity this)
{
float dt, dmg_remaining_next, f;
{
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;
#pragma once
CLASS(Hook, Weapon)
+/* spawnfunc */ ATTRIB(Hook, m_canonical_spawnfunc, string, "weapon_hook");
/* ammotype */ ATTRIB(Hook, ammo_type, int, RESOURCE_FUEL);
/* impulse */ ATTRIB(Hook, impulse, int, 0);
/* flags */ ATTRIB(Hook, spawnflags, int, WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
ENDCLASS(Hook)
REGISTER_WEAPON(HOOK, hook, NEW(Hook));
+SPAWNFUNC_WEAPON(weapon_hook, WEP_HOOK)
+
CLASS(OffhandHook, OffhandWeapon)
#ifdef SVQC
METHOD(OffhandHook, offhand_think, void(OffhandHook this, entity actor, bool key_pressed))
#ifdef SVQC
-spawnfunc(weapon_machinegun)
+METHOD(MachineGun, m_spawnfunc_hookreplace, Weapon(MachineGun this, entity e))
{
- if(autocvar_sv_q3acompat_machineshotgunswap)
- if(this.classname != "droppedweapon")
+ if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
{
- weapon_defaultspawnfunc(this, WEP_SHOCKWAVE);
- return;
+ return WEP_SHOCKWAVE;
}
- weapon_defaultspawnfunc(this, WEP_MACHINEGUN);
+ return this;
}
-spawnfunc(weapon_uzi) { spawnfunc_weapon_machinegun(this); }
void W_MachineGun_MuzzleFlash_Think(entity this)
{
#pragma once
CLASS(MachineGun, Weapon)
+/* spawnfunc */ ATTRIB(MachineGun, m_canonical_spawnfunc, string, "weapon_machinegun");
/* ammotype */ ATTRIB(MachineGun, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(MachineGun, impulse, int, 3);
/* flags */ ATTRIB(MachineGun, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
ENDCLASS(MachineGun)
REGISTER_WEAPON(MACHINEGUN, machinegun, NEW(MachineGun));
+
+SPAWNFUNC_WEAPON(weapon_machinegun, WEP_MACHINEGUN)
+SPAWNFUNC_WEAPON(weapon_uzi, WEP_MACHINEGUN)
#include "minelayer.qh"
#ifdef SVQC
-spawnfunc(weapon_minelayer) { weapon_defaultspawnfunc(this, WEP_MINE_LAYER); }
void W_MineLayer_Stick(entity this, entity to)
{
#pragma once
CLASS(MineLayer, Weapon)
+/* spawnfunc */ ATTRIB(MineLayer, m_canonical_spawnfunc, string, "weapon_minelayer");
/* ammotype */ ATTRIB(MineLayer, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(MineLayer, impulse, int, 4);
/* flags */ ATTRIB(MineLayer, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
ENDCLASS(MineLayer)
REGISTER_WEAPON(MINE_LAYER, minelayer, NEW(MineLayer));
+SPAWNFUNC_WEAPON(weapon_minelayer, WEP_MINE_LAYER)
+
#ifdef SVQC
void W_MineLayer_Think(entity this);
.float minelayer_detonate, mine_explodeanyway;
#ifdef SVQC
-spawnfunc(weapon_mortar) { weapon_defaultspawnfunc(this, WEP_MORTAR); }
-spawnfunc(weapon_grenadelauncher) { spawnfunc_weapon_mortar(this); }
-
void W_Mortar_Grenade_Explode(entity this, entity directhitentity)
{
if(directhitentity.takedamage == DAMAGE_AIM)
#pragma once
CLASS(Mortar, Weapon)
+/* 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");
ENDCLASS(Mortar)
REGISTER_WEAPON(MORTAR, mortar, NEW(Mortar));
+SPAWNFUNC_WEAPON(weapon_mortar, WEP_MORTAR)
+SPAWNFUNC_WEAPON(weapon_grenadelauncher, WEP_MORTAR)
#ifdef SVQC
.float gl_detonate_later;
#ifdef SVQC
#include <common/triggers/trigger/jumppads.qh>
-spawnfunc(weapon_porto) { weapon_defaultspawnfunc(this, WEP_PORTO); }
-
REGISTER_MUTATOR(porto_ticker, true);
MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) {
FOREACH_CLIENT(IS_PLAYER(it), it.porto_forbidden = max(0, it.porto_forbidden - 1));
{
this.flags = FL_ITEM;
IL_PUSH(g_items, this);
- this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128);
+ this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128, this);
tracetoss(this, this);
if(vdist(trace_endpos - this.realowner.origin, <, 128))
{
#pragma once
CLASS(PortoLaunch, Weapon)
+/* spawnfunc */ ATTRIB(PortoLaunch, m_canonical_spawnfunc, string, "weapon_porto");
/* ammotype */ ATTRIB(PortoLaunch, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(PortoLaunch, impulse, int, 0);
/* flags */ ATTRIB(PortoLaunch, spawnflags, int, WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON | WEP_FLAG_NODUAL);
ENDCLASS(PortoLaunch)
REGISTER_WEAPON(PORTO, porto, NEW(PortoLaunch));
+SPAWNFUNC_WEAPON(weapon_porto, WEP_PORTO)
+
#ifdef SVQC
.entity porto_current;
.vector porto_v_angle; // holds "held" view angles
#include "rifle.qh"
#ifdef SVQC
-spawnfunc(weapon_rifle) { weapon_defaultspawnfunc(this, WEP_RIFLE); }
-spawnfunc(weapon_campingrifle) { spawnfunc_weapon_rifle(this); }
-spawnfunc(weapon_sniperrifle) { spawnfunc_weapon_rifle(this); }
void W_Rifle_FireBullet(Weapon thiswep, .entity weaponentity, float pSpread, float pDamage, float pForce, float pSolidPenetration, float pAmmo, int deathtype, float pTracer, float pShots, Sound pSound, entity actor)
{
#pragma once
CLASS(Rifle, Weapon)
+/* 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");
ENDCLASS(Rifle)
REGISTER_WEAPON(RIFLE, rifle, NEW(Rifle));
+SPAWNFUNC_WEAPON(weapon_rifle, WEP_RIFLE)
+SPAWNFUNC_WEAPON(weapon_campingrifle, WEP_RIFLE)
+SPAWNFUNC_WEAPON(weapon_sniperrifle, WEP_RIFLE)
#ifdef SVQC
.float rifle_accumulator;
#include "seeker.qh"
#ifdef SVQC
-spawnfunc(weapon_seeker) { weapon_defaultspawnfunc(this, WEP_SEEKER); }
// ============================
// Begin: Missile functions, these are general functions to be manipulated by other code
#pragma once
CLASS(Seeker, Weapon)
+/* spawnfunc */ ATTRIB(Seeker, m_canonical_spawnfunc, string, "weapon_seeker");
/* ammotype */ ATTRIB(Seeker, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Seeker, impulse, int, 8);
/* flags */ ATTRIB(Seeker, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
ENDCLASS(Seeker)
REGISTER_WEAPON(SEEKER, seeker, NEW(Seeker));
+SPAWNFUNC_WEAPON(weapon_seeker, WEP_SEEKER)
+
#ifdef SVQC
.entity tag_target, wps_tag_tracker;
.float tag_time;
REGISTER_NET_TEMP(TE_CSQC_SHOCKWAVEPARTICLE)
#ifdef SVQC
-spawnfunc(weapon_shockwave)
+METHOD(Shockwave, m_spawnfunc_hookreplace, Weapon(Shockwave this, entity e))
{
//if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO
- if(autocvar_sv_q3acompat_machineshotgunswap)
- if(this.classname != "droppedweapon")
+ if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
{
- weapon_defaultspawnfunc(this, WEP_MACHINEGUN);
- return;
+ return WEP_MACHINEGUN;
}
- weapon_defaultspawnfunc(this, WEP_SHOCKWAVE);
+ return this;
}
const float MAX_SHOCKWAVE_HITS = 10;
if(autocvar_g_antilag == 0 || noantilag)
lag = 0; // only do hitscan, but no antilag
if(lag)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && it != actor, antilag_takeback(it, CS(it), time - lag));
- IL_EACH(g_monsters, it != actor,
- {
- antilag_takeback(it, it, time - lag);
- });
- }
+ antilag_takeback_all(actor, lag);
while(head)
{
}
if(lag)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && it != actor, antilag_restore(it, CS(it)));
- IL_EACH(g_monsters, it != actor,
- {
- antilag_restore(it, it);
- });
- }
+ antilag_restore_all(actor);
}
METHOD(Shockwave, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
#pragma once
CLASS(Shockwave, Weapon)
+/* spawnfunc */ ATTRIB(Shockwave, m_canonical_spawnfunc, string, "weapon_shockwave");
/* ammotype */ //ATTRIB(Shockwave, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(Shockwave, impulse, int, 2);
/* flags */ ATTRIB(Shockwave, spawnflags, int, WEP_TYPE_HITSCAN | WEP_FLAG_CANCLIMB | WEP_TYPE_MELEE_SEC);
ENDCLASS(Shockwave)
REGISTER_WEAPON(SHOCKWAVE, shockwave, NEW(Shockwave));
+SPAWNFUNC_WEAPON(weapon_shockwave, WEP_SHOCKWAVE)
#ifdef CSQC
void Net_ReadShockwaveParticle();
#include "shotgun.qh"
#ifdef SVQC
-spawnfunc(weapon_shotgun) { weapon_defaultspawnfunc(this, WEP_SHOTGUN); }
void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary)
{
#pragma once
CLASS(Shotgun, Weapon)
+/* spawnfunc */ ATTRIB(Shotgun, m_canonical_spawnfunc, string, "weapon_shotgun");
/* ammotype */ ATTRIB(Shotgun, ammo_type, int, RESOURCE_SHELLS);
/* impulse */ ATTRIB(Shotgun, impulse, int, 2);
/* flags */ ATTRIB(Shotgun, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_TYPE_MELEE_SEC);
ENDCLASS(Shotgun)
REGISTER_WEAPON(SHOTGUN, shotgun, NEW(Shotgun));
+
+SPAWNFUNC_WEAPON(weapon_shotgun, WEP_SHOTGUN)
.float tuba_lastnotes_cnt; // over
.vector tuba_lastnotes[MAX_TUBANOTES];
-spawnfunc(weapon_tuba) { weapon_defaultspawnfunc(this, WEP_TUBA); }
-
bool W_Tuba_HasPlayed(entity pl, .entity weaponentity, string melody, int instrument, bool ignorepitch, float mintempo, float maxtempo)
{
float i, j, mmin, mmax, nolength;
#pragma once
CLASS(Tuba, Weapon)
+/* spawnfunc */ ATTRIB(Tuba, m_canonical_spawnfunc, string, "weapon_tuba");
/* impulse */ ATTRIB(Tuba, impulse, int, 1);
/* flags */ ATTRIB(Tuba, spawnflags, int, WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Tuba, bot_pickupbasevalue, float, 2000);
ENDCLASS(Tuba)
REGISTER_WEAPON(TUBA, tuba, NEW(Tuba));
+SPAWNFUNC_WEAPON(weapon_tuba, WEP_TUBA)
+
#ifdef CSQC
entityclass(Tuba);
class(Tuba) .int note;
#endif
#ifdef SVQC
-spawnfunc(weapon_vaporizer) { weapon_defaultspawnfunc(this, WEP_VAPORIZER); }
-spawnfunc(weapon_minstanex) { spawnfunc_weapon_vaporizer(this); }
void W_RocketMinsta_Explosion(entity actor, vector loc)
{
#pragma once
CLASS(Vaporizer, Weapon)
+/* spawnfunc */ ATTRIB(Vaporizer, m_canonical_spawnfunc, string, "weapon_vaporizer");
/* ammotype */ ATTRIB(Vaporizer, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Vaporizer, impulse, int, 7);
/* flags */ ATTRIB(Vaporizer, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN | WEP_FLAG_NODUAL);
ENDCLASS(Vaporizer)
REGISTER_WEAPON(VAPORIZER, vaporizer, NEW(Vaporizer));
+SPAWNFUNC_WEAPON(weapon_vaporizer, WEP_VAPORIZER)
+SPAWNFUNC_WEAPON(weapon_minstanex, WEP_VAPORIZER)
#ifdef SVQC
.float vaporizer_lasthit;
#endif
#ifdef SVQC
-spawnfunc(weapon_vortex) { weapon_defaultspawnfunc(this, WEP_VORTEX); }
-spawnfunc(weapon_nex) { spawnfunc_weapon_vortex(this); }
REGISTER_MUTATOR(vortex_charge, true);
#pragma once
CLASS(Vortex, Weapon)
+/* spawnfunc */ ATTRIB(Vortex, m_canonical_spawnfunc, string, "weapon_vortex");
/* ammotype */ ATTRIB(Vortex, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Vortex, impulse, int, 7);
/* flags */ ATTRIB(Vortex, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_NODUAL);
ENDCLASS(Vortex)
REGISTER_WEAPON(VORTEX, vortex, NEW(Vortex));
+SPAWNFUNC_WEAPON(weapon_vortex, WEP_VORTEX)
+SPAWNFUNC_WEAPON(weapon_nex, WEP_VORTEX)
#ifdef SVQC
-
.float vortex_lasthit;
#endif
this.com_phys_water = true;
sys_phys_simulate(this, dt);
this.com_phys_water = false;
+ this.jumppadcount = 0;
} else if (time < this.ladder_time) {
this.com_phys_friction = PHYS_FRICTION(this);
this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod;
|| CS(this).impulse == 18
|| (CS(this).impulse >= 200 && CS(this).impulse <= 209)
) {
- this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
+ this.spectatorspeed = bound(autocvar_sv_spectator_speed_multiplier_min, this.spectatorspeed + 0.5, autocvar_sv_spectator_speed_multiplier_max);
} else if (CS(this).impulse == 11) {
this.spectatorspeed = maxspeed_mod;
} else if (CS(this).impulse == 12
|| CS(this).impulse == 19
|| (CS(this).impulse >= 220 && CS(this).impulse <= 229)
) {
- this.spectatorspeed = bound(1, this.spectatorspeed - 0.5, 5);
+ this.spectatorspeed = bound(autocvar_sv_spectator_speed_multiplier_min, this.spectatorspeed - 0.5, autocvar_sv_spectator_speed_multiplier_max);
} else if (CS(this).impulse >= 1 && CS(this).impulse <= 9) {
this.spectatorspeed = 1 + 0.5 * (CS(this).impulse - 1);
}
{
float spd = max(PHYS_MAXSPEED(this), PHYS_MAXAIRSPEED(this)) * maxspeed_mod;
if (this.speed != spd) {
- this.speed = spd;
+ this.speed = spd; // TODO: send this as a stat and set the below cvars on the client?
string temps = ftos(spd);
stuffcmd(this, strcat("cl_forwardspeed ", temps, "\n"));
stuffcmd(this, strcat("cl_backspeed ", temps, "\n"));
FIELD_SCALAR(fld, cvarfilter) \
FIELD_SCALAR(fld, debrisdamageforcescale) \
FIELD_SCALAR(fld, debrisfadetime) \
+ FIELD_SCALAR(fld, debrismovetype) \
+ FIELD_SCALAR(fld, debrisskin) \
FIELD_SCALAR(fld, debristimejitter) \
FIELD_SCALAR(fld, debristime) \
FIELD_SCALAR(fld, debris) \
FIELD_VEC(fld, absmin) \
FIELD_VEC(fld, angles) \
FIELD_VEC(fld, avelocity) \
+ FIELD_VEC(fld, debrisavelocityjitter) \
+ FIELD_VEC(fld, debrisvelocity) \
+ FIELD_VEC(fld, debrisvelocityjitter) \
FIELD_VEC(fld, color) \
FIELD_VEC(fld, mangle) \
FIELD_VEC(fld, maxs) \
vector m1 = e.maxs;
e.mins = '0 0 0';
e.maxs = '0 0 0';
- WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m0.x); e.mins.x = m0.x;
- WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m1.x); e.maxs.x = m1.x;
- WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m0.y); e.mins.y = m0.y;
- WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m1.y); e.maxs.y = m1.y;
- WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m0.z); e.mins.z = m0.z;
- WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m1.z); e.maxs.z = m1.z;
+ WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m0.x); e.mins_x = m0.x;
+ WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m1.x); e.maxs_x = m1.x;
+ WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m0.y); e.mins_y = m0.y;
+ WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m1.y); e.maxs_y = m1.y;
+ WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m0.z); e.mins_z = m0.z;
+ WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m1.z); e.maxs_z = m1.z;
setorigin(e, e.origin);
tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
{
+#ifdef SVQC
+ player.lastteleport_origin = player.origin;
+ player.lastteleporttime = time;
+#endif
setorigin(player, to); // NOTE: this also aborts the move, when this is called by touch
#ifdef SVQC
player.oldorigin = to; // for DP's unsticking
break;
}
me.encryptLabel.setText(me.encryptLabel, me.currentServerEncrypt);
+ setZonedTooltip(me.encryptLabel, _("Use the `crypto_aeslevel` cvar to change your preferences"), string_null);
s = crypto_getidfp(me.currentServerCName);
if (!s) { s = _("N/A"); }
#include <server/impulse.qc>
#include <server/ipban.qc>
#include <server/item_key.qc>
+#include <server/items.qc>
#include <server/mapvoting.qc>
#include <server/matrix.qc>
#include <server/miscfunctions.qc>
#include <server/impulse.qh>
#include <server/ipban.qh>
#include <server/item_key.qh>
+#include <server/items.qh>
#include <server/mapvoting.qh>
#include <server/matrix.qh>
#include <server/miscfunctions.qh>
}
store.antilag_index = ANTILAG_MAX_ORIGINS - 1; // next one is 0
}
+
+// TODO: use a single intrusive list across all antilagged entities
+void antilag_takeback_all(entity ignore, float lag)
+{
+ FOREACH_CLIENT(IS_PLAYER(it) && it != ignore, antilag_takeback(it, CS(it), time - lag));
+ IL_EACH(g_monsters, it != ignore,
+ {
+ antilag_takeback(it, it, time - lag);
+ });
+ IL_EACH(g_projectiles, it != ignore && it.classname == "nade",
+ {
+ antilag_takeback(it, it, time - lag);
+ });
+}
+
+void antilag_restore_all(entity ignore)
+{
+ FOREACH_CLIENT(IS_PLAYER(it) && it != ignore, antilag_restore(it, CS(it)));
+ IL_EACH(g_monsters, it != ignore,
+ {
+ antilag_restore(it, it);
+ });
+ IL_EACH(g_projectiles, it != ignore && it.classname == "nade",
+ {
+ antilag_restore(it, it);
+ });
+}
void antilag_restore(entity e, entity store);
void antilag_clear(entity e, entity store);
+void antilag_takeback_all(entity ignore, float lag);
+void antilag_restore_all(entity ignore);
+
.float antilag_debug;
#define ANTILAG_LATENCY(e) min(0.4, CS(e).ping * 0.001)
string autocvar__campaign_name;
bool autocvar__sv_init;
float autocvar_bot_ai_strategyinterval;
+float autocvar_bot_ai_strategyinterval_movingtarget;
#define autocvar_bot_number cvar("bot_number")
int autocvar_bot_vs_human;
int autocvar_captureleadlimit_override;
float autocvar_sv_jumpspeedcap_max_disable_on_ramps;
string autocvar_sv_jumpspeedcap_min;
float autocvar_sv_jumpvelocity;
+float autocvar_sv_jumpvelocity_crouch;
bool autocvar_sv_logscores_bots;
bool autocvar_sv_logscores_console;
bool autocvar_sv_logscores_file;
bool autocvar_sv_servermodelsonly;
int autocvar_sv_spectate;
float autocvar_sv_spectator_speed_multiplier;
+float autocvar_sv_spectator_speed_multiplier_min = 1;
+float autocvar_sv_spectator_speed_multiplier_max = 5;
bool autocvar_sv_status_privacy;
float autocvar_sv_stepheight;
float autocvar_sv_strengthsound_antispam_refire_threshold;
#include <server/defs.qh>
#include <common/weapons/_all.qh>
+#include <common/physics/player.qh>
const int WAYPOINTFLAG_GENERATED = BIT(23);
const int WAYPOINTFLAG_ITEM = BIT(22);
-const int WAYPOINTFLAG_TELEPORT = BIT(21);
+const int WAYPOINTFLAG_TELEPORT = BIT(21); // teleports, warpzones and jumppads
const int WAYPOINTFLAG_NORELINK = BIT(20);
const int WAYPOINTFLAG_PERSONAL = BIT(19);
const int WAYPOINTFLAG_PROTECTED = BIT(18); // Useless WP detection never kills these.
const int WAYPOINTFLAG_USEFUL = BIT(17); // Useless WP detection temporary flag.
const int WAYPOINTFLAG_DEAD_END = BIT(16); // Useless WP detection temporary flag.
+const int WAYPOINTFLAG_LADDER = BIT(15);
entity kh_worldkeylist;
.entity kh_worldkeynext;
float bot_weapons_mid[Weapons_MAX];
float skill;
+.float bot_tracewalk_time;
.float bot_attack;
.float bot_dodgerating;
.float bot_dodge;
.float bot_moveskill; // moving technique
.float bot_pickup;
.float(entity player, entity item) bot_pickupevalfunc;
-.float bot_strategytime;
.string cleanname;
.float havocbot_role_timeout;
+.void(entity this) havocbot_role;
+.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;
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius);
void havocbot_goalrating_waypoints(entity this, float ratingscale, vector org, float sradius);
+bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, entity item, vector item_org);
+
vector havocbot_middlepoint;
float havocbot_middlepoint_radius;
vector havocbot_symmetryaxis_equation;
+.float goalentity_lock_timeout;
+.float ignoregoaltime;
+.entity ignoregoal;
+
.entity bot_basewaypoint;
.bool navigation_dynamicgoal;
void navigation_dynamicgoal_init(entity this, bool initially_static);
entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
void navigation_goalrating_end(entity this);
void navigation_goalrating_start(entity this);
+void navigation_goalrating_timeout_set(entity this);
+void navigation_goalrating_timeout_force(entity this);
+void navigation_goalrating_timeout_expire(entity this, float seconds);
+bool navigation_goalrating_timeout(entity this);
+bool navigation_goalrating_timeout_can_be_anticipated(entity this);
void navigation_markroutes(entity this, entity fixed_source_waypoint);
void navigation_markroutes_inverted(entity fixed_source_waypoint);
void navigation_routerating(entity this, entity e, float f, float rangebias);
-bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
+vector get_closer_dest(entity ent, vector org);
-void waypoint_remove(entity e);
+void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest);
+vector set_tracewalk_dest_2(entity ent, vector org);
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode);
+
+void waypoint_remove_fromeditor(entity pl);
+void waypoint_remove(entity wp);
void waypoint_saveall();
void waypoint_schedulerelinkall();
void waypoint_schedulerelink(entity wp);
void waypoint_spawnforitem(entity e);
void waypoint_spawnforitem_force(entity e, vector org);
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent);
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent);
+void waypoint_spawn_fromeditor(entity pl);
entity waypoint_spawn(vector m1, vector m2, float f);
-
-.entity goalcurrent;
-void navigation_clearroute(entity this);
+void waypoint_unreachable(entity pl);
if(targ.team==0)
return false;
}
- else if(bot_ignore_bots)
- if(IS_BOT_CLIENT(targ))
- return false;
+ else if (autocvar_bot_ignore_bots && IS_BOT_CLIENT(targ))
+ return false;
if (!targ.takedamage)
return false;
this.bot_canfire = 1;
}
-float bot_aimdir(entity this, vector v, float maxfiredeviation)
+void bot_aimdir(entity this, vector v, float maxfiredeviation)
{
float dist, delta_t, blend;
vector desiredang, diffang;
this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
this.v_angle_z = 0;
+ // invalid aim dir (can happen when bot overlaps target)
+ if(!v) return;
+
// get the desired angles to aim at
//dprint(" at:", vtos(v));
v = normalize(v);
//dprint(ftos(maxfiredeviation),"\n");
//dprint(" diff:", vtos(diffang), "\n");
- return this.bot_canfire && (time < this.bot_firetimer);
+ //return this.bot_canfire && (time < this.bot_firetimer);
}
vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity)
{
- float f, r, hf, distanceratio;
+ float r, hf, distanceratio;
vector v;
/*
eprint(this);
return false;
}
- f = bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', r);
+ bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', r);
}
else
{
- f = bot_aimdir(this, v - shotorg, r);
+ bot_aimdir(this, v - shotorg, r);
//dprint("AIM: ");dprint(vtos(this.bot_aimtargorigin));dprint(" + ");dprint(vtos(this.bot_aimtargvelocity));dprint(" * ");dprint(ftos(this.bot_aimlatency + vlen(this.bot_aimtargorigin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
//traceline(shotorg, shotorg + shotdir * 10000, false, this);
//if (trace_ent.takedamage)
void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
float bot_shouldattack(entity this, entity targ);
-float bot_aimdir(entity this, vector v, float maxfiredeviation);
+void bot_aimdir(entity this, vector v, float maxfiredeviation);
bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity);
float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore);
if (this.deadflag == DEAD_DEAD)
{
PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_force(this);
}
}
else if(this.aistatus & AI_STATUS_STUCK)
else if(this.bot_forced_team==4)
this.team = NUM_TEAM_4;
else
- JoinBestTeam(this, false, true);
+ JoinBestTeam(this, true);
havocbot_setupbot(this);
}
void bot_calculate_stepheightvec()
{
stepheightvec = autocvar_sv_stepheight * '0 0 1';
- jumpstepheightvec = stepheightvec +
- ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85';
- // 0.75 factor is for safety to make the jumps easy
+ jumpheight_vec = (autocvar_sv_jumpvelocity ** 2) / (2 * autocvar_sv_gravity) * '0 0 1';
+ jumpstepheightvec = stepheightvec + jumpheight_vec * 0.85; // reduce it a bit to make the jumps easy
}
float bot_fixcount()
++realplayers;
});
}
- if(currentbots == -1)
- {
- currentbots = 0;
- // human players joining early may cause weird issues (bots appearing on
- // the scoreboard as spectators) when switching map with the gotomap
- // command, as it doesn't remove bots of the previous match, and with
- // minplayers > 1, so ignore human players in the first bot frame
- // TODO maybe find a cleaner solution
- activerealplayers = 0;
- }
int bots;
// add/remove bots if needed to make sure there are at least
void bot_serverframe()
{
+ if (intermission_running && currentbots > 0)
+ {
+ // after the end of the match all bots stay unless all human players disconnect
+ int realplayers = 0;
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++realplayers; });
+ if (!realplayers)
+ {
+ FOREACH_CLIENT(IS_BOT_CLIENT(it), { dropclient(it); });
+ currentbots = 0;
+ }
+ return;
+ }
+
if (game_stopped)
return;
- if (time < 2)
+ // Added 0.5 to avoid possible addition + immediate removal of bots that would make them appear as
+ // spectators in the scoreboard and never go away. This issue happens at time 2 if map is changed
+ // with the gotomap command, minplayers is > 1 and human clients join as players very soon
+ // either intentionally or automatically (sv_spectate 0)
+ if (time < 2.5)
{
currentbots = -1;
return;
}
+ if (currentbots == -1)
+ {
+ // count bots already in the server from the previous match
+ currentbots = 0;
+ FOREACH_CLIENT(IS_BOT_CLIENT(it), { ++currentbots; });
+ }
+
+ if(autocvar_skill != skill)
+ {
+ float wpcost_update = false;
+ if(skill >= autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill < autocvar_bot_ai_bunnyhop_skilloffset)
+ wpcost_update = true;
+ if(skill < autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill >= autocvar_bot_ai_bunnyhop_skilloffset)
+ wpcost_update = true;
+
+ skill = autocvar_skill;
+ if (wpcost_update)
+ waypoint_updatecost_foralllinks();
+ }
+
bot_calculate_stepheightvec();
bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
botframe_nextthink = time + 10;
}
- bot_ignore_bots = autocvar_bot_ignore_bots;
-
if(botframe_spawnedwaypoints)
{
if(autocvar_waypoint_benchmark)
{
botframe_spawnedwaypoints = true;
waypoint_loadall();
- if(!waypoint_load_links())
- waypoint_schedulerelinkall();
+ waypoint_load_links();
}
if (bot_list)
// frame, which causes choppy framerates)
if (bot_strategytoken_taken)
{
+ // give goal token to the first bot without goals; if all bots don't have
+ // any goal (or are dead/frozen) simply give it to the next one
bot_strategytoken_taken = false;
- if (bot_strategytoken)
- bot_strategytoken = bot_strategytoken.nextbot;
- if (!bot_strategytoken)
- bot_strategytoken = bot_list;
+ entity bot_strategytoken_save = bot_strategytoken;
+ while (true)
+ {
+ if (bot_strategytoken)
+ bot_strategytoken = bot_strategytoken.nextbot;
+ if (!bot_strategytoken)
+ bot_strategytoken = bot_list;
+
+ if (!(IS_DEAD(bot_strategytoken) || STAT(FROZEN, bot_strategytoken))
+ && !bot_strategytoken.goalcurrent)
+ break;
+
+ if (!bot_strategytoken_save) // break loop if all the bots are dead or frozen
+ break;
+ if (bot_strategytoken == bot_strategytoken_save)
+ bot_strategytoken_save = NULL; // looped through all the bots
+ }
}
if (botframe_nextdangertime < time)
float botframe_nextthink;
float botframe_nextdangertime;
float bot_cvar_nextthink;
-float bot_ignore_bots; // let bots not attack other bots (only works in non-teamplay)
+
+int _content_type;
+#define IN_LAVA(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME))
+#define IN_LIQUID(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_WATER || _content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME))
+#define SUBMERGED(pos) IN_LIQUID(pos + autocvar_sv_player_viewoffset)
+#define WETFEET(pos) IN_LIQUID(pos + eZ * (m1.z + 1))
/*
* Functions
if(bot_execute_commands(this))
return;
- if (bot_strategytoken == this)
- if (!bot_strategytoken_taken)
+ if (bot_strategytoken == this && !bot_strategytoken_taken)
{
if(this.havocbot_blockhead)
{
this.havocbot_role(this); // little too far down the rabbit hole
}
- // TODO: tracewalk() should take care of this job (better path finding under water)
// if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
if(!(IS_DEAD(this) || STAT(FROZEN, this)))
if(!this.goalcurrent)
}
if(IS_DEAD(this) || STAT(FROZEN, this))
+ {
+ if (this.goalcurrent)
+ navigation_clearroute(this);
return;
+ }
havocbot_chooseenemy(this);
this.aistatus |= AI_STATUS_ROAMING;
this.aistatus &= ~AI_STATUS_ATTACKING;
- vector now,v,next;//,heading;
+ vector now, next;
float aimdistance,skillblend,distanceblend,blend;
- next = now = ( (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5) - (this.origin + this.view_ofs);
+
+ vector v = get_closer_dest(this.goalcurrent, this.origin);
+ if(this.goalcurrent.wpisbox)
+ {
+ // avoid a glitch when bot is teleported but teleport waypoint isn't removed yet
+ if(this.goalstack02 && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT
+ && this.lastteleporttime > 0 && time - this.lastteleporttime < 0.15)
+ v = (this.goalstack02.absmin + this.goalstack02.absmax) * 0.5;
+ // aim to teleport origin if bot is inside teleport waypoint but hasn't touched the real teleport yet
+ else if(boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
+ v = this.goalcurrent.origin;
+ }
+ next = now = v - (this.origin + this.view_ofs);
aimdistance = vlen(now);
- //heading = this.velocity;
+
//dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
if(
this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
float bunnyhopdistance;
vector deviation;
float maxspeed;
- vector gco, gno;
// Don't jump when attacking
if(this.aistatus & AI_STATUS_ATTACKING)
this.bot_timelastseengoal = 0;
}
- gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
+ vector gco = get_closer_dest(this.goalcurrent, this.origin);
bunnyhopdistance = vlen(this.origin - gco);
// Run only to visible goals
if(IS_ONGROUND(this))
- if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
+ if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
if(checkpvs(this.origin + this.view_ofs, this.goalcurrent))
{
this.bot_lastseengoal = this.goalcurrent;
if(fabs(gco.z - this.origin.z) < this.maxs.z - this.mins.z)
if(this.goalstack01 && !wasfreed(this.goalstack01))
{
- gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
+ vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
deviation = vectoangles(gno - this.origin) - vectoangles(gco - this.origin);
while (deviation.y < -180) deviation.y = deviation.y + 360;
while (deviation.y > 180) deviation.y = deviation.y - 360;
#endif
}
-.entity goalcurrent_prev;
-.float goalcurrent_distance;
-.float goalcurrent_distance_time;
+// return true when bot isn't getting closer to the current goal
+bool havocbot_checkgoaldistance(entity this, vector gco)
+{
+ float curr_dist_z = max(20, fabs(this.origin.z - gco.z));
+ float curr_dist_2d = max(20, vlen(vec2(this.origin - gco)));
+ float distance_time = this.goalcurrent_distance_time;
+ if(distance_time < 0)
+ distance_time = -distance_time;
+ if(curr_dist_z >= this.goalcurrent_distance_z && curr_dist_2d >= this.goalcurrent_distance_2d)
+ {
+ if(!distance_time)
+ this.goalcurrent_distance_time = time;
+ else if (time - distance_time > 0.5)
+ return true;
+ }
+ else
+ {
+ // reduce it a little bit so it works even with very small approaches to the goal
+ this.goalcurrent_distance_z = max(20, curr_dist_z - 10);
+ this.goalcurrent_distance_2d = max(20, curr_dist_2d - 10);
+ this.goalcurrent_distance_time = 0;
+ }
+ return false;
+}
+
+entity havocbot_select_an_item_of_group(entity this, int gr)
+{
+ entity selected = NULL;
+ float selected_dist2 = 0;
+ // select farthest item of this group from bot's position
+ IL_EACH(g_items, it.item_group == gr && it.solid,
+ {
+ float dist2 = vlen2(this.origin - it.origin);
+ if (dist2 < 600 ** 2 && dist2 > selected_dist2)
+ {
+ selected = it;
+ selected_dist2 = vlen2(this.origin - selected.origin);
+ }
+ });
+
+ if (!selected)
+ return NULL;
+
+ set_tracewalk_dest(selected, this.origin, false);
+ if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+ {
+ return NULL;
+ }
+
+ return selected;
+}
+
void havocbot_movetogoal(entity this)
{
- vector destorg;
vector diff;
vector dir;
vector flatdir;
- vector m1;
- vector m2;
vector evadeobstacle;
vector evadelava;
float maxspeed;
- vector gco;
//float dist;
vector dodge;
//if (this.goalentity)
CS(this).movement = '0 0 0';
maxspeed = autocvar_sv_maxspeed;
+ PHYS_INPUT_BUTTON_JETPACK(this) = false;
// Jetpack navigation
- if(this.goalcurrent)
if(this.navigation_jetpack_goal)
if(this.goalcurrent==this.navigation_jetpack_goal)
if(this.ammo_fuel)
if(this.aistatus & AI_STATUS_JETPACK_LANDING)
{
// Calculate brake distance in xy
- float db, v, d;
- vector dxy;
-
- dxy = this.origin - ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ); dxy.z = 0;
- d = vlen(dxy);
- v = vlen(this.velocity - this.velocity.z * '0 0 1');
- db = ((v ** 2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
- // dprint("distance ", ftos(ceil(d)), " velocity ", ftos(ceil(v)), " brake at ", ftos(ceil(db)), "\n");
+ float d = vlen(vec2(this.origin - (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5));
+ float v = vlen(vec2(this.velocity));
+ float db = ((v ** 2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
+ //LOG_INFOF("distance %d, velocity %d, brake at %d ", ceil(d), ceil(v), ceil(db));
if(d < db || d < 500)
{
// Brake
- if(fabs(this.velocity.x)>maxspeed*0.3)
+ if(v > maxspeed * 0.3)
{
CS(this).movement_x = dir * v_forward * -maxspeed;
return;
}
// Flying
- PHYS_INPUT_BUTTON_HOOK(this) = true;
+ PHYS_INPUT_BUTTON_JETPACK(this) = true;
if(this.navigation_jetpack_point.z - STAT(PL_MAX, this).z + STAT(PL_MIN, this).z < this.origin.z)
{
CS(this).movement_x = dir * v_forward * maxspeed;
// Handling of jump pads
if(this.jumppadcount)
{
- // If got stuck on the jump pad try to reach the farthest visible waypoint
- // but with some randomness so it can try out different paths
- if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
+ if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ {
+ this.aistatus |= AI_STATUS_OUT_JUMPPAD;
+ navigation_poptouchedgoals(this);
+ return;
+ }
+ else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
{
- if(fabs(this.velocity.z)<50)
+ // If got stuck on the jump pad try to reach the farthest visible waypoint
+ // but with some randomness so it can try out different paths
+ if(!this.goalcurrent)
{
entity newgoal = NULL;
- if (vdist(this.origin - this.goalcurrent.origin, <, 150))
- this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
- else IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
+ IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
{
+ if(it.wpflags & WAYPOINTFLAG_TELEPORT)
+ if(it.origin.z < this.origin.z - 100 && vdist(vec2(it.origin - this.origin), <, 100))
+ continue;
+
traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, this);
if(trace_fraction < 1)
}
}
else
- return;
+ {
+ if (this.goalcurrent.bot_pickup)
+ {
+ entity jumppad_wp = this.goalcurrent_prev;
+ navigation_poptouchedgoals(this);
+ if(!this.goalcurrent && jumppad_wp.wp00)
+ {
+ // head to the jumppad destination once bot reaches the goal item
+ navigation_pushroute(this, jumppad_wp.wp00);
+ }
+ }
+ vector gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
+ if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
+ this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
+ else if(havocbot_checkgoaldistance(this, gco))
+ {
+ navigation_clearroute(this);
+ navigation_goalrating_timeout_force(this);
+ }
+ else
+ return;
+ }
}
else
{
- if(time - this.lastteleporttime > 0.3 && this.velocity.z > 0)
+ if(time - this.lastteleporttime > 0.2 && this.velocity.z > 0)
{
vector velxy = this.velocity; velxy_z = 0;
if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
// If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
- if(skill>6)
- if (!(IS_ONGROUND(this)))
+ if (skill > 6 && !(IS_ONGROUND(this)))
{
+ #define ROCKETJUMP_DAMAGE() WEP_CVAR(devastator, damage) * 0.8 \
+ * ((this.strength_finished > time) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
+ * ((this.invincible_finished > time) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
+
tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this);
if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos ))
if(this.items & IT_JETPACK)
if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos + '0 0 1' ))
{
if(this.velocity.z<0)
- {
- PHYS_INPUT_BUTTON_HOOK(this) = true;
- }
+ PHYS_INPUT_BUTTON_JETPACK(this) = true;
}
else
- PHYS_INPUT_BUTTON_HOOK(this) = true;
+ PHYS_INPUT_BUTTON_JETPACK(this) = true;
// If there is no goal try to move forward
return;
}
- else if(this.health > WEP_CVAR(devastator, damage) * 0.5 * ((this.strength_finished < time) ? autocvar_g_balance_powerup_strength_selfdamage : 1))
+ else if(this.health + this.armorvalue > ROCKETJUMP_DAMAGE())
{
if(this.velocity.z < 0)
{
}
// If we are under water with no goals, swim up
- if(this.waterlevel)
- if(this.goalcurrent==NULL)
+ if(this.waterlevel && !this.goalcurrent)
{
dir = '0 0 0';
if(this.waterlevel>WATERLEVEL_SWIMMING)
bool locked_goal = false;
- if(this.goalentity && wasfreed(this.goalentity))
+ if((this.goalentity && wasfreed(this.goalentity))
+ || (this.goalcurrent == this.goalentity && this.goalentity.tag_entity))
{
navigation_clearroute(this);
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_force(this);
return;
}
+ else if(this.goalentity.tag_entity)
+ {
+ navigation_goalrating_timeout_expire(this, 2);
+ }
else if(this.goalentity.bot_pickup)
{
if(this.goalentity.bot_pickup_respawning)
this.goalentity.bot_pickup_respawning = false;
else if(time < this.goalentity.scheduledrespawntime - 10) // item already taken (by someone else)
{
- this.goalentity.bot_pickup_respawning = false;
- navigation_clearroute(this);
- this.bot_strategytime = 0;
- return;
+ if(checkpvs(this.origin, this.goalentity))
+ {
+ this.goalentity.bot_pickup_respawning = false;
+ navigation_goalrating_timeout_expire(this, random());
+ }
+ locked_goal = true; // wait for item to respawn
}
else if(this.goalentity == this.goalcurrent)
locked_goal = true; // wait for item to respawn
}
- else if(!this.goalentity.solid)
+ else if(!this.goalentity.solid && !boxesoverlap(this.goalentity.absmin, this.goalentity.absmax, this.absmin, this.absmax))
{
- navigation_clearroute(this);
- this.bot_strategytime = 0;
- return;
+ if(checkpvs(this.origin, this.goalentity))
+ {
+ navigation_goalrating_timeout_expire(this, random());
+ }
+ }
+ }
+ if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
+ locked_goal = true;
+
+ navigation_shortenpath(this);
+
+ if (IS_MOVABLE(this.goalcurrent))
+ {
+ if (IS_DEAD(this.goalcurrent))
+ {
+ if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
+ {
+ navigation_goalrating_timeout_force(this);
+ return;
+ }
+ }
+ else if (this.bot_tracewalk_time < time)
+ {
+ set_tracewalk_dest(this.goalcurrent, this.origin, true);
+ if (!(trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)))
+ {
+ navigation_goalrating_timeout_force(this);
+ return;
+ }
+ this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
}
}
if(!locked_goal)
- navigation_poptouchedgoals(this);
+ {
+ // optimize path finding by anticipating goalrating when bot is near a waypoint;
+ // in this case path finding can start directly from a waypoint instead of
+ // looking for all the reachable waypoints up to a certain distance
+ if (navigation_poptouchedgoals(this))
+ {
+ if (this.goalcurrent)
+ {
+ if (IS_MOVABLE(this.goalcurrent) && IS_DEAD(this.goalcurrent))
+ {
+ // remove even if not visible
+ navigation_goalrating_timeout_force(this);
+ return;
+ }
+ else if (navigation_goalrating_timeout_can_be_anticipated(this))
+ navigation_goalrating_timeout_force(this);
+ }
+ else
+ {
+ entity old_goal = this.goalcurrent_prev;
+ if (old_goal.item_group && this.item_group != old_goal.item_group)
+ {
+ // Avoid multiple costly calls of path finding code that selects one of the closest
+ // item of the group by telling the bot to head directly to the farthest item.
+ // Next time we let the bot select a goal as usual which can be another item
+ // of this group (the closest one) and so on
+ this.item_group = old_goal.item_group;
+ entity new_goal = havocbot_select_an_item_of_group(this, old_goal.item_group);
+ if (new_goal)
+ navigation_pushroute(this, new_goal);
+ }
+ }
+ }
+ }
// if ran out of goals try to use an alternative goal or get a new strategy asap
if(this.goalcurrent == NULL)
{
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_force(this);
return;
}
if(autocvar_bot_debug_goalstack)
debuggoalstack(this);
- m1 = this.goalcurrent.origin + this.goalcurrent.mins;
- m2 = this.goalcurrent.origin + this.goalcurrent.maxs;
- destorg = this.origin;
- destorg.x = bound(m1_x, destorg.x, m2_x);
- destorg.y = bound(m1_y, destorg.y, m2_y);
- destorg.z = bound(m1_z, destorg.z, m2_z);
+ bool bunnyhop_forbidden = false;
+ vector destorg = get_closer_dest(this.goalcurrent, this.origin);
+
+ // in case bot ends up inside the teleport waypoint without touching
+ // the teleport itself, head to the teleport origin
+ if(this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z))
+ {
+ bunnyhop_forbidden = true;
+ destorg = this.goalcurrent.origin;
+ if(destorg.z > this.origin.z)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ }
+
diff = destorg - this.origin;
- //dist = vlen(diff);
+
+ if (fabs(diff.x) < 10 && fabs(diff.y) < 10
+ && this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout)
+ {
+ destorg = this.origin;
+ diff.x = 0;
+ diff.y = 0;
+ }
+
dir = normalize(diff);
flatdir = diff;flatdir.z = 0;
flatdir = normalize(flatdir);
- gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
//if (this.bot_dodgevector_time < time)
{
- // this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
- // this.bot_dodgevector_jumpbutton = 1;
+ //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
+ //this.bot_dodgevector_jumpbutton = 1;
evadeobstacle = '0 0 0';
evadelava = '0 0 0';
{
if(this.waterlevel>WATERLEVEL_SWIMMING)
{
- // flatdir_z = 1;
- this.aistatus |= AI_STATUS_OUT_WATER;
+ if(!this.goalcurrent)
+ this.aistatus |= AI_STATUS_OUT_WATER;
+ else if(destorg.z > this.origin.z)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
}
else
{
- if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && gco.z < this.origin.z) &&
+ dir = flatdir;
+ if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && destorg.z < this.origin.z) &&
( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER))
PHYS_INPUT_BUTTON_JUMP(this) = true;
else
PHYS_INPUT_BUTTON_JUMP(this) = false;
}
- dir = normalize(flatdir);
}
else
{
// jump if going toward an obstacle that doesn't look like stairs we
// can walk up directly
- offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : v_forward * 32);
- tracebox(this.origin, this.mins, this.maxs, this.origin + offset, false, this);
+ vector deviation = '0 0 0';
+ if (this.velocity)
+ {
+ deviation = vectoangles(diff) - vectoangles(this.velocity);
+ while (deviation.y < -180) deviation.y += 360;
+ while (deviation.y > 180) deviation.y -= 360;
+ }
+ vector flat_diff = vec2(diff);
+ offset = max(32, vlen(vec2(this.velocity)) * cos(deviation.y * DEG2RAD) * 0.2) * flatdir;
+ vector actual_destorg = this.origin + offset;
+ if (!this.goalstack01 || this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ {
+ if (vlen2(flat_diff) < vlen2(offset))
+ {
+ actual_destorg.x = destorg.x;
+ actual_destorg.y = destorg.y;
+ }
+ }
+ else if (vdist(flat_diff, <, 32) && diff.z < -16) // destination is under the bot
+ {
+ actual_destorg.x = destorg.x;
+ actual_destorg.y = destorg.y;
+ }
+ else if (vlen2(flat_diff) < vlen2(offset))
+ {
+ vector next_goal_org = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
+ vector next_dir = normalize(vec2(next_goal_org - destorg));
+ float next_dist = vlen(vec2(this.origin + offset - destorg));
+ actual_destorg = vec2(destorg) + next_dist * next_dir;
+ actual_destorg.z = this.origin.z;
+ }
+
+ tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
if (trace_fraction < 1)
if (trace_plane_normal.z < 0.7)
{
s = trace_fraction;
- tracebox(this.origin + stepheightvec, this.mins, this.maxs, this.origin + offset + stepheightvec, false, this);
+ tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
if (trace_fraction < s + 0.01)
if (trace_plane_normal.z < 0.7)
{
s = trace_fraction;
- tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, this.origin + offset + jumpstepheightvec, false, this);
+ tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, actual_destorg + jumpstepheightvec, false, this);
if (trace_fraction > s)
PHYS_INPUT_BUTTON_JUMP(this) = true;
}
}
// if bot for some reason doesn't get close to the current goal find another one
- if(!IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50))
+ if(!this.jumppadcount && !IS_PLAYER(this.goalcurrent))
+ if(!(locked_goal && this.goalcurrent_distance_z < 50 && this.goalcurrent_distance_2d < 50))
+ if(havocbot_checkgoaldistance(this, destorg))
{
- float curr_dist = vlen(this.origin - this.goalcurrent.origin);
- if(this.goalcurrent != this.goalcurrent_prev)
- {
- this.goalcurrent_prev = this.goalcurrent;
- this.goalcurrent_distance = curr_dist;
- this.goalcurrent_distance_time = 0;
- }
- else if(curr_dist > this.goalcurrent_distance)
+ if(this.goalcurrent_distance_time < 0) // can't get close for the second time
{
- if(!this.goalcurrent_distance_time)
- this.goalcurrent_distance_time = time;
- else if (time - this.goalcurrent_distance_time > 0.5)
- {
- this.goalcurrent_prev = NULL;
- navigation_clearroute(this);
- this.bot_strategytime = 0;
- return;
- }
+ navigation_clearroute(this);
+ navigation_goalrating_timeout_force(this);
+ return;
}
- else
+
+ set_tracewalk_dest(this.goalcurrent, this.origin, false);
+ if (!tracewalk(this, this.origin, this.mins, this.maxs,
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
{
- // reduce it a little bit so it works even with very small approaches to the goal
- this.goalcurrent_distance = max(20, curr_dist - 15);
- this.goalcurrent_distance_time = 0;
+ navigation_clearroute(this);
+ navigation_goalrating_timeout_force(this);
+ return;
}
+
+ // give bot only another chance to prevent bot getting stuck
+ // in case it thinks it can walk but actually can't
+ this.goalcurrent_distance_z = FLOAT_MAX;
+ this.goalcurrent_distance_2d = FLOAT_MAX;
+ this.goalcurrent_distance_time = -time; // mark second try
}
// Check for water/slime/lava and dangerous edges
// (only when the bot is on the ground or jumping intentionally)
+ offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : v_forward * 32);
vector dst_ahead = this.origin + this.view_ofs + offset;
vector dst_down = dst_ahead - '0 0 3000';
traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
bool unreachable = false;
- bool ignorehazards = false;
s = CONTENT_SOLID;
if(trace_fraction == 1 && this.jumppadcount == 0 && !this.goalcurrent.wphardwired )
if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
s = pointcontents(trace_endpos + '0 0 1');
if (s != CONTENT_SOLID)
if (s == CONTENT_LAVA || s == CONTENT_SLIME)
- {
evadelava = normalize(this.velocity) * -1;
- if(this.waterlevel >= WATERLEVEL_WETFEET && (this.watertype == CONTENT_LAVA || this.watertype == CONTENT_SLIME))
- ignorehazards = true;
- }
- else if (s == CONTENT_WATER)
- {
- if(this.waterlevel >= WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER)
- ignorehazards = true;
- }
else if (s == CONTENT_SKY)
evadeobstacle = normalize(this.velocity) * -1;
else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
{
- if (gco.z > this.origin.z + jumpstepheightvec.z)
+ if (destorg.z > this.origin.z + jumpstepheightvec.z)
{
// the goal is probably on an upper platform, assume bot can't get there
unreachable = true;
if(evadeobstacle || evadelava || (s == CONTENT_WATER))
{
- if(!ignorehazards)
- this.aistatus |= AI_STATUS_DANGER_AHEAD;
+ this.aistatus |= AI_STATUS_DANGER_AHEAD;
if(IS_PLAYER(this.goalcurrent))
unreachable = true;
}
if(unreachable)
{
navigation_clearroute(this);
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_force(this);
+ this.ignoregoal = this.goalcurrent;
+ this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
}
}
havocbot_keyboard_movement(this, destorg);
// Bunnyhop!
-// if(this.aistatus & AI_STATUS_ROAMING)
- if(this.goalcurrent)
+ //if(this.aistatus & AI_STATUS_ROAMING)
+ if(!bunnyhop_forbidden && this.goalcurrent)
if(skill+this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
havocbot_bunnyhop(this, dir);
debuggoalstack(this);
// Heading
- vector dir = ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ) - (this.origin + this.view_ofs);
+ vector dir = get_closer_dest(this.goalcurrent, this.origin);
+ dir = dir - (this.origin + this.view_ofs);
dir.z = 0;
bot_aimdir(this, dir, -1);
.float havocbot_stickenemy;
.float havocbot_role_timeout;
+.float bot_tracewalk_time;
.entity ignoregoal;
.entity bot_lastseengoal;
.entity havocbot_personal_waypoint;
#include <server/defs.qh>
#include <server/miscfunctions.qh>
+#include <server/items.qh>
#include "havocbot.qh"
#include "../cvars.qh"
#include "../bot.qh"
#include "../navigation.qh"
+.float bot_ratingscale;
+.float bot_ratingscale_time;
.float max_armorvalue;
.float havocbot_role_timeout;
}
};
+bool havocbot_goalrating_item_can_be_left_to_teammate(entity this, entity player, entity item)
+{
+ if (item.health && player.health <= this.health) {return true;}
+ if (item.armorvalue && player.armorvalue <= this.armorvalue) {return true;}
+ if (item.weapons && !(player.weapons & item.weapons)) {return true;}
+ if (item.ammo_shells && player.ammo_shells <= this.ammo_shells) {return true;}
+ if (item.ammo_nails && player.ammo_nails <= this.ammo_nails) {return true;}
+ if (item.ammo_rockets && player.ammo_rockets <= this.ammo_rockets) {return true;}
+ if (item.ammo_cells && player.ammo_cells <= this.ammo_cells) {return true;}
+ if (item.ammo_plasma && player.ammo_plasma <= this.ammo_plasma) {return true;}
+ if (item.itemdef.instanceOfPowerup) {return true;}
+
+ return false;
+};
+
+bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, entity item, vector item_org)
+{
+ if(!teamplay)
+ return true;
+
+ // actually these variables hold the squared distances in order to optimize code
+ float friend_distance = FLOAT_MAX;
+ float enemy_distance = FLOAT_MAX;
+ float dist;
+
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
+ {
+ if (it.team == this.team)
+ {
+ if (!IS_REAL_CLIENT(it))
+ continue;
+
+ dist = vlen2(it.origin - item_org);
+ if(dist > friend_distance)
+ continue;
+
+ if(havocbot_goalrating_item_can_be_left_to_teammate(this, it, item))
+ {
+ friend_distance = dist;
+ continue;
+ }
+ }
+ else
+ {
+ // If enemy only track distances
+ // TODO: track only if visible ?
+ dist = vlen2(it.origin - item_org);
+ if(dist < enemy_distance)
+ enemy_distance = dist;
+ }
+ });
+
+ // Rate the item only if no one needs it, or if an enemy is closer to it
+ dist = vlen2(item_org - org);
+ if ((enemy_distance < friend_distance && dist < enemy_distance) ||
+ (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ** 2) ||
+ (dist < friend_distance && dist < 200 ** 2))
+ return true;
+ return false;
+};
+
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
{
- float rating, discard, friend_distance, enemy_distance;
+ float rating;
vector o;
ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
IL_EACH(g_items, it.bot_pickup,
{
- rating = 0;
+ // ignore if bot already rated this item with a higher ratingscale
+ // NOTE: this code assumes each bot rates items in a different frame
+ if(it.bot_ratingscale_time == time && ratingscale < it.bot_ratingscale)
+ continue;
+ it.bot_ratingscale_time = time;
+ it.bot_ratingscale = ratingscale;
if(!it.solid)
{
continue;
// Check if the item can be picked up safely
- if(it.classname == "droppedweapon")
+ if(Item_IsLoot(it))
{
if(!IS_ONGROUND(it))
continue;
traceline(o, o + '0 0 -1500', true, NULL);
- if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(trace_endpos + '0 0 1')) & DPCONTENTS_LIQUIDSMASK)
+ if(IN_LAVA(trace_endpos + '0 0 1'))
continue;
+
// this tracebox_hits_trigger_hurt call isn't needed:
// dropped weapons are removed as soon as they fall on a trigger_hurt
// and can't be rated while they are in the air
}
else
{
- // Ignore items under water
- // TODO: can't .waterlevel be used here?
- if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(it.origin + ((it.mins + it.maxs) * 0.5))) & DPCONTENTS_LIQUIDSMASK)
+ if(IN_LAVA(it.origin + (it.mins + it.maxs) * 0.5))
continue;
}
- if(teamplay)
- {
- friend_distance = 10000; enemy_distance = 10000;
- discard = false;
-
- entity picker = it;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
- {
- if ( it.team == this.team )
- {
- if ( !IS_REAL_CLIENT(it) || discard )
- continue;
-
- if( vdist(it.origin - o, >, friend_distance) )
- continue;
-
- friend_distance = vlen(it.origin - o); // distance between player and item
- discard = true;
-
- if (picker.health && it.health > this.health) continue;
- if (picker.armorvalue && it.armorvalue > this.armorvalue) continue;
-
- if (picker.weapons && (picker.weapons & ~it.weapons)) continue;
-
- if (picker.ammo_shells && it.ammo_shells > this.ammo_shells) continue;
- if (picker.ammo_nails && it.ammo_nails > this.ammo_nails) continue;
- if (picker.ammo_rockets && it.ammo_rockets > this.ammo_rockets) continue;
- if (picker.ammo_cells && it.ammo_cells > this.ammo_cells) continue;
- if (picker.ammo_plasma && it.ammo_plasma > this.ammo_plasma) continue;
-
- discard = false;
- }
- else
- {
- // If enemy only track distances
- // TODO: track only if visible ?
- if( vdist(it.origin - o, <, enemy_distance) )
- enemy_distance = vlen(it.origin - o); // distance between player and item
- }
- });
-
- // Rate the item only if no one needs it, or if an enemy is closer to it
- if ( (enemy_distance < friend_distance && vdist(o - org, <, enemy_distance)) ||
- (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ) || !discard )
- rating = it.bot_pickupevalfunc(this, it);
- }
- else
- rating = it.bot_pickupevalfunc(this, it);
+ if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
+ continue;
+ rating = it.bot_pickupevalfunc(this, it);
if(rating > 0)
navigation_routerating(this, it, rating * ratingscale, 2000);
});
// TODO: Merge this logic with the bot_shouldattack function
if(vdist(it.origin - org, <, 100) || vdist(it.origin - org, >, sradius))
continue;
+ if(vdist(vec2(it.velocity), >, autocvar_sv_maxspeed * 2))
+ continue;
// rate only visible enemies
/*
{
if (time < this.strength_finished - 1) t += 0.5;
if (time < it.strength_finished - 1) t -= 0.5;
+ if (time < this.invincible_finished - 1) t += 0.2;
+ if (time < it.invincible_finished - 1) t -= 0.4;
}
t += max(0, 8 - skill) * 0.05; // less skilled bots attack more mindlessly
ratingscale *= t;
if(IS_DEAD(this))
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
- if(IS_PLAYER(this.goalentity))
- this.bot_strategytime = time + min(2, autocvar_bot_ai_strategyinterval);
+ navigation_goalrating_timeout_set(this);
}
}
void havocbot_chooserole(entity this)
{
LOG_TRACE("choosing a role...");
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_force(this);
if(!MUTATOR_CALLHOOK(HavocBot_ChooseRole, this))
havocbot_chooserole_generic(this);
}
.float speed;
+void navigation_goalrating_timeout_set(entity this)
+{
+ if(IS_MOVABLE(this.goalentity))
+ this.bot_strategytime = time + autocvar_bot_ai_strategyinterval_movingtarget;
+ else
+ this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+}
+
+// use this when current goal must be discarded immediately
+void navigation_goalrating_timeout_force(entity this)
+{
+ navigation_goalrating_timeout_expire(this, 0);
+}
+
+// use this when current goal can be kept for a short while to increase the chance
+// of bot touching a waypoint, which helps to find a new goal more efficiently
+void navigation_goalrating_timeout_expire(entity this, float seconds)
+{
+ if (seconds <= 0)
+ this.bot_strategytime = 0;
+ else if (this.bot_strategytime > time + seconds)
+ this.bot_strategytime = time + seconds;
+}
+
+bool navigation_goalrating_timeout(entity this)
+{
+ return this.bot_strategytime < time;
+}
+
+#define MAX_CHASE_DISTANCE 700
+bool navigation_goalrating_timeout_can_be_anticipated(entity this)
+{
+ if(time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
+ return true;
+
+ if (this.goalentity.bot_pickup && time > this.bot_strategytime - 5)
+ {
+ vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
+ if(!havocbot_goalrating_item_pickable_check_players(this, this.origin, this.goalentity, gco))
+ {
+ this.ignoregoal = this.goalentity;
+ this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
+ return true;
+ }
+ }
+ return false;
+}
+
void navigation_dynamicgoal_init(entity this, bool initially_static)
{
this.navigation_dynamicgoal = true;
void navigation_dynamicgoal_set(entity this)
{
this.nearestwaypointtimeout = time;
+ if (this.nearestwaypoint)
+ this.nearestwaypointtimeout += 2;
}
void navigation_dynamicgoal_unset(entity this)
this.nearestwaypointtimeout = -1;
}
-// rough simulation of walking from one point to another to test if a path
-// can be traveled, used for waypoint linking and havocbot
+// returns point of ent closer to org
+vector get_closer_dest(entity ent, vector org)
+{
+ vector dest = '0 0 0';
+ if ((ent.classname != "waypoint") || ent.wpisbox)
+ {
+ vector wm1 = ent.origin + ent.mins;
+ vector wm2 = ent.origin + ent.maxs;
+ dest.x = bound(wm1.x, org.x, wm2.x);
+ dest.y = bound(wm1.y, org.y, wm2.y);
+ dest.z = bound(wm1.z, org.z, wm2.z);
+ }
+ else
+ dest = ent.origin;
+ return dest;
+}
-bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode)
+void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest)
{
- vector org;
- vector move;
- vector dir;
- float dist;
- float totaldist;
- float stepdist;
- float ignorehazards;
- float swimming;
- entity tw_ladder = NULL;
+ if ((ent.classname != "waypoint") || ent.wpisbox)
+ {
+ vector wm1 = ent.origin + ent.mins;
+ vector wm2 = ent.origin + ent.maxs;
+ if (IS_PLAYER(ent) || IS_MONSTER(ent))
+ {
+ // move destination point out of player bbox otherwise tracebox always fails
+ // (if bot_navigation_ignoreplayers is false)
+ wm1 += vec2(PL_MIN_CONST) + '-1 -1 0';
+ wm2 += vec2(PL_MAX_CONST) + '1 1 0';
+ }
+ // set destination point to x and y coords of ent that are closer to org
+ // z coord is set to ent's min height
+ tracewalk_dest.x = bound(wm1.x, org.x, wm2.x);
+ tracewalk_dest.y = bound(wm1.y, org.y, wm2.y);
+ tracewalk_dest.z = wm1.z;
+ tracewalk_dest_height = wm2.z - wm1.z; // destination height
+ }
+ else
+ {
+ tracewalk_dest = ent.origin;
+ tracewalk_dest_height = 0;
+ }
+ if (fix_player_dest && IS_PLAYER(ent) && !IS_ONGROUND(ent))
+ {
+ // snap player to the ground
+ if (org.x == tracewalk_dest.x && org.y == tracewalk_dest.y)
+ {
+ // bot is right under the player
+ tracebox(ent.origin, ent.mins, ent.maxs, ent.origin - '0 0 700', MOVE_NORMAL, ent);
+ tracewalk_dest = trace_endpos;
+ tracewalk_dest_height = 0;
+ }
+ else
+ {
+ tracebox(tracewalk_dest, ent.mins, ent.maxs, tracewalk_dest - '0 0 700', MOVE_NORMAL, ent);
+ if (!trace_startsolid && tracewalk_dest.z - trace_endpos.z > 0)
+ {
+ tracewalk_dest_height = tracewalk_dest.z - trace_endpos.z;
+ tracewalk_dest.z = trace_endpos.z;
+ }
+ }
+ }
+}
+
+// returns point of ent closer to org
+vector set_tracewalk_dest_2(entity ent, vector org)
+{
+ vector closer_dest = '0 0 0';
+ if ((ent.classname != "waypoint") || ent.wpisbox)
+ {
+ vector wm1 = ent.origin + ent.mins;
+ vector wm2 = ent.origin + ent.maxs;
+ closer_dest.x = bound(wm1.x, org.x, wm2.x);
+ closer_dest.y = bound(wm1.y, org.y, wm2.y);
+ closer_dest.z = bound(wm1.z, org.z, wm2.z);
+ // set destination point to x and y coords of ent that are closer to org
+ // z coord is set to ent's min height
+ tracewalk_dest.x = closer_dest.x;
+ tracewalk_dest.y = closer_dest.y;
+ tracewalk_dest.z = wm1.z;
+ tracewalk_dest_height = wm2.z - wm1.z; // destination height
+ }
+ else
+ {
+ closer_dest = ent.origin;
+ tracewalk_dest = closer_dest;
+ tracewalk_dest_height = 0;
+ }
+ return closer_dest;
+}
+
+bool navigation_check_submerged_state(entity ent, vector pos)
+{
+ bool submerged;
+ if(IS_PLAYER(ent))
+ submerged = (ent.waterlevel == WATERLEVEL_SUBMERGED);
+ else if(ent.nav_submerged_state != SUBMERGED_UNDEFINED)
+ submerged = (ent.nav_submerged_state == SUBMERGED_YES);
+ else
+ {
+ submerged = SUBMERGED(pos);
+ // NOTE: SUBMERGED check of box waypoint origin may fail even if origin
+ // is actually submerged because often they are inside some solid.
+ // That's why submerged state is saved now that we know current pos is
+ // not stuck in solid (previous tracewalk call to this pos was successfully)
+ if(!ent.navigation_dynamicgoal)
+ ent.nav_submerged_state = (submerged) ? SUBMERGED_YES : SUBMERGED_NO;
+ }
+ return submerged;
+}
+
+bool navigation_checkladders(entity e, vector org, vector m1, vector m2, vector end, vector end2, int movemode)
+{
+ IL_EACH(g_ladders, it.classname == "func_ladder",
+ {
+ if(it.bot_pickup)
+ if(boxesoverlap(org + m1 + '-1 -1 -1', org + m2 + '1 1 1', it.absmin, it.absmax))
+ if(boxesoverlap(end, end2, it.absmin + vec2(m1) + '-1 -1 0', it.absmax + vec2(m2) + '1 1 0'))
+ {
+ vector top = org;
+ top.z = it.absmax.z + (PL_MAX_CONST.z - PL_MIN_CONST.z);
+ tracebox(org, m1, m2, top, movemode, e);
+ if(trace_fraction == 1)
+ return true;
+ }
+ });
+ return false;
+}
+
+vector resurface_limited(vector org, float lim, vector m1)
+{
+ if (WETFEET(org + eZ * (lim - org.z)))
+ org.z = lim;
+ else
+ {
+ float RES_min_h = org.z;
+ float RES_max_h = lim;
+ do {
+ org.z = 0.5 * (RES_min_h + RES_max_h);
+ if(WETFEET(org))
+ RES_min_h = org.z;
+ else
+ RES_max_h = org.z;
+ } while (RES_max_h - RES_min_h >= 1);
+ org.z = RES_min_h;
+ }
+ return org;
+}
+#define RESURFACE_LIMITED(org, lim) org = resurface_limited(org, lim, m1)
+
+#define NAV_WALK 0
+#define NAV_SWIM_ONWATER 1
+#define NAV_SWIM_UNDERWATER 2
+// rough simulation of walking from one point to another to test if a path
+// can be traveled, used for waypoint linking and havocbot
+// if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ]
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
+{
if(autocvar_bot_debug_tracewalk)
{
debugresetnodes();
debugnode(e, start);
}
- move = end - start;
- move.z = 0;
- org = start;
- dist = totaldist = vlen(move);
- dir = normalize(move);
- stepdist = 32;
- ignorehazards = false;
- swimming = false;
+ vector org = start;
+ vector flatdir = end - start;
+ flatdir.z = 0;
+ float flatdist = vlen(flatdir);
+ flatdir = normalize(flatdir);
+ float stepdist = 32;
+ bool ignorehazards = false;
+ int nav_action;
// Analyze starting point
traceline(start, start, MOVE_NORMAL, e);
if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
ignorehazards = true;
- else
- {
- traceline( start, start + '0 0 -65536', MOVE_NORMAL, e);
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
- {
- ignorehazards = true;
- swimming = true;
- }
- }
+
tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
if (trace_startsolid)
{
return false;
}
+ vector end2 = end;
+ if(end_height)
+ end2.z += end_height;
+
+ vector fixed_end = end;
+ vector move;
+
+ if (flatdist > 0 && WETFEET(org))
+ {
+ if (SUBMERGED(org))
+ nav_action = NAV_SWIM_UNDERWATER;
+ else
+ {
+ // tracebox down by player's height
+ // useful to know if water level is so low that bot can still walk
+ tracebox(org, m1, m2, org - eZ * (m2.z - m1.z), movemode, e);
+ if (SUBMERGED(trace_endpos))
+ {
+ org = trace_endpos;
+ nav_action = NAV_SWIM_UNDERWATER;
+ }
+ else
+ nav_action = NAV_WALK;
+ }
+ }
+ else
+ nav_action = NAV_WALK;
+
// Movement loop
- move = end - org;
- for (;;)
+ while (true)
{
- if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1'))
+ if (flatdist <= 0)
{
- // Succeeded
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_SUCCESS);
+ bool success = true;
+ if (org.z > end2.z + 1)
+ {
+ tracebox(org, m1, m2, end2, movemode, e);
+ org = trace_endpos;
+ if (org.z > end2.z + 1)
+ success = false;
+ }
+ else if (org.z < end.z - 1)
+ {
+ tracebox(org, m1, m2, org - jumpheight_vec, movemode, e);
+ if (SUBMERGED(trace_endpos))
+ {
+ vector v = trace_endpos;
+ tracebox(v, m1, m2, end, movemode, e);
+ if(trace_endpos.z >= end.z - 1)
+ {
+ RESURFACE_LIMITED(v, trace_endpos.z);
+ trace_endpos = v;
+ }
+ }
+ else if (trace_endpos.z > org.z - jumpheight_vec.z)
+ tracebox(trace_endpos, m1, m2, trace_endpos + jumpheight_vec, movemode, e);
+ org = trace_endpos;
+ if (org.z < end.z - 1)
+ success = false;
+ }
- //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
- return true;
+ if (success)
+ {
+ // Succeeded
+ if(autocvar_bot_debug_tracewalk)
+ {
+ debugnode(e, org);
+ debugnodestatus(org, DEBUG_NODE_SUCCESS);
+ }
+
+ //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+ return true;
+ }
}
+
if(autocvar_bot_debug_tracewalk)
debugnode(e, org);
- if (dist <= 0)
+ if (flatdist <= 0)
break;
- if (stepdist > dist)
- stepdist = dist;
- dist = dist - stepdist;
- traceline(org, org, MOVE_NORMAL, e);
- if (!ignorehazards)
- {
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
- {
- // hazards blocking path
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_FAIL);
- //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
- return false;
+ if (stepdist > flatdist)
+ stepdist = flatdist;
+ if(nav_action == NAV_SWIM_UNDERWATER || (nav_action == NAV_SWIM_ONWATER && org.z > end2.z))
+ {
+ // can't use movement direction here to calculate move because of
+ // precision errors especially when direction has a high enough z value
+ //water_dir = normalize(water_end - org);
+ //move = org + water_dir * stepdist;
+ fixed_end.z = bound(end.z, org.z, end2.z);
+ if (stepdist == flatdist) {
+ move = fixed_end;
+ flatdist = 0;
+ } else {
+ move = org + (fixed_end - org) * (stepdist / flatdist);
+ flatdist = vlen(vec2(fixed_end - move));
}
}
- if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
+ else // horiz. direction
{
- move = normalize(end - org);
- tracebox(org, m1, m2, org + move * stepdist, movemode, e);
+ flatdist -= stepdist;
+ move = org + flatdir * stepdist;
+ }
- if(autocvar_bot_debug_tracewalk)
- debugnode(e, trace_endpos);
+ if(nav_action == NAV_SWIM_ONWATER)
+ {
+ tracebox(org, m1, m2, move, movemode, e); // swim
+ // hit something
if (trace_fraction < 1)
{
- swimming = true;
- org = trace_endpos + normalize(org - trace_endpos) * stepdist;
- for (; org.z < end.z + e.maxs.z; org.z += stepdist)
+ // stepswim
+ tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
+
+ if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water
{
- if(autocvar_bot_debug_tracewalk)
- debugnode(e, org);
+ org = trace_endpos;
+ if(navigation_checkladders(e, org, m1, m2, end, end2, movemode))
+ {
+ if(autocvar_bot_debug_tracewalk)
+ {
+ debugnode(e, org);
+ debugnodestatus(org, DEBUG_NODE_SUCCESS);
+ }
- if(pointcontents(org) == CONTENT_EMPTY)
- break;
- }
+ //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+ return true;
+ }
- if(pointcontents(org + '0 0 1') != CONTENT_EMPTY)
- {
if(autocvar_bot_debug_tracewalk)
debugnodestatus(org, DEBUG_NODE_FAIL);
return false;
- //print("tracewalk: ", vtos(start), " failed under water\n");
+ //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+ }
+
+ //succesful stepswim
+
+ if (flatdist <= 0)
+ {
+ org = trace_endpos;
+ continue;
}
- continue;
+ if (org.z <= move.z) // going horiz.
+ {
+ tracebox(trace_endpos, m1, m2, move, movemode, e);
+ org = trace_endpos;
+ nav_action = NAV_WALK;
+ continue;
+ }
}
- else
+
+ if (org.z <= move.z) // going horiz.
+ {
org = trace_endpos;
+ nav_action = NAV_SWIM_ONWATER;
+ }
+ else // going down
+ {
+ org = trace_endpos;
+ if (SUBMERGED(org))
+ nav_action = NAV_SWIM_UNDERWATER;
+ else
+ nav_action = NAV_SWIM_ONWATER;
+ }
}
- else
+ else if(nav_action == NAV_SWIM_UNDERWATER)
+ {
+ if (move.z >= org.z) // swimming upwards or horiz.
+ {
+ tracebox(org, m1, m2, move, movemode, e); // swim
+
+ bool stepswum = false;
+
+ // hit something
+ if (trace_fraction < 1)
+ {
+ // stepswim
+ vector stepswim_move = move + stepheightvec;
+ if (flatdist > 0 && stepswim_move.z > end2.z + stepheightvec.z) // don't allow stepswim to go higher than destination
+ stepswim_move.z = end2.z;
+
+ tracebox(org + stepheightvec, m1, m2, stepswim_move, movemode, e);
+
+ // hit something
+ if (trace_startsolid)
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+
+ //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+ return false;
+ }
+
+ if (trace_fraction < 1)
+ {
+ float org_z_prev = org.z;
+ RESURFACE_LIMITED(org, end2.z);
+ if(org.z == org_z_prev)
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+
+ //print("tracewalk: ", vtos(start), " can't reach ", vtos(end), "\n");
+ return false;
+ }
+ if(SUBMERGED(org))
+ nav_action = NAV_SWIM_UNDERWATER;
+ else
+ nav_action = NAV_SWIM_ONWATER;
+
+ // we didn't advance horiz. in this step, flatdist decrease should be reverted
+ // but we can't do it properly right now... apply this workaround instead
+ if (flatdist <= 0)
+ flatdist = 1;
+
+ continue;
+ }
+
+ //succesful stepswim
+
+ if (flatdist <= 0)
+ {
+ org = trace_endpos;
+ continue;
+ }
+
+ stepswum = true;
+ }
+
+ if (!WETFEET(trace_endpos))
+ {
+ tracebox(trace_endpos, m1, m2, trace_endpos - eZ * (stepdist + (m2.z - m1.z)), movemode, e);
+ // if stepswum we'll land on the obstacle, avoid the SUBMERGED check
+ if (!stepswum && SUBMERGED(trace_endpos))
+ {
+ RESURFACE_LIMITED(trace_endpos, end2.z);
+ org = trace_endpos;
+ nav_action = NAV_SWIM_ONWATER;
+ continue;
+ }
+
+ // not submerged
+ org = trace_endpos;
+ nav_action = NAV_WALK;
+ continue;
+ }
+
+ // wetfeet
+ org = trace_endpos;
+ nav_action = NAV_SWIM_UNDERWATER;
+ continue;
+ }
+ else //if (move.z < org.z) // swimming downwards
+ {
+ tracebox(org, m1, m2, move, movemode, e); // swim
+
+ // hit something
+ if (trace_fraction < 1)
+ {
+ // stepswim
+ tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
+
+ // hit something
+ if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(move, DEBUG_NODE_FAIL);
+
+ //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+ return false;
+ }
+
+ //succesful stepswim
+
+ if (flatdist <= 0)
+ {
+ org = trace_endpos;
+ continue;
+ }
+
+ if (trace_endpos.z > org.z && !SUBMERGED(trace_endpos))
+ {
+ // stepswim caused upwards direction
+ tracebox(trace_endpos, m1, m2, trace_endpos - stepheightvec, movemode, e);
+ if (!SUBMERGED(trace_endpos))
+ {
+ org = trace_endpos;
+ nav_action = NAV_WALK;
+ continue;
+ }
+ }
+ }
+
+ org = trace_endpos;
+ nav_action = NAV_SWIM_UNDERWATER;
+ continue;
+ }
+ }
+ else if(nav_action == NAV_WALK)
{
- move = dir * stepdist + org;
+ // walk
tracebox(org, m1, m2, move, movemode, e);
if(autocvar_bot_debug_tracewalk)
tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
if (trace_fraction < 1 || trace_startsolid)
{
- tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
- if (trace_fraction < 1 || trace_startsolid)
+ if (trace_startsolid) // hit ceiling above org
+ {
+ // reduce stepwalk height
+ tracebox(org, m1, m2, org + stepheightvec, movemode, e);
+ tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e);
+ }
+ else //if (trace_fraction < 1)
+ {
+ tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
+ if (trace_startsolid) // hit ceiling above org
+ {
+ // reduce jumpstepwalk height
+ tracebox(org, m1, m2, org + jumpstepheightvec, movemode, e);
+ tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e);
+ }
+ }
+
+ if (trace_fraction < 1)
{
+ vector v = trace_endpos;
+ v.z = org.z + jumpheight_vec.z;
+ if(navigation_checkladders(e, v, m1, m2, end, end2, movemode))
+ {
+ if(autocvar_bot_debug_tracewalk)
+ {
+ debugnode(e, v);
+ debugnodestatus(v, DEBUG_NODE_SUCCESS);
+ }
+
+ //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+ return true;
+ }
+
if(autocvar_bot_debug_tracewalk)
debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
- IL_EACH(g_ladders, it.classname == "func_ladder",
- { it.solid = SOLID_BSP; });
-
traceline( org, move, movemode, e);
- IL_EACH(g_ladders, it.classname == "func_ladder",
- { it.solid = SOLID_TRIGGER; });
-
if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
{
vector nextmove;
move = trace_endpos;
while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
{
- nextmove = move + (dir * stepdist);
+ nextmove = move + (flatdir * stepdist);
traceline( move, nextmove, movemode, e);
move = nextmove;
}
- }
- else if (trace_ent.classname == "func_ladder")
- {
- tw_ladder = trace_ent;
- vector ladder_bottom = trace_endpos - dir * m2.x;
- vector ladder_top = ladder_bottom;
- ladder_top.z = trace_ent.absmax.z + (-m1.z + 1);
- tracebox(ladder_bottom, m1, m2, ladder_top, movemode, e);
- if (trace_fraction < 1 || trace_startsolid)
- {
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
-
- return false; // failed
- }
- org = ladder_top + dir * m2.x;
- move = org + dir * stepdist;
- continue;
+ flatdist = vlen(vec2(end - move));
}
else
{
// (this is the same logic as the Quake walkmove function used)
tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
- // moved successfully
- if(swimming)
+ org = trace_endpos;
+
+ if (!ignorehazards)
{
- float c;
- c = pointcontents(org + '0 0 1');
- if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME))
- swimming = false;
- else
- continue;
+ if (IN_LAVA(org))
+ {
+ if(autocvar_bot_debug_tracewalk)
+ {
+ debugnode(e, trace_endpos);
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+ }
+
+ //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
+ return false;
+ }
}
- org = trace_endpos;
- }
+ if (flatdist <= 0)
+ {
+ if(move.z >= end2.z && org.z < end2.z)
+ org.z = end2.z;
+ continue;
+ }
- if(tw_ladder && org.z < tw_ladder.absmax.z)
- {
- // stop tracewalk if destination height is lower than the top of the ladder
- // otherwise bot can't easily figure out climbing direction
+ if(org.z > move.z - 1 || !SUBMERGED(org))
+ {
+ nav_action = NAV_WALK;
+ continue;
+ }
+
+ // ended up submerged while walking
if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_FAIL);
+ debugnode(e, org);
- return false;
+ RESURFACE_LIMITED(org, move.z);
+ nav_action = NAV_SWIM_ONWATER;
+ continue;
}
}
// completely empty the goal stack, used when deciding where to go
void navigation_clearroute(entity this)
{
- //print("bot ", etos(this), " clear\n");
+ this.goalcurrent_prev = this.goalcurrent;
+ this.goalcurrent_distance_2d = FLOAT_MAX;
+ this.goalcurrent_distance_z = FLOAT_MAX;
+ this.goalcurrent_distance_time = 0;
+ this.goalentity_lock_timeout = 0;
this.goalentity = NULL;
this.goalcurrent = NULL;
this.goalstack01 = NULL;
// steps to the goal, and then recalculate the path.
void navigation_pushroute(entity this, entity e)
{
+ this.goalcurrent_prev = this.goalcurrent;
+ this.goalcurrent_distance_2d = FLOAT_MAX;
+ this.goalcurrent_distance_z = FLOAT_MAX;
+ this.goalcurrent_distance_time = 0;
//print("bot ", etos(this), " push ", etos(e), "\n");
if(this.goalstack31 == this.goalentity)
this.goalentity = NULL;
// (used when a spawnfunc_waypoint is reached)
void navigation_poproute(entity this)
{
+ this.goalcurrent_prev = this.goalcurrent;
+ this.goalcurrent_distance_2d = FLOAT_MAX;
+ this.goalcurrent_distance_z = FLOAT_MAX;
+ this.goalcurrent_distance_time = 0;
//print("bot ", etos(this), " pop\n");
if(this.goalcurrent == this.goalentity)
+ {
this.goalentity = NULL;
+ this.goalentity_lock_timeout = 0;
+ }
this.goalcurrent = this.goalstack01;
this.goalstack01 = this.goalstack02;
this.goalstack02 = this.goalstack03;
this.goalstack31 = NULL;
}
-float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist)
+// walking to wp (walkfromwp == false) v2 and v2_height will be used as
+// waypoint destination coordinates instead of v (only useful for box waypoints)
+// for normal waypoints v2 == v and v2_height == 0
+float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, vector o2, float o2_height, float walkfromwp, float bestdist)
{
- float dist;
- dist = vlen(v - org);
- if (bestdist > dist)
+ if (vdist(v - org, <, bestdist))
{
traceline(v, org, true, ent);
if (trace_fraction == 1)
{
if (walkfromwp)
{
- if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, bot_navigation_movemode))
+ if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, v2, v2_height, bot_navigation_movemode))
return true;
}
else
{
- if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v, bot_navigation_movemode))
+ if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, o2, o2_height, bot_navigation_movemode))
return true;
}
}
// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
{
+ if(ent.tag_entity)
+ ent = ent.tag_entity;
+
vector pm1 = ent.origin + ent.mins;
vector pm2 = ent.origin + ent.maxs;
IL_EACH(g_waypoints, it != ent && it != except,
{
if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
+ {
+ if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal)
+ {
+ waypoint_clearlinks(ent); // initialize wpXXmincost fields
+ navigation_item_addlink(it, ent);
+ }
return it;
+ }
});
- vector org = ent.origin + 0.5 * (ent.mins + ent.maxs);
- org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
- // TODO possibly make other code have the same support for bboxes
- if(ent.tag_entity)
- org = org + ent.tag_entity.origin;
+ vector org = ent.origin;
if (navigation_testtracewalk)
te_plasmaburn(org);
entity best = NULL;
- vector v;
+ vector v = '0 0 0';
+
+ if(ent.size && !IS_PLAYER(ent))
+ {
+ org += 0.5 * (ent.mins + ent.maxs);
+ org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
+ }
+
+ if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal)
+ {
+ waypoint_clearlinks(ent); // initialize wpXXmincost fields
+ IL_EACH(g_waypoints, it != ent,
+ {
+ if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
+ continue;
+
+ set_tracewalk_dest(ent, it.origin, false);
+ if (vdist(tracewalk_dest - it.origin, <, 1050)
+ && tracewalk(ent, it.origin, PL_MIN_CONST, PL_MAX_CONST,
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+ {
+ navigation_item_addlink(it, ent);
+ }
+ });
+ }
// box check failed, try walk
IL_EACH(g_waypoints, it != ent,
{
- if(it.wpisbox)
+ if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
+ continue;
+ v = it.origin;
+
+ if (walkfromwp)
{
- vector wm1 = it.origin + it.mins;
- vector wm2 = it.origin + it.maxs;
- v.x = bound(wm1_x, org.x, wm2_x);
- v.y = bound(wm1_y, org.y, wm2_y);
- v.z = bound(wm1_z, org.z, wm2_z);
+ set_tracewalk_dest(ent, v, true);
+ if (trace_ent == ent)
+ {
+ bestdist = 0;
+ best = it;
+ break;
+ }
}
else
- v = it.origin;
- if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist))
+ set_tracewalk_dest(it, org, false);
+
+ if (navigation_waypoint_will_link(v, org, ent,
+ tracewalk_dest, tracewalk_dest_height,
+ tracewalk_dest, tracewalk_dest_height, walkfromwp, bestdist))
{
- bestdist = vlen(v - org);
+ if (walkfromwp)
+ bestdist = vlen(tracewalk_dest - v);
+ else
+ bestdist = vlen(v - org);
best = it;
}
});
+ if(!best && !ent.navigation_dynamicgoal)
+ {
+ int solid_save = ent.solid;
+ ent.solid = SOLID_BSP;
+ IL_EACH(g_jumppads, true,
+ {
+ if(trigger_push_test(it, ent))
+ {
+ best = it.nearestwaypoint;
+ break;
+ }
+ });
+ ent.solid = solid_save;
+ }
return best;
}
entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
// finds the waypoints near the bot initiating a navigation query
float navigation_markroutes_nearestwaypoints(entity this, float maxdist)
{
- vector v, m1, m2;
-// navigation_testtracewalk = true;
+ //navigation_testtracewalk = true;
int c = 0;
IL_EACH(g_waypoints, !it.wpconsidered,
{
- if (it.wpisbox)
- {
- m1 = it.origin + it.mins;
- m2 = it.origin + it.maxs;
- v = this.origin;
- v.x = bound(m1_x, v.x, m2_x);
- v.y = bound(m1_y, v.y, m2_y);
- v.z = bound(m1_z, v.z, m2_z);
- }
- else
- v = it.origin;
- vector diff = v - this.origin;
+ set_tracewalk_dest(it, this.origin, false);
+
+ vector diff = tracewalk_dest - this.origin;
diff.z = max(0, diff.z);
if(vdist(diff, <, maxdist))
{
it.wpconsidered = true;
- if (tracewalk(this, this.origin, this.mins, this.maxs, v, bot_navigation_movemode))
+ if (tracewalk(this, this.origin, this.mins, this.maxs,
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
{
- it.wpnearestpoint = v;
- it.wpcost = vlen(v - this.origin) + it.dmg;
+ it.wpnearestpoint = tracewalk_dest;
+ it.wpcost = waypoint_gettravelcost(this.origin, tracewalk_dest, this, it) + it.dmg;
it.wpfire = 1;
it.enemy = NULL;
c = c + 1;
}
// updates a path link if a spawnfunc_waypoint link is better than the current one
-void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p)
+void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost, vector p)
{
- vector m1;
- vector m2;
+ vector m1, m2;
vector v;
if (wp.wpisbox)
{
- m1 = wp.absmin;
- m2 = wp.absmax;
+ m1 = wp.origin + wp.mins;
+ m2 = wp.origin + wp.maxs;
v.x = bound(m1_x, p.x, m2_x);
v.y = bound(m1_y, p.y, m2_y);
v.z = bound(m1_z, p.z, m2_z);
}
else
v = wp.origin;
- cost2 = cost2 + vlen(v - p);
- if (wp.wpcost > cost2)
+ if (w.wpflags & WAYPOINTFLAG_TELEPORT)
+ cost += w.wp00mincost; // assuming teleport has exactly one destination
+ else
+ cost += waypoint_gettravelcost(p, v, w, wp);
+ if (wp.wpcost > cost)
{
- wp.wpcost = cost2;
+ wp.wpcost = cost;
wp.enemy = w;
wp.wpfire = 1;
wp.wpnearestpoint = v;
cost = it.wpcost; // cost to walk from it to home
p = it.wpnearestpoint;
entity wp = it;
- IL_EACH(g_waypoints, true,
+ IL_EACH(g_waypoints, it != wp,
{
- if(wp != it.wp00) if(wp != it.wp01) if(wp != it.wp02) if(wp != it.wp03)
- if(wp != it.wp04) if(wp != it.wp05) if(wp != it.wp06) if(wp != it.wp07)
- if(wp != it.wp08) if(wp != it.wp09) if(wp != it.wp10) if(wp != it.wp11)
- if(wp != it.wp12) if(wp != it.wp13) if(wp != it.wp14) if(wp != it.wp15)
- if(wp != it.wp16) if(wp != it.wp17) if(wp != it.wp18) if(wp != it.wp19)
- if(wp != it.wp20) if(wp != it.wp21) if(wp != it.wp22) if(wp != it.wp23)
- if(wp != it.wp24) if(wp != it.wp25) if(wp != it.wp26) if(wp != it.wp27)
- if(wp != it.wp28) if(wp != it.wp29) if(wp != it.wp30) if(wp != it.wp31)
+ if(!waypoint_islinked(it, wp))
continue;
cost2 = cost + it.dmg;
navigation_markroutes_checkwaypoint(wp, it, cost2, p);
if(e.blacklisted)
return;
+ rangebias = waypoint_getlinearcost(rangebias);
+ f = waypoint_getlinearcost(f);
+
if (IS_PLAYER(e))
{
bool rate_wps = false;
}
}
- vector o = (e.absmin + e.absmax) * 0.5;
+ vector goal_org = (e.absmin + e.absmax) * 0.5;
//print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n");
if(g_jetpack)
if(this.items & IT_JETPACK)
if(autocvar_bot_ai_navigation_jetpack)
- if(vdist(this.origin - o, >, autocvar_bot_ai_navigation_jetpack_mindistance))
+ if(vdist(this.origin - goal_org, >, autocvar_bot_ai_navigation_jetpack_mindistance))
{
vector pointa, pointb;
pointa = trace_endpos - '0 0 1';
// Point B
- traceline(o, o + '0 0 65535', MOVE_NORMAL, e);
+ traceline(goal_org, goal_org + '0 0 65535', MOVE_NORMAL, e);
pointb = trace_endpos - '0 0 1';
// Can I see these two points from the sky?
if ((!e.nearestwaypoint || e.navigation_dynamicgoal)
&& e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
{
- nwp = navigation_findnearestwaypoint(e, true);
- if(nwp)
- e.nearestwaypoint = nwp;
+ if(IS_BOT_CLIENT(e) && e.goalcurrent && e.goalcurrent.classname == "waypoint")
+ e.nearestwaypoint = nwp = e.goalcurrent;
else
+ e.nearestwaypoint = nwp = navigation_findnearestwaypoint(e, true);
+ if(!nwp)
{
LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e));
}
LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
- if (nwp)
- if (nwp.wpcost < 10000000)
+ if (nwp && nwp.wpcost < 10000000)
{
//te_wizspike(nwp.wpnearestpoint);
- LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = ");
- f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint)));
+ float nwptoitem_cost = 0;
+ if(nwp.wpflags & WAYPOINTFLAG_TELEPORT)
+ nwptoitem_cost = nwp.wp00mincost;
+ else
+ nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e);
+ float cost = nwp.wpcost + nwptoitem_cost;
+ LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = ");
+ f = f * rangebias / (rangebias + cost);
LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
if (navigation_bestrating < f)
{
if (!e)
return false;
+ entity teleport_goal = NULL;
+
this.goalentity = e;
+ if(e.wpflags & WAYPOINTFLAG_TELEPORT)
+ {
+ // force teleport destination as route destination
+ teleport_goal = e;
+ navigation_pushroute(this, e.wp00);
+ this.goalentity = e.wp00;
+ }
+
// put the entity on the goal stack
//print("routetogoal ", etos(e), "\n");
navigation_pushroute(this, e);
+ if(teleport_goal)
+ e = this.goalentity;
+
if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
{
this.wp_goal_prev1 = this.wp_goal_prev0;
return true;
// if it can reach the goal there is nothing more to do
- if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), (e.absmin + e.absmax) * 0.5, bot_navigation_movemode))
+ set_tracewalk_dest(e, startposition, true);
+ if ((!IS_MOVABLE(this.goalcurrent) || vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE))
+ && (trace_ent == this || tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this),
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)))
+ {
return true;
+ }
entity nearest_wp = NULL;
// see if there are waypoints describing a path to the item
e = e.nearestwaypoint;
nearest_wp = e;
}
+ else if(teleport_goal)
+ e = teleport_goal;
else
e = e.enemy; // we already have added it, so...
if(nearest_wp && nearest_wp.enemy)
{
// often path can be optimized by not adding the nearest waypoint
- if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), (this.goalentity.absmin + this.goalentity.absmax) * 0.5, bot_navigation_movemode))
+ if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor)
+ {
+ if (nearest_wp.enemy.wpcost < autocvar_bot_ai_strategyinterval_movingtarget)
+ {
+ if (vdist(vec2(this.goalentity.origin - nearest_wp.origin), <, 32))
+ e = nearest_wp.enemy;
+ else
+ {
+ set_tracewalk_dest(this.goalentity, nearest_wp.enemy.origin, true);
+ if (trace_ent == this || (vdist(tracewalk_dest - nearest_wp.enemy.origin, <, 1050)
+ && vlen2(tracewalk_dest - nearest_wp.enemy.origin) < vlen2(nearest_wp.origin - nearest_wp.enemy.origin)
+ && tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)))
+ {
+ e = nearest_wp.enemy;
+ }
+ }
+ }
+ }
+ else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity))
e = nearest_wp.enemy;
}
return false;
}
+// shorten path by removing intermediate goals
+void navigation_shortenpath(entity this)
+{
+ if (!this.goalstack01 || wasfreed(this.goalstack01))
+ return;
+ if (this.bot_tracewalk_time > time)
+ return;
+ this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
+
+ bool cut_allowed = false;
+ entity next = this.goalentity;
+ // evaluate whether bot can discard current route and chase directly a player, trying to
+ // keep waypoint route as long as possible, as it is safer and faster (bot can bunnyhop)
+ if (IS_MOVABLE(next))
+ {
+ set_tracewalk_dest(next, this.origin, true);
+ if (vdist(this.origin - tracewalk_dest, <, 200))
+ cut_allowed = true;
+ else if (vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE)
+ && vdist(tracewalk_dest - this.goalcurrent.origin, >, 200)
+ && vdist(this.origin - this.goalcurrent.origin, >, 100)
+ && checkpvs(this.origin + this.view_ofs, next))
+ {
+ if (vlen2(next.origin - this.origin) < vlen2(this.goalcurrent.origin - this.origin))
+ cut_allowed = true;
+ else
+ {
+ vector deviation = vectoangles(this.goalcurrent.origin - this.origin) - vectoangles(next.origin - this.origin);
+ while (deviation.y < -180) deviation.y += 360;
+ while (deviation.y > 180) deviation.y -= 360;
+ if (fabs(deviation.y) > 25)
+ cut_allowed = true;
+ }
+ }
+ if (cut_allowed)
+ {
+ if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+ {
+ LOG_DEBUG("path optimized for ", this.netname, ", route cleared");
+ do
+ {
+ navigation_poproute(this);
+ }
+ while (this.goalcurrent != next);
+ }
+ return;
+ }
+ }
+
+ next = this.goalstack01;
+ // if for some reason the bot is closer to the next goal, pop the current one
+ if (!IS_MOVABLE(next) // already checked in the previous case
+ && vlen2(this.goalcurrent.origin - next.origin) > vlen2(next.origin - this.origin)
+ && checkpvs(this.origin + this.view_ofs, next))
+ {
+ set_tracewalk_dest(next, this.origin, true);
+ cut_allowed = true;
+ }
+
+ if (cut_allowed)
+ {
+ if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+ {
+ LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
+ navigation_poproute(this);
+ }
+ }
+}
+
// removes any currently touching waypoints from the goal stack
// (this is how bots detect if they reached a goal)
-void navigation_poptouchedgoals(entity this)
+int navigation_poptouchedgoals(entity this)
{
- vector org, m1, m2;
- org = this.origin;
- m1 = org + this.mins;
- m2 = org + this.maxs;
+ int removed_goals = 0;
+
+ if(!this.goalcurrent)
+ return removed_goals;
if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
// make sure jumppad is really hit, don't rely on distance based checks
// as they may report a touch even if it didn't really happen
- if(this.lastteleporttime>0)
- if(time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
+ if(this.lastteleporttime > 0 && TELEPORT_USED(this, this.goalcurrent))
{
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
}
navigation_poproute(this);
- return;
+ this.lastteleporttime = 0;
+ ++removed_goals;
}
+ else
+ return removed_goals;
}
-
- // If for some reason the bot is closer to the next goal, pop the current one
- if(this.goalstack01 && !wasfreed(this.goalstack01))
- if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin))
- if(checkpvs(this.origin + this.view_ofs, this.goalstack01))
- if(tracewalk(this, this.origin, this.mins, this.maxs, (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5, bot_navigation_movemode))
+ else if (this.lastteleporttime > 0)
{
- LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
- navigation_poproute(this);
- // TODO this may also be a nice idea to do "early" (e.g. by
- // manipulating the vlen() comparisons) to shorten paths in
- // general - this would make bots walk more "on rails" than
- // "zigzagging" which they currently do with sufficiently
- // random-like waypoints, and thus can make a nice bot
- // personality property
+ // sometimes bot is pushed so hard (by a jumppad or a shot) that ends up touching the next
+ // teleport / jumppad / warpzone present in its path skipping check of one or more goals
+ // if so immediately fix bot path by removing skipped goals
+ entity tele_ent = NULL;
+ if (this.goalstack01 && (this.goalstack01.wpflags & WAYPOINTFLAG_TELEPORT))
+ tele_ent = this.goalstack01;
+ else if (this.goalstack02 && (this.goalstack02.wpflags & WAYPOINTFLAG_TELEPORT))
+ tele_ent = this.goalstack02;
+ else if (this.goalstack03 && (this.goalstack03.wpflags & WAYPOINTFLAG_TELEPORT))
+ tele_ent = this.goalstack03;
+ if (tele_ent && TELEPORT_USED(this, tele_ent))
+ {
+ if (this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+ if (tele_ent.wpflags & WAYPOINTFLAG_PERSONAL && tele_ent.owner == this)
+ {
+ this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
+ this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
+ }
+ while (this.goalcurrent != tele_ent)
+ {
+ navigation_poproute(this);
+ ++removed_goals;
+ }
+ navigation_poproute(this);
+ this.lastteleporttime = 0;
+ ++removed_goals;
+ return removed_goals;
+ }
}
// Loose goal touching check when running
if(this.aistatus & AI_STATUS_RUNNING)
if(this.goalcurrent.classname=="waypoint")
- if(!(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
- if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
+ if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
{
if(vdist(this.origin - this.goalcurrent.origin, <, 150))
{
}
navigation_poproute(this);
+ ++removed_goals;
+ if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ return removed_goals;
}
}
}
gc_min = this.goalcurrent.origin - '1 1 1' * 12;
gc_max = this.goalcurrent.origin + '1 1 1' * 12;
}
- if(!boxesoverlap(m1, m2, gc_min, gc_max))
- break;
-
- if((this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
+ if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
break;
// Detect personal waypoints
}
navigation_poproute(this);
+ ++removed_goals;
+ if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ return removed_goals;
}
+ return removed_goals;
+}
+
+entity navigation_get_really_close_waypoint(entity this)
+{
+ entity wp = this.goalcurrent;
+ if(!wp)
+ wp = this.goalcurrent_prev;
+ if(!wp)
+ return NULL;
+ if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, 50))
+ {
+ wp = this.goalcurrent_prev;
+ if(!wp)
+ return NULL;
+ }
+ if(wp.classname != "waypoint")
+ {
+ wp = wp.nearestwaypoint;
+ if(!wp)
+ return NULL;
+ }
+ if(vdist(wp.origin - this.origin, >, 50))
+ {
+ wp = NULL;
+ IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+ {
+ if(vdist(it.origin - this.origin, <, 50))
+ {
+ wp = it;
+ break;
+ }
+ });
+ if(!wp)
+ return NULL;
+ }
+ if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
+ return NULL;
+
+ set_tracewalk_dest(wp, this.origin, false);
+ if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+ {
+ return NULL;
+ }
+ return wp;
}
// begin a goal selection session (queries spawnfunc_waypoint network)
this.navigation_jetpack_goal = NULL;
navigation_bestrating = -1;
+ entity wp = navigation_get_really_close_waypoint(this);
navigation_clearroute(this);
navigation_bestgoal = NULL;
- navigation_markroutes(this, NULL);
+ navigation_markroutes(this, wp);
}
// ends a goal selection session (updates goal stack to the best goal)
vector m1, m2, v, o;
float c, d, danger;
c = 0;
+ entity wp_cur;
IL_EACH(g_waypoints, true,
{
danger = 0;
- m1 = it.mins;
- m2 = it.maxs;
+ m1 = it.absmin;
+ m2 = it.absmax;
+ wp_cur = it;
IL_EACH(g_bot_dodge, it.bot_dodge,
{
v = it.origin;
v.y = bound(m1_y, v.y, m2_y);
v.z = bound(m1_z, v.z, m2_z);
o = (it.absmin + it.absmax) * 0.5;
- d = it.bot_dodgerating - vlen(o - v);
+ d = waypoint_getlinearcost(it.bot_dodgerating) - waypoint_gettravelcost(o, v, it, wp_cur);
if (d > 0)
{
traceline(o, v, true, NULL);
void navigation_unstuck(entity this)
{
- float search_radius = 1000;
-
if (!autocvar_bot_wander_enable)
return;
+ bool has_user_waypoints = false;
+ IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED),
+ {
+ has_user_waypoints = true;
+ break;
+ });
+ if (!has_user_waypoints)
+ return;
+
+ float search_radius = 1000;
+
if (!bot_waypoint_queue_owner)
{
LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue");
// evaluate the next goal on the queue
float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
- if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), bot_waypoint_queue_goal.origin, bot_navigation_movemode))
+ set_tracewalk_dest(bot_waypoint_queue_goal, this.origin, false);
+ if (tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+ tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
{
if( d > bot_waypoint_queue_bestgoalrating)
{
{
LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_timeout_set(this);
this.aistatus &= ~AI_STATUS_STUCK;
}
else
vector jumpstepheightvec;
vector stepheightvec;
+vector jumpheight_vec;
entity navigation_bestgoal;
.entity goalstack20, goalstack21, goalstack22, goalstack23;
.entity goalstack24, goalstack25, goalstack26, goalstack27;
.entity goalstack28, goalstack29, goalstack30, goalstack31;
+
+.entity goalcurrent_prev;
+.float goalcurrent_distance_z;
+.float goalcurrent_distance_2d;
+.float goalcurrent_distance_time;
+
+.float goalentity_lock_timeout;
+
.entity nearestwaypoint;
+.float nearestwaypointtimeout;
+
+/*
+// item it is linked from waypoint it.wpXX (INCOMING link)
+// links are sorted by their cost (wpXXmincost)
+.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
+.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
+
+.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
+.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
+.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
+.float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost;
+*/
+
+#define navigation_item_islinked(from_wp, to_item) waypoint_islinked(to_item, from_wp)
+#define navigation_item_addlink(from_wp, to_item) \
+ waypoint_addlink_customcost(to_item, from_wp, waypoint_getlinkcost(from_wp, to_item))
+
+#define TELEPORT_USED(pl, tele_wp) \
+ (time - pl.lastteleporttime < ((tele_wp.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15) \
+ && boxesoverlap(tele_wp.absmin, tele_wp.absmax, pl.lastteleport_origin + STAT(PL_MIN, pl), pl.lastteleport_origin + STAT(PL_MAX, pl)))
+
+vector tracewalk_dest;
+float tracewalk_dest_height;
.entity wp_goal_prev0;
.entity wp_goal_prev1;
-.float nearestwaypointtimeout;
.float lastteleporttime;
+.vector lastteleport_origin;
.float blacklisted;
void navigation_dynamicgoal_set(entity this);
void navigation_dynamicgoal_unset(entity this);
+.int nav_submerged_state;
+#define SUBMERGED_UNDEFINED 0
+#define SUBMERGED_NO 1
+#define SUBMERGED_YES 2
+bool navigation_check_submerged_state(entity ent, vector pos);
+
/*
* Functions
void debuggoalstack(entity this);
-float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
+float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode);
float navigation_markroutes_nearestwaypoints(entity this, float maxdist);
float navigation_routetogoal(entity this, entity e, vector startposition);
void navigation_markroutes(entity this, entity fixed_source_waypoint);
void navigation_markroutes_inverted(entity fixed_source_waypoint);
void navigation_routerating(entity this, entity e, float f, float rangebias);
-void navigation_poptouchedgoals(entity this);
+void navigation_shortenpath(entity this);
+int navigation_poptouchedgoals(entity this);
void navigation_goalrating_start(entity this);
void navigation_goalrating_end(entity this);
+void navigation_goalrating_timeout_set(entity this);
+void navigation_goalrating_timeout_force(entity this);
+bool navigation_goalrating_timeout(entity this);
void navigation_unstuck(entity this);
void botframe_updatedangerousobjects(float maxupdate);
entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
-float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist);
+float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, vector o2, float o2_height, float walkfromwp, float bestdist);
#include <common/constants.qh>
#include <common/net_linked.qh>
+#include <common/physics/player.qh>
#include <lib/warpzone/common.qh>
#include <lib/warpzone/util_server.qh>
+.entity spawnpointmodel;
+void waypoint_unreachable(entity pl)
+{
+ IL_EACH(g_waypoints, true,
+ {
+ it.colormod = '0.5 0.5 0.5';
+ it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
+ });
+
+ entity e2 = navigation_findnearestwaypoint(pl, false);
+ if(!e2)
+ {
+ LOG_INFOF("Can't find any waypoint nearby\n");
+ return;
+ }
+
+ navigation_markroutes(pl, e2);
+
+ int j = 0;
+ int m = 0;
+ IL_EACH(g_waypoints, it.wpcost >= 10000000,
+ {
+ LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
+ it.colormod_z = 8;
+ it.effects |= EF_NODEPTHTEST | EF_BLUE;
+ j++;
+ m++;
+ });
+ if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
+ navigation_markroutes_inverted(e2);
+
+ j = 0;
+ IL_EACH(g_waypoints, it.wpcost >= 10000000,
+ {
+ LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
+ it.colormod_x = 8;
+ if (!(it.effects & EF_NODEPTHTEST)) // not already reported before
+ m++;
+ it.effects |= EF_NODEPTHTEST | EF_RED;
+ j++;
+ });
+ if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
+ if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
+
+ j = 0;
+ IL_EACH(g_spawnpoints, true,
+ {
+ if (navigation_findnearestwaypoint(it, false))
+ {
+ if(it.spawnpointmodel)
+ {
+ delete(it.spawnpointmodel);
+ it.spawnpointmodel = NULL;
+ }
+ }
+ else
+ {
+ if(!it.spawnpointmodel)
+ {
+ tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
+ entity e = new(spawnpointmodel);
+ vector org = trace_endpos + eZ;
+ setorigin(e, org);
+ e.solid = SOLID_TRIGGER;
+ it.spawnpointmodel = e;
+ }
+ LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
+ it.spawnpointmodel.effects |= EF_NODEPTHTEST;
+ _setmodel(it.spawnpointmodel, pl.model);
+ it.spawnpointmodel.frame = pl.frame;
+ it.spawnpointmodel.skin = pl.skin;
+ it.spawnpointmodel.colormap = pl.colormap;
+ it.spawnpointmodel.colormod = pl.colormod;
+ it.spawnpointmodel.glowmod = pl.glowmod;
+ setsize(it.spawnpointmodel, PL_MIN_CONST, PL_MAX_CONST);
+ j++;
+ }
+ });
+ if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
+
+ j = 0;
+ IL_EACH(g_items, true,
+ {
+ it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
+ it.colormod = '0.5 0.5 0.5';
+ });
+ IL_EACH(g_items, true,
+ {
+ if (navigation_findnearestwaypoint(it, false))
+ continue;
+ LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
+ it.effects |= EF_NODEPTHTEST | EF_RED;
+ it.colormod_x = 8;
+ j++;
+ });
+ if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
+
+ j = 0;
+ IL_EACH(g_items, true,
+ {
+ if (navigation_findnearestwaypoint(it, true))
+ continue;
+ LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
+ it.effects |= EF_NODEPTHTEST | EF_BLUE;
+ it.colormod_z = 8;
+ j++;
+ });
+ if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
+}
+
+vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
+{
+ vector new_org = org;
+ if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
+ {
+ vector map_center = havocbot_middlepoint;
+ if (autocvar_g_waypointeditor_symmetrical == -1)
+ map_center = autocvar_g_waypointeditor_symmetrical_origin;
+
+ new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
+ }
+ else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
+ {
+ float m = havocbot_symmetryaxis_equation.x;
+ float q = havocbot_symmetryaxis_equation.y;
+ if (autocvar_g_waypointeditor_symmetrical == -2)
+ {
+ m = autocvar_g_waypointeditor_symmetrical_axis.x;
+ q = autocvar_g_waypointeditor_symmetrical_axis.y;
+ }
+
+ new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
+ new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
+ }
+ new_org.z = org.z;
+ return new_org;
+}
+
void waypoint_setupmodel(entity wp)
{
if (autocvar_g_waypointeditor)
wp.colormod = '1 0 0';
else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
wp.colormod = '1 1 0';
+ else if (wp.wphardwired)
+ wp.colormod = '0.5 0 1';
else
wp.colormod = '1 1 1';
}
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)
{
- IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax),
+ 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;
});
return w;
}
+void waypoint_spawn_fromeditor(entity pl)
+{
+ entity e;
+ vector org = pl.origin;
+ int ctf_flags = havocbot_symmetryaxis_equation.z;
+ bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
+ || (autocvar_g_waypointeditor_symmetrical < 0));
+ int order = ctf_flags;
+ if(autocvar_g_waypointeditor_symmetrical_order >= 2)
+ {
+ order = autocvar_g_waypointeditor_symmetrical_order;
+ ctf_flags = order;
+ }
+
+ if(!PHYS_INPUT_BUTTON_CROUCH(pl))
+ {
+ // snap waypoint to item's origin if close enough
+ IL_EACH(g_items, true,
+ {
+ vector item_org = (it.absmin + it.absmax) * 0.5;
+ item_org.z = it.absmin.z - PL_MIN_CONST.z;
+ if(vlen(item_org - org) < 30)
+ {
+ org = item_org;
+ break;
+ }
+ });
+ }
+
+ LABEL(add_wp);
+ e = waypoint_spawn(org, org, 0);
+ if(!e)
+ {
+ LOG_INFOF("Couldn't spawn waypoint at %v\n", org);
+ return;
+ }
+ waypoint_schedulerelink(e);
+ bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
+ if(sym)
+ {
+ org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+ if (vdist(org - pl.origin, >, 32))
+ {
+ if(order > 2)
+ order--;
+ else
+ sym = false;
+ goto add_wp;
+ }
+ }
+}
+
+void waypoint_remove(entity wp)
+{
+ // tell all waypoints linked to wp that they need to relink
+ IL_EACH(g_waypoints, it != wp,
+ {
+ if (waypoint_islinked(it, wp))
+ waypoint_removelink(it, wp);
+ });
+ delete(wp);
+}
+
+void waypoint_remove_fromeditor(entity pl)
+{
+ entity e = navigation_findnearestwaypoint(pl, false);
+
+ int ctf_flags = havocbot_symmetryaxis_equation.z;
+ bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
+ || (autocvar_g_waypointeditor_symmetrical < 0));
+ int order = ctf_flags;
+ if(autocvar_g_waypointeditor_symmetrical_order >= 2)
+ {
+ order = autocvar_g_waypointeditor_symmetrical_order;
+ ctf_flags = order;
+ }
+
+ LABEL(remove_wp);
+ if (!e) return;
+ if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
+
+ if (e.wphardwired)
+ {
+ LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired\n");
+ return;
+ }
+
+ entity wp_sym = NULL;
+ if (sym)
+ {
+ vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+ FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
+ if(vdist(org - it.origin, <, 3))
+ {
+ wp_sym = it;
+ break;
+ }
+ });
+ }
+
+ bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
+ waypoint_remove(e);
+
+ if (sym && wp_sym)
+ {
+ e = wp_sym;
+ if(order > 2)
+ order--;
+ else
+ sym = false;
+ goto remove_wp;
+ }
+}
+
void waypoint_removelink(entity from, entity to)
{
if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK))
return false;
}
-// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
-void waypoint_addlink(entity from, entity to)
+void waypoint_updatecost_foralllinks()
{
- float c;
+ IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+ {
+ if(it.wp00) it.wp00mincost = waypoint_getlinkcost(it, it.wp00);
+ if(it.wp01) it.wp01mincost = waypoint_getlinkcost(it, it.wp01);
+ if(it.wp02) it.wp02mincost = waypoint_getlinkcost(it, it.wp02);
+ if(it.wp03) it.wp03mincost = waypoint_getlinkcost(it, it.wp03);
+ if(it.wp04) it.wp04mincost = waypoint_getlinkcost(it, it.wp04);
+ if(it.wp05) it.wp05mincost = waypoint_getlinkcost(it, it.wp05);
+ if(it.wp06) it.wp06mincost = waypoint_getlinkcost(it, it.wp06);
+ if(it.wp07) it.wp07mincost = waypoint_getlinkcost(it, it.wp07);
+ if(it.wp08) it.wp08mincost = waypoint_getlinkcost(it, it.wp08);
+ if(it.wp09) it.wp09mincost = waypoint_getlinkcost(it, it.wp09);
+ if(it.wp10) it.wp10mincost = waypoint_getlinkcost(it, it.wp10);
+ if(it.wp11) it.wp11mincost = waypoint_getlinkcost(it, it.wp11);
+ if(it.wp12) it.wp12mincost = waypoint_getlinkcost(it, it.wp12);
+ if(it.wp13) it.wp13mincost = waypoint_getlinkcost(it, it.wp13);
+ if(it.wp14) it.wp14mincost = waypoint_getlinkcost(it, it.wp14);
+ if(it.wp15) it.wp15mincost = waypoint_getlinkcost(it, it.wp15);
+ if(it.wp16) it.wp16mincost = waypoint_getlinkcost(it, it.wp16);
+ if(it.wp17) it.wp17mincost = waypoint_getlinkcost(it, it.wp17);
+ if(it.wp18) it.wp18mincost = waypoint_getlinkcost(it, it.wp18);
+ if(it.wp19) it.wp19mincost = waypoint_getlinkcost(it, it.wp19);
+ if(it.wp20) it.wp20mincost = waypoint_getlinkcost(it, it.wp20);
+ if(it.wp21) it.wp21mincost = waypoint_getlinkcost(it, it.wp21);
+ if(it.wp22) it.wp22mincost = waypoint_getlinkcost(it, it.wp22);
+ if(it.wp23) it.wp23mincost = waypoint_getlinkcost(it, it.wp23);
+ if(it.wp24) it.wp24mincost = waypoint_getlinkcost(it, it.wp24);
+ if(it.wp25) it.wp25mincost = waypoint_getlinkcost(it, it.wp25);
+ if(it.wp26) it.wp26mincost = waypoint_getlinkcost(it, it.wp26);
+ if(it.wp27) it.wp27mincost = waypoint_getlinkcost(it, it.wp27);
+ if(it.wp28) it.wp28mincost = waypoint_getlinkcost(it, it.wp28);
+ if(it.wp29) it.wp29mincost = waypoint_getlinkcost(it, it.wp29);
+ if(it.wp30) it.wp30mincost = waypoint_getlinkcost(it, it.wp30);
+ if(it.wp31) it.wp31mincost = waypoint_getlinkcost(it, it.wp31);
+ });
+}
- if (from == to)
- return;
- if (from.wpflags & WAYPOINTFLAG_NORELINK)
- return;
+float waypoint_getlinearcost(float dist)
+{
+ if(skill >= autocvar_bot_ai_bunnyhop_skilloffset)
+ return dist / (autocvar_sv_maxspeed * 1.25);
+ return dist / autocvar_sv_maxspeed;
+}
+float waypoint_getlinearcost_underwater(float dist)
+{
+ // NOTE: this value is hardcoded on the engine too, see SV_WaterMove
+ return dist / (autocvar_sv_maxspeed * 0.7);
+}
- if (waypoint_islinked(from, to))
- return;
+float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent)
+{
+ bool submerged_from = navigation_check_submerged_state(from_ent, from);
+ bool submerged_to = navigation_check_submerged_state(to_ent, to);
+
+ if (submerged_from && submerged_to)
+ return waypoint_getlinearcost_underwater(vlen(to - from));
+
+ float c = waypoint_getlinearcost(vlen(to - from));
- if (to.wpisbox || from.wpisbox)
+ float height = from.z - to.z;
+ if(height > jumpheight_vec.z && autocvar_sv_gravity > 0)
{
- // if either is a box we have to find the nearest points on them to
- // calculate the distance properly
- vector v1, v2, m1, m2;
- v1 = from.origin;
- m1 = to.absmin;
- m2 = to.absmax;
- v1_x = bound(m1_x, v1_x, m2_x);
- v1_y = bound(m1_y, v1_y, m2_y);
- v1_z = bound(m1_z, v1_z, m2_z);
- v2 = to.origin;
- m1 = from.absmin;
- m2 = from.absmax;
- v2_x = bound(m1_x, v2_x, m2_x);
- v2_y = bound(m1_y, v2_y, m2_y);
- v2_z = bound(m1_z, v2_z, m2_z);
- v2 = to.origin;
- c = vlen(v2 - v1);
+ float height_cost = sqrt(height / (autocvar_sv_gravity / 2));
+ c = waypoint_getlinearcost(vlen(vec2(to - from))); // xy distance cost
+ if(height_cost > c)
+ c = height_cost;
}
- else
- c = vlen(to.origin - from.origin);
+
+ if (submerged_from || submerged_to)
+ return (c + waypoint_getlinearcost_underwater(vlen(to - from))) / 2;
+ return c;
+}
+
+float waypoint_getlinkcost(entity from, entity to)
+{
+ vector v1 = from.origin;
+ vector v2 = to.origin;
+ if (from.wpisbox)
+ {
+ vector m1 = from.absmin, m2 = from.absmax;
+ v1.x = bound(m1.x, v2.x, m2.x);
+ v1.y = bound(m1.y, v2.y, m2.y);
+ v1.z = bound(m1.z, v2.z, m2.z);
+ }
+ if (to.wpisbox)
+ {
+ vector m1 = to.absmin, m2 = to.absmax;
+ v2.x = bound(m1.x, v1.x, m2.x);
+ v2.y = bound(m1.y, v1.y, m2.y);
+ v2.z = bound(m1.z, v1.z, m2.z);
+ }
+ return waypoint_gettravelcost(v1, v2, from, to);
+}
+
+// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
+// if c == -1 automatically determine cost of the link
+void waypoint_addlink_customcost(entity from, entity to, float c)
+{
+ if (from == to || waypoint_islinked(from, to))
+ return;
+ if (c == -1 && (from.wpflags & WAYPOINTFLAG_NORELINK))
+ return;
+
+ if(c == -1)
+ c = waypoint_getlinkcost(from, to);
if (from.wp31mincost < c) return;
if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
from.wp00 = to;from.wp00mincost = c;return;
}
+void waypoint_addlink(entity from, entity to)
+{
+ waypoint_addlink_customcost(from, to, -1);
+}
+
// relink this spawnfunc_waypoint
// (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
// (SLOW!)
void waypoint_think(entity this)
{
- vector sv, sm1, sm2, ev, em1, em2, dv;
+ vector sv = '0 0 0', sv2 = '0 0 0', ev = '0 0 0', ev2 = '0 0 0', dv;
+ float sv2_height = 0, ev2_height = 0;
bot_calculate_stepheightvec();
bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
//dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n");
- sm1 = this.origin + this.mins;
- sm2 = this.origin + this.maxs;
IL_EACH(g_waypoints, this != it,
{
if (boxesoverlap(this.absmin, this.absmax, it.absmin, it.absmax))
++relink_pvsculled;
continue;
}
- sv = it.origin;
- sv.x = bound(sm1_x, sv.x, sm2_x);
- sv.y = bound(sm1_y, sv.y, sm2_y);
- sv.z = bound(sm1_z, sv.z, sm2_z);
- ev = this.origin;
- em1 = it.origin + it.mins;
- em2 = it.origin + it.maxs;
- ev.x = bound(em1_x, ev.x, em2_x);
- ev.y = bound(em1_y, ev.y, em2_y);
- ev.z = bound(em1_z, ev.z, em2_z);
+
+ sv = set_tracewalk_dest_2(this, it.origin);
+ sv2 = tracewalk_dest;
+ sv2_height = tracewalk_dest_height;
+ ev = set_tracewalk_dest_2(it, this.origin);
+ ev2 = tracewalk_dest;
+ ev2_height = tracewalk_dest_height;
+
dv = ev - sv;
dv.z = 0;
if(vdist(dv, >=, 1050)) // max search distance in XY
++relink_lengthculled;
continue;
}
+
navigation_testtracewalk = 0;
- if (!this.wpisbox)
- {
- tracebox(sv - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, sv, false, this);
- if (!trace_startsolid)
- {
- //dprint("sv deviation", vtos(trace_endpos - sv), "\n");
- sv = trace_endpos + '0 0 1';
- }
- }
- if (!it.wpisbox)
- {
- tracebox(ev - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, ev, false, it);
- if (!trace_startsolid)
- {
- //dprint("ev deviation", vtos(trace_endpos - ev), "\n");
- ev = trace_endpos + '0 0 1';
- }
- }
+
//traceline(this.origin, it.origin, false, NULL);
//if (trace_fraction == 1)
- if (!this.wpisbox && tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev, MOVE_NOMONSTERS))
- waypoint_addlink(this, it);
- else
+ if (this.wpisbox)
relink_walkculled += 0.5;
- if (!it.wpisbox && tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv, MOVE_NOMONSTERS))
- waypoint_addlink(it, this);
else
+ {
+ if (tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev2, ev2_height, MOVE_NOMONSTERS))
+ waypoint_addlink(this, it);
+ else
+ relink_walkculled += 0.5;
+ }
+
+ if (it.wpisbox)
relink_walkculled += 0.5;
+ else
+ {
+ if (tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS))
+ waypoint_addlink(it, this);
+ else
+ relink_walkculled += 0.5;
+ }
}
});
navigation_testtracewalk = 0;
//waypoint_schedulerelink(this);
}
-void waypoint_remove(entity wp)
-{
- // tell all waypoints linked to wp that they need to relink
- IL_EACH(g_waypoints, it != wp,
- {
- if (waypoint_islinked(it, wp))
- waypoint_removelink(it, wp);
- });
- delete(wp);
-}
-
-void waypoint_removeall()
-{
- IL_EACH(g_waypoints, true,
- {
- delete(it);
- });
-}
-
// tell all waypoints to relink
// actually this is useful only to update relink_* stats
void waypoint_schedulerelinkall()
waypoint_load_links_hardwired();
}
+#define GET_GAMETYPE_EXTENSION() ((g_race) ? ".race" : "")
+
// Load waypoint links from file
-float waypoint_load_links()
+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;
}
+ bool parse_comments = true;
+ float ver = 0;
+
while ((s = fgets(file)))
{
+ if(parse_comments)
+ {
+ if(substring(s, 0, 2) == "//")
+ {
+ if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
+ ver = stof(substring(s, 19, -1));
+ continue;
+ }
+ else
+ {
+ if(ver < WAYPOINT_VERSION)
+ {
+ 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;
+ }
+ }
+
tokens = tokenizebyseparator(s, "*");
if (tokens!=2)
{
// 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)
waypoint_addlink(wp_from, wp_to);
wp_from.wphardwired = true;
wp_to.wphardwired = true;
+ waypoint_setupmodel(wp_from);
+ waypoint_setupmodel(wp_to);
}
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");
}
-void waypoint_load_links_hardwired() { waypoint_load_or_remove_links_hardwired(false); }
-void waypoint_remove_links_hardwired() { waypoint_load_or_remove_links_hardwired(true); }
-
entity waypoint_get_link(entity w, float i)
{
switch(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)
{
return;
}
+ fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
+
int c = 0;
IL_EACH(g_waypoints, true,
{
}
});
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)
{
return;
}
+ // add 3 comments to not break compatibility with older Xonotic versions
+ // (they are read as a waypoint with origin '0 0 0' and flag 0 though)
+ fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
+ fputs(file, strcat("//", "\n"));
+ fputs(file, strcat("//", "\n"));
+
int c = 0;
IL_EACH(g_waypoints, true,
{
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);
- if (file >= 0)
+
+ if (gt_ext != "" && file < 0)
{
- while ((s = fgets(file)))
- {
- m1 = stov(s);
- s = fgets(file);
- if (!s)
- break;
- m2 = stov(s);
- s = fgets(file);
- if (!s)
- break;
- fl = stof(s);
- waypoint_spawn(m1, m2, fl);
- if (m1 == m2)
- cwp = cwp + 1;
- else
- cwb = cwb + 1;
- }
- fclose(file);
- LOG_TRACE("loaded ", ftos(cwp), " waypoints and ", ftos(cwb), " wayboxes from maps/", mapname, ".waypoints");
+ // if race waypoint file doesn't exist load the default one
+ filename = sprintf("maps/%s.waypoints", mapname);
+ file = fopen(filename, FILE_READ);
}
- else
+
+ if (file < 0)
{
LOG_TRACE("waypoint load from ", filename, " failed");
+ return 0;
}
+
+ bool parse_comments = true;
+ float ver = 0;
+
+ while ((s = fgets(file)))
+ {
+ if(parse_comments)
+ {
+ if(substring(s, 0, 2) == "//")
+ {
+ if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
+ ver = stof(substring(s, 19, -1));
+ continue;
+ }
+ 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;
+ }
+ }
+ m1 = stov(s);
+ s = fgets(file);
+ if (!s)
+ break;
+ m2 = stov(s);
+ s = fgets(file);
+ if (!s)
+ break;
+ fl = stof(s);
+ waypoint_spawn(m1, m2, fl);
+ if (m1 == m2)
+ cwp = cwp + 1;
+ else
+ cwb = cwb + 1;
+ }
+ fclose(file);
+ LOG_TRACE("loaded ", ftos(cwp), " waypoints and ", ftos(cwb), " wayboxes from maps/", mapname, ".waypoints");
+
return cwp + cwb;
}
-vector waypoint_fixorigin(vector position)
+#define waypoint_fixorigin(position, tracetest_ent) \
+ waypoint_fixorigin_down_dir(position, tracetest_ent, '0 0 -1')
+
+vector waypoint_fixorigin_down_dir(vector position, entity tracetest_ent, vector down_dir)
{
- tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + '0 0 -512', MOVE_NOMONSTERS, NULL);
+ tracebox(position + '0 0 1', PL_MIN_CONST, PL_MAX_CONST, position + down_dir * 3000, MOVE_NOMONSTERS, tracetest_ent);
+ if(trace_startsolid)
+ tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z / 2), PL_MIN_CONST, PL_MAX_CONST, position + down_dir * 3000, MOVE_NOMONSTERS, tracetest_ent);
+ if(trace_startsolid)
+ tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + down_dir * 3000, MOVE_NOMONSTERS, tracetest_ent);
if(trace_fraction < 1)
position = trace_endpos;
- //traceline(position, position + '0 0 -512', MOVE_NOMONSTERS, NULL);
- //print("position is ", ftos(trace_endpos_z - position_z), " above solid\n");
return position;
}
void waypoint_spawnforitem_force(entity e, vector org)
{
// Fix the waypoint altitude if necessary
- org = waypoint_fixorigin(org);
+ org = waypoint_fixorigin(org, NULL);
// don't spawn an item spawnfunc_waypoint if it already exists
IL_EACH(g_waypoints, true,
waypoint_spawnforitem_force(e, e.origin);
}
-void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
+void waypoint_spawnforteleporter_boxes(entity e, int teleport_flag, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
{
entity w;
entity dw;
- w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
+ w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | teleport_flag | WAYPOINTFLAG_NORELINK);
dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED);
// one way link to the destination
w.wp00 = dw;
e.nearestwaypointtimeout = -1;
}
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken)
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent)
{
- org = waypoint_fixorigin(org);
- destination = waypoint_fixorigin(destination);
- waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken);
+ // warpzones with oblique warp plane rely on down_dir to snap waypoints
+ // to the ground without leaving the warp plane
+ // warpzones with horizontal warp plane (down_dir.x == -1) generate
+ // destination waypoint snapped to the ground (leaving warpzone), source
+ // waypoint in the center of the warp plane
+ if(down_dir.x != -1)
+ org = waypoint_fixorigin_down_dir(org, tracetest_ent, down_dir);
+ if(down_dir.x == -1)
+ down_dir = '0 0 -1';
+ destination = waypoint_fixorigin_down_dir(destination, tracetest_ent, down_dir);
+ waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, org, org, destination, destination, timetaken);
}
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken)
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent)
{
- destination = waypoint_fixorigin(destination);
- waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken);
+ destination = waypoint_fixorigin(destination, tracetest_ent);
+ waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin - PL_MAX_CONST + '1 1 1', e.absmax - PL_MIN_CONST + '-1 -1 -1', destination, destination, timetaken);
}
entity waypoint_spawnpersonal(entity this, vector position)
// drop the waypoint to a proper location:
// first move it up by a player height
// then move it down to hit the floor with player bbox size
- position = waypoint_fixorigin(position);
+ position = waypoint_fixorigin(position, this);
w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
w.nearestwaypoint = NULL;
te_lightning2(NULL, wp1.origin, wp2.origin);
}
+void waypoint_showlinks_to(entity wp, int display_type)
+{
+ IL_EACH(g_waypoints, it != wp,
+ {
+ if (waypoint_islinked(it, wp))
+ waypoint_showlink(it, wp, display_type);
+ });
+}
+
+void waypoint_showlinks_from(entity wp, int display_type)
+{
+ waypoint_showlink(wp.wp00, wp, display_type); waypoint_showlink(wp.wp16, wp, display_type);
+ waypoint_showlink(wp.wp01, wp, display_type); waypoint_showlink(wp.wp17, wp, display_type);
+ waypoint_showlink(wp.wp02, wp, display_type); waypoint_showlink(wp.wp18, wp, display_type);
+ waypoint_showlink(wp.wp03, wp, display_type); waypoint_showlink(wp.wp19, wp, display_type);
+ waypoint_showlink(wp.wp04, wp, display_type); waypoint_showlink(wp.wp20, wp, display_type);
+ waypoint_showlink(wp.wp05, wp, display_type); waypoint_showlink(wp.wp21, wp, display_type);
+ waypoint_showlink(wp.wp06, wp, display_type); waypoint_showlink(wp.wp22, wp, display_type);
+ waypoint_showlink(wp.wp07, wp, display_type); waypoint_showlink(wp.wp23, wp, display_type);
+ waypoint_showlink(wp.wp08, wp, display_type); waypoint_showlink(wp.wp24, wp, display_type);
+ waypoint_showlink(wp.wp09, wp, display_type); waypoint_showlink(wp.wp25, wp, display_type);
+ waypoint_showlink(wp.wp10, wp, display_type); waypoint_showlink(wp.wp26, wp, display_type);
+ waypoint_showlink(wp.wp11, wp, display_type); waypoint_showlink(wp.wp27, wp, display_type);
+ waypoint_showlink(wp.wp12, wp, display_type); waypoint_showlink(wp.wp28, wp, display_type);
+ waypoint_showlink(wp.wp13, wp, display_type); waypoint_showlink(wp.wp29, wp, display_type);
+ waypoint_showlink(wp.wp14, wp, display_type); waypoint_showlink(wp.wp30, wp, display_type);
+ waypoint_showlink(wp.wp15, wp, display_type); waypoint_showlink(wp.wp31, wp, display_type);
+}
+
void botframe_showwaypointlinks()
{
if (time < botframe_waypointeditorlightningtime)
if (head)
{
te_lightning2(NULL, head.origin, it.origin);
- waypoint_showlink(head.wp00, head, display_type);
- waypoint_showlink(head.wp01, head, display_type);
- waypoint_showlink(head.wp02, head, display_type);
- waypoint_showlink(head.wp03, head, display_type);
- waypoint_showlink(head.wp04, head, display_type);
- waypoint_showlink(head.wp05, head, display_type);
- waypoint_showlink(head.wp06, head, display_type);
- waypoint_showlink(head.wp07, head, display_type);
- waypoint_showlink(head.wp08, head, display_type);
- waypoint_showlink(head.wp09, head, display_type);
- waypoint_showlink(head.wp10, head, display_type);
- waypoint_showlink(head.wp11, head, display_type);
- waypoint_showlink(head.wp12, head, display_type);
- waypoint_showlink(head.wp13, head, display_type);
- waypoint_showlink(head.wp14, head, display_type);
- waypoint_showlink(head.wp15, head, display_type);
- waypoint_showlink(head.wp16, head, display_type);
- waypoint_showlink(head.wp17, head, display_type);
- waypoint_showlink(head.wp18, head, display_type);
- waypoint_showlink(head.wp19, head, display_type);
- waypoint_showlink(head.wp20, head, display_type);
- waypoint_showlink(head.wp21, head, display_type);
- waypoint_showlink(head.wp22, head, display_type);
- waypoint_showlink(head.wp23, head, display_type);
- waypoint_showlink(head.wp24, head, display_type);
- waypoint_showlink(head.wp25, head, display_type);
- waypoint_showlink(head.wp26, head, display_type);
- waypoint_showlink(head.wp27, head, display_type);
- waypoint_showlink(head.wp28, head, display_type);
- waypoint_showlink(head.wp29, head, display_type);
- waypoint_showlink(head.wp30, head, display_type);
- waypoint_showlink(head.wp31, head, display_type);
+ if(PHYS_INPUT_BUTTON_CROUCH(it))
+ waypoint_showlinks_to(head, display_type);
+ else
+ waypoint_showlinks_from(head, display_type);
}
}
});
// if wp -> porg, then OK
float maxdist;
- if(navigation_waypoint_will_link(wp.origin, porg, p, walkfromwp, 1050))
+ if(navigation_waypoint_will_link(wp.origin, porg, p, porg, 0, wp.origin, 0, walkfromwp, 1050))
{
// we may find a better one
maxdist = vlen(wp.origin - porg);
{
float d = vlen(wp.origin - it.origin) + vlen(it.origin - porg);
if(d < bestdist)
- if(navigation_waypoint_will_link(wp.origin, it.origin, p, walkfromwp, 1050))
- if(navigation_waypoint_will_link(it.origin, porg, p, walkfromwp, 1050))
+ if(navigation_waypoint_will_link(wp.origin, it.origin, p, it.origin, 0, wp.origin, 0, walkfromwp, 1050))
+ if(navigation_waypoint_will_link(it.origin, porg, p, porg, 0, it.origin, 0, walkfromwp, 1050))
{
bestdist = d;
p.(fld) = it;
if(wp)
{
- if(!navigation_waypoint_will_link(wp.origin, o, p, walkfromwp, 1050))
+ if(!navigation_waypoint_will_link(wp.origin, o, p, o, 0, wp.origin, 0, walkfromwp, 1050))
{
// we cannot walk from wp.origin to o
// get closer to tmax
// if we get here, o is valid regarding waypoints
// check if o is connected right to the player
// we break if it succeeds, as that means o is a good waypoint location
- if(navigation_waypoint_will_link(o, porg, p, walkfromwp, 1050))
+ if(navigation_waypoint_will_link(o, porg, p, porg, 0, o, 0, walkfromwp, 1050))
break;
// o is no good, we need to get closer to the player
it.wpflags |= WAYPOINTFLAG_USEFUL;
if (it.wpflags & WAYPOINTFLAG_TELEPORT)
it.wpflags |= WAYPOINTFLAG_USEFUL;
+ if (it.wpflags & WAYPOINTFLAG_LADDER)
+ it.wpflags |= WAYPOINTFLAG_USEFUL;
if (it.wpflags & WAYPOINTFLAG_PROTECTED)
it.wpflags |= WAYPOINTFLAG_USEFUL;
// b) WP is closest WP for an item/spawnpoint/other entity
* Globals and Fields
*/
+// increase by 0.01 when changes require only waypoint relinking
+// increase by 1 when changes require to manually edit waypoints
+// max 2 decimal places, always specified
+#define WAYPOINT_VERSION 1.00
+
// 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;
float botframe_loadedforcedlinks;
float botframe_cachedwaypointlinks;
+// waypoint wp links to waypoint wp.wpXX (OUTGOING link)
+// links are sorted by their cost (wpXXmincost)
.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
-// itemscore = (howmuchmoreIwant / howmuchIcanwant) / itemdistance
.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
*/
spawnfunc(waypoint);
+void waypoint_removelink(entity from, entity to);
+bool waypoint_islinked(entity from, entity to);
+void waypoint_addlink_customcost(entity from, entity to, float c);
void waypoint_addlink(entity from, entity to);
void waypoint_think(entity this);
void waypoint_clearlinks(entity wp);
void waypoint_schedulerelink(entity wp);
-void waypoint_remove(entity e);
-void waypoint_removeall();
+float waypoint_getlinkcost(entity from, entity to);
+float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent);
+float waypoint_getlinearcost(float dist);
+void waypoint_updatecost_foralllinks();
+
+void waypoint_remove_fromeditor(entity pl);
+void waypoint_remove(entity wp);
void waypoint_schedulerelinkall();
-void waypoint_load_links_hardwired();
void waypoint_save_links();
void waypoint_saveall();
void waypoint_spawnforitem_force(entity e, vector org);
void waypoint_spawnforitem(entity e);
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent);
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent);
void botframe_showwaypointlinks();
float waypoint_loadall();
-float waypoint_load_links();
+bool waypoint_load_links();
+#define waypoint_load_links_hardwired() waypoint_load_or_remove_links_hardwired(false)
+#define waypoint_remove_links_hardwired() waypoint_load_or_remove_links_hardwired(true)
+void waypoint_load_or_remove_links_hardwired(bool removal_mode);
+void waypoint_spawn_fromeditor(entity pl);
entity waypoint_spawn(vector m1, vector m2, float f);
entity waypoint_spawnpersonal(entity this, vector position);
-vector waypoint_fixorigin(vector position);
+void waypoint_unreachable(entity pl);
+
+vector waypoint_fixorigin_down_dir(vector position, entity tracetest_ent, vector down_dir);
void botframe_autowaypoints();
void navigation_markroutes_inverted(entity fixed_source_waypoint) { }
void navigation_routerating(entity this, entity e, float f, float rangebias) { }
-bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) { return false; }
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode) { return false; }
-void waypoint_remove(entity e) { }
+void waypoint_remove_fromeditor(entity pl) { }
+void waypoint_remove(entity wp) { }
void waypoint_saveall() { }
void waypoint_schedulerelinkall() { }
void waypoint_schedulerelink(entity wp) { }
void waypoint_spawnforitem(entity e) { }
void waypoint_spawnforitem_force(entity e, vector org) { }
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken) { }
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken) { }
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent) { }
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent) { }
+void waypoint_spawn_fromeditor(entity pl) { }
entity waypoint_spawn(vector m1, vector m2, float f) { return NULL; }
#endif
accuracy_resend(this);
if (this.team < 0)
- JoinBestTeam(this, false, true);
+ JoinBestTeam(this, true);
entity spot = SelectSpawnPoint(this, false);
if (!spot) {
this.health = start_health;
this.armorvalue = start_armorvalue;
this.weapons = start_weapons;
- GiveRandomWeapons(this, random_start_weapons_count,
- autocvar_g_random_start_weapons, random_start_ammo);
+ if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
+ {
+ GiveRandomWeapons(this, random_start_weapons_count,
+ autocvar_g_random_start_weapons, random_start_ammo);
+ }
}
SetSpectatee_status(this, 0);
this.fire_endtime = -1;
this.revive_progress = 0;
this.revival_time = 0;
+
this.air_finished = time + 12;
+ this.waterlevel = WATERLEVEL_NONE;
+ this.watertype = CONTENT_EMPTY;
entity spawnevent = new_pure(spawnevent);
spawnevent.owner = this;
setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24));
// don't reset back to last position, even if new position is stuck in solid
this.oldorigin = this.origin;
- this.lastteleporttime = time; // prevent insane speeds due to changing origin
if(this.conveyor)
IL_REMOVE(g_conveyed, this);
this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
+ // player was spectator
if (CS(this).killcount == FRAGS_SPECTATOR) {
PlayerScore_Clear(this);
CS(this).killcount = 0;
+ CS(this).startplaytime = time;
}
for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
.float clientkill_nexttime;
void ClientKill_Now_TeamChange(entity this)
{
- if(CS(this).killindicator_teamchange == -1)
+ if(this.killindicator_teamchange == -1)
{
- JoinBestTeam( this, false, true );
+ JoinBestTeam( this, true );
}
- else if(CS(this).killindicator_teamchange == -2)
+ else if(this.killindicator_teamchange == -2)
{
if(blockSpectators)
Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
PutObserverInServer(this);
}
else
- SV_ChangeTeam(this, CS(this).killindicator_teamchange - 1);
- CS(this).killindicator_teamchange = 0;
+ SV_ChangeTeam(this, this.killindicator_teamchange - 1);
+ this.killindicator_teamchange = 0;
}
void ClientKill_Now(entity this)
if(this.vehicle)
{
vehicles_exit(this.vehicle, VHEF_RELEASE);
- if(!CS(this).killindicator_teamchange)
+ if(!this.killindicator_teamchange)
{
this.vehicle_health = -1;
Damage(this, this, this, 1 , DEATH_KILL.m_id, this.origin, '0 0 0');
this.killindicator = NULL;
- if(CS(this).killindicator_teamchange)
+ if(this.killindicator_teamchange)
ClientKill_Now_TeamChange(this);
if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
return;
killtime = M_ARGV(1, float);
- CS(this).killindicator_teamchange = targetteam;
+ this.killindicator_teamchange = targetteam;
if(!this.killindicator)
{
}
if (!teamplay && this.team_forced > 0) this.team_forced = 0;
- {
- int id = this.playerid;
- this.playerid = 0; // silent
- JoinBestTeam(this, false, false); // if the team number is valid, keep it
- this.playerid = id;
- }
+ int playerid_save = this.playerid;
+ this.playerid = 0; // silent
+ JoinBestTeam(this, false); // if the team number is valid, keep it
+ this.playerid = playerid_save;
if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) {
TRANSMUTE(Observer, this);
if(!this.team_selected)
if(autocvar_g_campaign || autocvar_g_balance_teams)
- JoinBestTeam(this, false, true);
+ JoinBestTeam(this, true);
if(autocvar_g_campaign)
campaign_bots_may_start = true;
void DrownPlayer(entity this)
{
- if(IS_DEAD(this))
+ if(IS_DEAD(this) || game_stopped || time < game_starttime)
return;
if (this.waterlevel != WATERLEVEL_SUBMERGED || this.vehicle)
ATTRIB(Client, parm_idlesince, int, this.parm_idlesince);
ATTRIB(Client, muted, bool, this.muted);
- ATTRIB(Client, killindicator_teamchange, int, this.killindicator_teamchange);
ATTRIB(Client, idlekick_lasttimeleft, float, this.idlekick_lasttimeleft);
ATTRIB(Client, pm_frametime, float, this.pm_frametime);
ATTRIB(Client, pressedkeys, int, this.pressedkeys);
ATTRIB(Client, motd_actived_time, float, this.motd_actived_time);
ATTRIB(Client, jointime, float, this.jointime);
ATTRIB(Client, spectatortime, float, this.spectatortime);
+ ATTRIB(Client, startplaytime, float, this.startplaytime);
ATTRIB(Client, version_nagtime, float, this.version_nagtime);
ATTRIB(Client, netname_previous, string, this.netname_previous);
ATTRIB(Client, allowed_timeouts, int, this.allowed_timeouts);
case "walk":
{
- if (argc == 4)
+ if (argc == 4 || argc == 5)
{
e = nextent(NULL);
- if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), MOVE_NORMAL)) LOG_INFO("can walk");
- else LOG_INFO("cannot walk");
+ if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), stof(argv(4)), MOVE_NORMAL))
+ LOG_INFO("can walk");
+ else
+ LOG_INFO("cannot walk");
return;
}
}
LOG_INFO("Incorrect parameters for ^2trace^7");
case CMD_REQUEST_USAGE:
{
- LOG_INFO("Usage:^3 sv_cmd trace command (startpos endpos)");
+ LOG_INFO("Usage:^3 sv_cmd trace command [startpos endpos] [endpos_height]");
+ LOG_INFO(" Where startpos and endpos are parameters for 'walk' and 'showline' commands,");
+ LOG_INFO(" 'endpos_height' is an optional parameter for 'walk' command,");
LOG_INFO(" Full list of commands here: \"debug, debug2, walk, showline.\"");
LOG_INFO("See also: ^2bbox, gettaginfo^7");
return;
#include <server/miscfunctions.qh>
#include <common/weapons/_all.qh>
-spawnfunc(weapon_electro);
-spawnfunc(weapon_hagar);
-spawnfunc(weapon_machinegun);
-spawnfunc(item_bullets);
-spawnfunc(item_armor_mega);
-spawnfunc(item_health_mega);
-spawnfunc(item_health_medium);
-
//***********************
//QUAKE 1 ENTITIES - So people can play quake1 maps with the xonotic weapons
//***********************
-spawnfunc(weapon_nailgun) {spawnfunc_weapon_electro(this);}
-spawnfunc(weapon_supernailgun) {spawnfunc_weapon_hagar(this);}
-spawnfunc(weapon_supershotgun) {spawnfunc_weapon_machinegun(this);}
+SPAWNFUNC_WEAPON(weapon_nailgun, WEP_ELECTRO)
+SPAWNFUNC_WEAPON(weapon_supernailgun, WEP_HAGAR)
+SPAWNFUNC_WEAPON(weapon_supershotgun, WEP_MACHINEGUN)
-spawnfunc(item_spikes) {spawnfunc_item_bullets(this);}
+SPAWNFUNC_ITEM(item_spikes, ITEM_Bullets)
//spawnfunc(item_armor1) {spawnfunc_item_armor_medium(this);} // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
-spawnfunc(item_armor2) {spawnfunc_item_armor_mega(this);}
-spawnfunc(item_armorInv) {spawnfunc_item_armor_mega(this);} // TODO: make sure we actually want this
-spawnfunc(item_health) {if (this.spawnflags & 2) spawnfunc_item_health_mega(this);else spawnfunc_item_health_medium(this);}
-
-//spawnfunc_item_spikes
-//spawnfunc_item_health
-
-
-
+SPAWNFUNC_ITEM(item_armor2, ITEM_ArmorMega)
+SPAWNFUNC_ITEM(item_armorInv, ITEM_ArmorMega) // TODO: make sure we actually want this
+spawnfunc(item_health) {if (this.spawnflags & 2) StartItem(this, ITEM_HealthMega);else StartItem(this, ITEM_HealthMedium);}
#include "quake2.qh"
-spawnfunc(item_armor_medium);
-
-spawnfunc(item_invincible);
-
+#include <common/items/_mod.qh>
//***********************
//QUAKE 2 ENTITIES - So people can play quake2 maps with the xonotic weapons
//***********************
-spawnfunc(item_armor_jacket) {spawnfunc_item_armor_medium(this);}
+SPAWNFUNC_ITEM(item_armor_jacket, ITEM_ArmorMedium)
-spawnfunc(item_invulnerability) {spawnfunc_item_invincible(this);}
+SPAWNFUNC_ITEM(item_invulnerability, ITEM_Shield)
// rest of the quake 2 entities are handled by q1 and q3 compat
#include <server/defs.qh>
#include <server/miscfunctions.qh>
+#include <server/items.qh>
#include <common/weapons/_all.qh>
-spawnfunc(weapon_crylink);
-spawnfunc(weapon_electro);
-spawnfunc(weapon_hagar);
-spawnfunc(weapon_hook);
-spawnfunc(weapon_machinegun);
-spawnfunc(weapon_vortex);
-spawnfunc(weapon_minelayer);
-
spawnfunc(target_items);
-spawnfunc(item_bullets);
-spawnfunc(item_cells);
-spawnfunc(item_rockets);
-spawnfunc(item_shells);
-
-spawnfunc(item_strength);
-
-spawnfunc(item_armor_big);
-spawnfunc(item_armor_mega);
-spawnfunc(item_armor_small);
-
-spawnfunc(item_health_medium);
-spawnfunc(item_health_mega);
-
//***********************
//QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
//***********************
// NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
// SG -> SG
-spawnfunc(ammo_shells) { spawnfunc_item_shells(this); }
+SPAWNFUNC_ITEM(ammo_shells, ITEM_Shells)
// MG -> MG
-spawnfunc(ammo_bullets) { spawnfunc_item_bullets(this); }
+SPAWNFUNC_ITEM(ammo_bullets, ITEM_Bullets)
// GL -> Mortar
-spawnfunc(ammo_grenades) { spawnfunc_item_rockets(this); }
+SPAWNFUNC_ITEM(ammo_grenades, ITEM_Rockets)
// Mines -> Rockets
-spawnfunc(weapon_prox_launcher) { spawnfunc_weapon_minelayer(this); }
-spawnfunc(ammo_mines) { spawnfunc_item_rockets(this); }
+SPAWNFUNC_WEAPON(weapon_prox_launcher, WEP_MINE_LAYER)
+SPAWNFUNC_ITEM(ammo_mines, ITEM_Rockets)
// LG -> Lightning
-spawnfunc(weapon_lightning) { spawnfunc_weapon_electro(this); }
-spawnfunc(ammo_lightning) { spawnfunc_item_cells(this); }
+SPAWNFUNC_WEAPON(weapon_lightning, WEP_ELECTRO)
+SPAWNFUNC_ITEM(ammo_lightning, ITEM_Cells)
// Plasma -> Hagar
-spawnfunc(weapon_plasmagun) { spawnfunc_weapon_hagar(this); }
-spawnfunc(ammo_cells) { spawnfunc_item_rockets(this); }
+SPAWNFUNC_WEAPON(weapon_plasmagun, WEP_HAGAR)
+SPAWNFUNC_ITEM(ammo_cells, ITEM_Rockets)
// Rail -> Vortex
-spawnfunc(weapon_railgun) { spawnfunc_weapon_vortex(this); }
-spawnfunc(ammo_slugs) { spawnfunc_item_cells(this); }
+SPAWNFUNC_WEAPON(weapon_railgun, WEP_VORTEX)
+SPAWNFUNC_ITEM(ammo_slugs, ITEM_Cells)
// BFG -> Crylink
-spawnfunc(weapon_bfg) { spawnfunc_weapon_crylink(this); }
-spawnfunc(ammo_bfg) { spawnfunc_item_cells(this); }
+SPAWNFUNC_WEAPON(weapon_bfg, WEP_CRYLINK)
+SPAWNFUNC_ITEM(ammo_bfg, ITEM_Cells)
// grappling hook -> hook
-spawnfunc(weapon_grapplinghook) { spawnfunc_weapon_hook(this); }
+SPAWNFUNC_WEAPON(weapon_grapplinghook, WEP_HOOK)
// RL -> RL
-spawnfunc(ammo_rockets) { spawnfunc_item_rockets(this); }
+SPAWNFUNC_ITEM(ammo_rockets, ITEM_Rockets)
// Armor
-spawnfunc(item_armor_body) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_armor_combat) { spawnfunc_item_armor_big(this); }
-spawnfunc(item_armor_shard) { spawnfunc_item_armor_small(this); }
-spawnfunc(item_enviro) { spawnfunc_item_invincible(this); }
+SPAWNFUNC_ITEM(item_armor_body, ITEM_ArmorMega)
+SPAWNFUNC_ITEM(item_armor_combat, ITEM_ArmorBig)
+SPAWNFUNC_ITEM(item_armor_shard, ITEM_ArmorSmall)
+SPAWNFUNC_ITEM(item_enviro, ITEM_Shield)
// medkit -> armor (we have no holdables)
-spawnfunc(holdable_medkit) { spawnfunc_item_armor_mega(this); }
+SPAWNFUNC_ITEM(holdable_medkit, ITEM_ArmorMega)
// doubler -> strength
-spawnfunc(item_doubler) { spawnfunc_item_strength(this); }
+SPAWNFUNC_ITEM(item_doubler, ITEM_Strength)
.float wait;
.float delay;
{
IL_EACH(g_items, it.targetname == this.target,
{
- if (it.classname == "weapon_rocketlauncher" || it.classname == "weapon_devastator") {
+ if (it.classname == "weapon_devastator") {
this.ammo_rockets += it.count * WEP_CVAR(devastator, ammo);
this.netname = cons(this.netname, "devastator");
}
- else if (it.classname == "weapon_lightning") {
+ else if (it.classname == "weapon_vortex") {
+ this.ammo_cells += it.count * WEP_CVAR_PRI(vortex, ammo); // WEAPONTODO
+ this.netname = cons(this.netname, "vortex");
+ }
+ else if (it.classname == "weapon_electro") {
this.ammo_cells += it.count * WEP_CVAR_PRI(electro, ammo); // WEAPONTODO
this.netname = cons(this.netname, "electro");
}
- else if (it.classname == "weapon_plasmagun") {
+ else if (it.classname == "weapon_hagar") {
this.ammo_rockets += it.count * WEP_CVAR_PRI(hagar, ammo); // WEAPONTODO
this.netname = cons(this.netname, "hagar");
}
- else if (it.classname == "weapon_bfg") {
+ else if (it.classname == "weapon_crylink") {
this.ammo_cells += it.count * WEP_CVAR_PRI(crylink, ammo);
this.netname = cons(this.netname, "crylink");
}
- else if (it.classname == "weapon_grenadelauncher" || it.classname == "weapon_mortar") {
+ else if (it.classname == "weapon_mortar") {
this.ammo_rockets += it.count * WEP_CVAR_PRI(mortar, ammo); // WEAPONTODO
this.netname = cons(this.netname, "mortar");
}
- else if (it.classname == "item_armor_body")
+ else if (it.classname == "item_armor_mega")
this.armorvalue = 100;
else if (it.classname == "item_health_mega")
this.health = 200;
#include <server/defs.qh>
#include <server/miscfunctions.qh>
+#include <server/items.qh>
#include <common/weapons/_all.qh>
-// #include <server/mutators/gamemode.qh>
-
-spawnfunc(weapon_arc);
-spawnfunc(weapon_crylink);
-spawnfunc(weapon_electro);
-spawnfunc(weapon_mortar);
-spawnfunc(weapon_hagar);
-spawnfunc(weapon_machinegun);
-spawnfunc(weapon_devastator);
-spawnfunc(weapon_shotgun);
-spawnfunc(weapon_vortex);
-
-spawnfunc(item_armor_big);
-spawnfunc(item_armor_mega);
-spawnfunc(item_armor_small);
-
-spawnfunc(item_bullets);
-spawnfunc(item_cells);
-spawnfunc(item_quad);
-spawnfunc(item_rockets);
-spawnfunc(item_shells);
-
-spawnfunc(item_jetpack);
spawnfunc(item_haste);
-spawnfunc(item_health_medium);
-spawnfunc(item_health_mega);
spawnfunc(item_invis);
//***********************
-//WORD OF PADMAN ENTITIES - So people can play wop maps with the xonotic weapons
+//WORLD OF PADMAN ENTITIES - So people can play wop maps with the xonotic weapons
//***********************
//spawnfunc(item_revival) /* handled by buffs mutator */
//spawnfunc(item_jumper) /* handled by buffs mutator */
-spawnfunc(weapon_punchy) { spawnfunc_weapon_arc(this); }
-spawnfunc(weapon_nipper) { spawnfunc_weapon_machinegun(this); }
-spawnfunc(weapon_pumper) { spawnfunc_weapon_shotgun(this); }
-spawnfunc(weapon_boaster) { spawnfunc_weapon_electro(this); }
-spawnfunc(weapon_splasher) { spawnfunc_weapon_vortex(this); }
-spawnfunc(weapon_bubbleg) { spawnfunc_weapon_hagar(this); }
-spawnfunc(weapon_balloony) { spawnfunc_weapon_mortar(this); }
-spawnfunc(weapon_betty) { spawnfunc_weapon_devastator(this); }
-spawnfunc(weapon_imperius) { spawnfunc_weapon_crylink(this); }
-
-spawnfunc(ammo_pumper) { spawnfunc_item_shells(this); }
-spawnfunc(ammo_nipper) { spawnfunc_item_bullets(this); }
-spawnfunc(ammo_balloony) { spawnfunc_item_rockets(this); }
-spawnfunc(ammo_bubbleg) { spawnfunc_item_rockets(this); }
-spawnfunc(ammo_boaster) { spawnfunc_item_cells(this); }
-spawnfunc(ammo_betty) { spawnfunc_item_rockets(this); }
-spawnfunc(ammo_imperius) { spawnfunc_item_cells(this); }
-
-spawnfunc(item_padpower) { spawnfunc_item_quad(this); }
-spawnfunc(item_climber) { spawnfunc_item_invincible(this); }
+SPAWNFUNC_WEAPON(weapon_punchy, WEP_ARC)
+SPAWNFUNC_WEAPON(weapon_nipper, WEP_MACHINEGUN)
+SPAWNFUNC_WEAPON(weapon_pumper, WEP_SHOTGUN)
+SPAWNFUNC_WEAPON(weapon_boaster, WEP_ELECTRO)
+SPAWNFUNC_WEAPON(weapon_splasher, WEP_VORTEX)
+SPAWNFUNC_WEAPON(weapon_bubbleg, WEP_HAGAR)
+SPAWNFUNC_WEAPON(weapon_balloony, WEP_MORTAR)
+SPAWNFUNC_WEAPON(weapon_betty, WEP_DEVASTATOR)
+SPAWNFUNC_WEAPON(weapon_imperius, WEP_CRYLINK)
+
+SPAWNFUNC_ITEM(ammo_pumper, ITEM_Shells)
+SPAWNFUNC_ITEM(ammo_nipper, ITEM_Bullets)
+SPAWNFUNC_ITEM(ammo_balloony, ITEM_Rockets)
+SPAWNFUNC_ITEM(ammo_bubbleg, ITEM_Rockets)
+SPAWNFUNC_ITEM(ammo_boaster, ITEM_Cells)
+SPAWNFUNC_ITEM(ammo_betty, ITEM_Rockets)
+SPAWNFUNC_ITEM(ammo_imperius, ITEM_Cells)
+
+SPAWNFUNC_ITEM(item_padpower, ITEM_Strength)
+SPAWNFUNC_ITEM(item_climber, ITEM_Shield)
spawnfunc(item_speedy) { spawnfunc_item_haste(this); }
spawnfunc(item_visionless) { spawnfunc_item_invis(this); }
-spawnfunc(item_armor_padshield) { spawnfunc_item_armor_mega(this); }
+SPAWNFUNC_ITEM(item_armor_padshield, ITEM_ArmorMega)
-spawnfunc(holdable_floater) { spawnfunc_item_jetpack(this); }
+SPAWNFUNC_ITEM(holdable_floater, ITEM_Jetpack)
#pragma once
-float warmup_limit;
#include <common/weapons/_all.qh>
#include <common/stats.qh>
float game_completion_ratio; // 0 at start, 1 near end
.float winning;
-.float jointime; // time of joining
+.float jointime; // time of connecting
+.float startplaytime; // time of switching from spectator to player
.float alivetime; // time of being alive
.float motd_actived_time; // used for both motd and campaign_message
// set when showing a kill countdown
.entity killindicator;
+.bool canteamdamage;
+
void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
float lockteams;
.float stat_respawn_time = _STAT(RESPAWN_TIME); // shows respawn time, and is negative when awaiting respawn
+.int killindicator_teamchange;
+
void PlayerUseKey(entity this);
USING(spawn_evalfunc_t, vector(entity this, entity player, entity spot, vector current));
else
{
// teamkill
- GameRules_scoring_add(attacker, KILLS, -1); // or maybe add a teamkills field?
+ GameRules_scoring_add(attacker, TEAMKILLS, 1);
}
}
else
{
// regular frag
GameRules_scoring_add(attacker, KILLS, 1);
- if(targ.playerid)
+ if(!warmup_stage && targ.playerid)
PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
}
attacker.killsound += 1;
+ // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
+ // these 2 macros are spread over multiple files
#define SPREE_ITEM(counta,countb,center,normal,gentle) \
case counta: \
{ \
Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
- PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
+ if (!warmup_stage)\
+ {\
+ PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
+ }\
break; \
}
switch(CS(attacker).killcount)
}
#undef SPREE_ITEM
- if(!checkrules_firstblood)
+ if(!warmup_stage && !checkrules_firstblood)
{
checkrules_firstblood = true;
notif_firstblood = true; // modify the current messages so that they too show firstblood information
if(GameRules_scoring_add(targ, SCORE, 0) == -5)
{
Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
- PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+ if (!warmup_stage)
+ {
+ PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+ }
}
}
force = '0 0 0';
}
}
- else
+ else if(!targ.canteamdamage)
damage = 0;
}
}
source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
if (lag)
- {
- // take players back into the past
- FOREACH_CLIENT(IS_PLAYER(it) && it != forent, antilag_takeback(it, CS(it), time - lag));
- IL_EACH(g_monsters, it != forent,
- {
- antilag_takeback(it, it, time - lag);
- });
- }
+ antilag_takeback_all(forent, lag);
// do the trace
if(wz)
// restore players to current positions
if (lag)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && it != forent, antilag_restore(it, CS(it)));
- IL_EACH(g_monsters, it != forent,
- {
- antilag_restore(it, it);
- });
- }
+ antilag_restore_all(forent);
// restore shooter solid type
if(source)
{
antilag_record(it, it, altime);
});
+ IL_EACH(g_projectiles, it.classname == "nade",
+ {
+ antilag_record(it, it, altime);
+ });
systems_update();
IL_ENDFRAME();
}
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;
}
}
sprint(this, "all waypoints cleared\n");
}
-vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
-{
- vector new_org = org;
- if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
- {
- vector map_center = havocbot_middlepoint;
- if (autocvar_g_waypointeditor_symmetrical == -1)
- map_center = autocvar_g_waypointeditor_symmetrical_origin;
-
- new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
- }
- else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
- {
- float m = havocbot_symmetryaxis_equation.x;
- float q = havocbot_symmetryaxis_equation.y;
- if (autocvar_g_waypointeditor_symmetrical == -2)
- {
- m = autocvar_g_waypointeditor_symmetrical_axis.x;
- q = autocvar_g_waypointeditor_symmetrical_axis.y;
- }
-
- new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
- new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
- }
- new_org.z = org.z;
- return new_org;
-}
-
IMPULSE(navwaypoint_spawn)
{
if (!autocvar_g_waypointeditor) return;
- entity e;
- vector org = this.origin;
- int ctf_flags = havocbot_symmetryaxis_equation.z;
- bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
- || (autocvar_g_waypointeditor_symmetrical < 0));
- int order = ctf_flags;
- if(autocvar_g_waypointeditor_symmetrical_order >= 2)
- {
- order = autocvar_g_waypointeditor_symmetrical_order;
- ctf_flags = order;
- }
-
- LABEL(add_wp);
- e = waypoint_spawn(org, org, 0);
- waypoint_schedulerelink(e);
- bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
- if(sym)
- {
- org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
- if (vdist(org - this.origin, >, 32))
- {
- if(order > 2)
- order--;
- else
- sym = false;
- goto add_wp;
- }
- }
+ waypoint_spawn_fromeditor(this);
}
IMPULSE(navwaypoint_remove)
{
if (!autocvar_g_waypointeditor) return;
- entity e = navigation_findnearestwaypoint(this, false);
- int ctf_flags = havocbot_symmetryaxis_equation.z;
- bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
- || (autocvar_g_waypointeditor_symmetrical < 0));
- int order = ctf_flags;
- if(autocvar_g_waypointeditor_symmetrical_order >= 2)
- {
- order = autocvar_g_waypointeditor_symmetrical_order;
- ctf_flags = order;
- }
-
- LABEL(remove_wp);
- if (!e) return;
- if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
-
- if (e.wphardwired)
- {
- LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired");
- return;
- }
-
- entity wp_sym = NULL;
- if (sym)
- {
- vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
- IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), {
- if(vdist(org - it.origin, <, 3))
- {
- wp_sym = it;
- break;
- }
- });
- }
- bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
- waypoint_remove(e);
- if (sym && wp_sym)
- {
- e = wp_sym;
- if(order > 2)
- order--;
- else
- sym = false;
- goto remove_wp;
- }
+ waypoint_remove_fromeditor(this);
}
IMPULSE(navwaypoint_relink)
IMPULSE(navwaypoint_unreachable)
{
if (!autocvar_g_waypointeditor) return;
- IL_EACH(g_waypoints, true,
- {
- it.colormod = '0.5 0.5 0.5';
- it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
- });
- entity e2 = navigation_findnearestwaypoint(this, false);
- navigation_markroutes(this, e2);
-
- int j, m;
-
- j = 0;
- m = 0;
- IL_EACH(g_waypoints, it.wpcost >= 10000000,
- {
- LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin));
- it.colormod_z = 8;
- it.effects |= EF_NODEPTHTEST | EF_BLUE;
- ++j;
- ++m;
- });
- if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)", j);
- navigation_markroutes_inverted(e2);
-
- j = 0;
- IL_EACH(g_waypoints, it.wpcost >= 10000000,
- {
- LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin));
- it.colormod_x = 8;
- if (!(it.effects & EF_NODEPTHTEST)) // not already reported before
- ++m;
- it.effects |= EF_NODEPTHTEST | EF_RED;
- ++j;
- });
- if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)", j);
- if (m) LOG_INFOF("%d waypoints have been marked total", m);
-
- j = 0;
- IL_EACH(g_spawnpoints, true,
- {
- vector org = it.origin;
- tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
- setorigin(it, trace_endpos);
- if (navigation_findnearestwaypoint(it, false))
- {
- setorigin(it, org);
- it.effects &= ~EF_NODEPTHTEST;
- it.model = "";
- }
- else
- {
- setorigin(it, org);
- LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin));
- it.effects |= EF_NODEPTHTEST;
- _setmodel(it, this.model);
- it.frame = this.frame;
- it.skin = this.skin;
- it.colormod = '8 0.5 8';
- setsize(it, '0 0 0', '0 0 0');
- ++j;
- }
- });
- if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)", j);
-
- j = 0;
- IL_EACH(g_items, true,
- {
- it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
- it.colormod = '0.5 0.5 0.5';
- });
- IL_EACH(g_items, true,
- {
- if (navigation_findnearestwaypoint(it, false)) continue;
- LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
- it.effects |= EF_NODEPTHTEST | EF_RED;
- it.colormod_x = 8;
- ++j;
- });
- if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)", j);
-
- j = 0;
- IL_EACH(g_items, true,
- {
- if (navigation_findnearestwaypoint(it, true)) continue;
- LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
- it.effects |= EF_NODEPTHTEST | EF_BLUE;
- it.colormod_z = 8;
- ++j;
- });
- if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)", j);
+ waypoint_unreachable(this);
}
--- /dev/null
+#include "items.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the functions related to
+/// game items.
+/// \copyright GNU GPLv2 or any later version.
+
+#include "g_subs.qh"
+#include <common/weapons/all.qh>
+
+.bool m_isloot; ///< Holds whether item is loot.
+/// \brief Holds whether strength, shield or superweapon timers expire while
+/// this item is on the ground.
+.bool m_isexpiring;
+
+entity Item_Create(string class_name, vector position, bool no_align)
+{
+ entity item = spawn();
+ item.classname = class_name;
+ item.spawnfunc_checked = true;
+ setorigin(item, position);
+ item.noalign = no_align;
+ Item_Initialize(item, class_name);
+ if (wasfreed(item))
+ {
+ return NULL;
+ }
+ return item;
+}
+
+void Item_Initialize(entity item, string class_name)
+{
+ FOREACH(Weapons, it.m_canonical_spawnfunc == class_name,
+ {
+ weapon_defaultspawnfunc(item, it);
+ return;
+ });
+ FOREACH(Items, it.m_canonical_spawnfunc == class_name,
+ {
+ StartItem(item, it);
+ return;
+ });
+ LOG_FATALF("Item_Initialize: Invalid classname: %s", class_name);
+}
+
+entity Item_CreateLoot(string class_name, vector position, vector vel,
+ float time_to_live)
+{
+ entity item = spawn();
+ if (!Item_InitializeLoot(item, class_name, position, vel, time_to_live))
+ {
+ return NULL;
+ }
+ return item;
+}
+
+bool Item_InitializeLoot(entity item, string class_name, vector position,
+ vector vel, float time_to_live)
+{
+ item.classname = class_name;
+ Item_SetLoot(item, true);
+ item.noalign = true;
+ setorigin(item, position);
+ item.pickup_anyway = true;
+ item.spawnfunc_checked = true;
+ Item_Initialize(item, class_name);
+ if (wasfreed(item))
+ {
+ return false;
+ }
+ item.gravity = 1;
+ item.velocity = vel;
+ SUB_SetFade(item, time + time_to_live, 1);
+ return true;
+}
+
+bool Item_IsLoot(entity item)
+{
+ return item.m_isloot;
+}
+
+void Item_SetLoot(entity item, bool loot)
+{
+ item.m_isloot = loot;
+}
+
+bool Item_ShouldKeepPosition(entity item)
+{
+ return item.noalign || (item.spawnflags & 1);
+}
+
+bool Item_IsExpiring(entity item)
+{
+ return item.m_isexpiring;
+}
+
+void Item_SetExpiring(entity item, bool expiring)
+{
+ item.m_isexpiring = expiring;
+}
+
+// Compatibility spawn functions
+
+// FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
+SPAWNFUNC_ITEM(item_armor1, ITEM_ArmorSmall)
+
+SPAWNFUNC_ITEM(item_armor25, ITEM_ArmorMega)
+
+SPAWNFUNC_ITEM(item_armor_large, ITEM_ArmorMega)
+
+SPAWNFUNC_ITEM(item_health1, ITEM_HealthSmall)
+
+SPAWNFUNC_ITEM(item_health25, ITEM_HealthMedium)
+
+SPAWNFUNC_ITEM(item_health_large, ITEM_HealthBig)
+
+SPAWNFUNC_ITEM(item_health100, ITEM_HealthMega)
+
+SPAWNFUNC_ITEM(item_quad, ITEM_Strength)
--- /dev/null
+#pragma once
+
+/// \file
+/// \brief Header file that describes the functions related to game items.
+/// \copyright GNU GPLv2 or any later version.
+
+/// \brief Creates a new item.
+/// \param[in] class_name Class name of the item.
+/// \param[in] position Position of the item.
+/// \param[in] no_align True if item should be placed directly at specified
+/// position, false to let it drop to the ground.
+/// \return Item on success, NULL otherwise.
+entity Item_Create(string class_name, vector position, bool no_align);
+
+/// \brief Initializes the item according to classname.
+/// \param[in,out] item Item to initialize.
+/// \param[in] class_name Class name to use.
+/// \return No return.
+/// \nore This function is useful if you want to set some item properties before
+/// initialization.
+void Item_Initialize(entity item, string class_name);
+
+/// \brief Creates a loot item.
+/// \param[in] class_name Class name of the item.
+/// \param[in] position Position of the item.
+/// \param[in] velocity of the item.
+/// \param[in] time_to_live Amount of time after which the item will disappear.
+/// \return Item on success, NULL otherwise.
+entity Item_CreateLoot(string class_name, vector position, vector vel,
+ float time_to_live);
+
+/// \brief Initializes the loot item.
+/// \param[in] class_name Class name of the item.
+/// \param[in] position Position of the item.
+/// \param[in] velocity of the item.
+/// \param[in] time_to_live Amount of time after which the item will disappear.
+/// \return True on success, false otherwise.
+/// \nore This function is useful if you want to set some item properties before
+/// initialization.
+bool Item_InitializeLoot(entity item, string class_name, vector position,
+ vector vel, float time_to_live);
+
+/// \brief Returns whether the item is loot.
+/// \param[in] item Item to check.
+/// \return True if the item is loot, false otherwise.
+bool Item_IsLoot(entity item);
+
+/// \brief Sets the item loot status.
+/// \param[in,out] item Item to adjust.
+/// \param[in] loot Whether item is loot.
+/// \return No return.
+void Item_SetLoot(entity item, bool loot);
+
+/// \brief Returns whether item should keep its position or be dropped to the
+/// ground.
+/// \param[in] item Item to check.
+/// \return True if item should keep its position or false if it should be
+/// dropped to the ground.
+bool Item_ShouldKeepPosition(entity item);
+
+/// \brief Returns whether the item is expiring (i.e. its strength, shield and
+/// superweapon timers expire while it is on the ground).
+/// \param[in] item Item to check.
+/// \return True if the item is expiring, false otherwise.
+bool Item_IsExpiring(entity item);
+
+/// \brief Sets the item expiring status (i.e. whether its strength, shield
+/// and superweapon timers expire while it is on the ground).
+/// \param[in,out] item Item to adjust.
+/// \param[in] expiring Whether item is expiring.
+/// \return No return.
+void Item_SetExpiring(entity item, bool expiring);
#include "mutators/_mod.qh"
#include "../common/t_items.qh"
#include "resources.qh"
+#include "items.qh"
#include "weapons/accuracy.qh"
#include "weapons/csqcprojectile.qh"
#include "weapons/selection.qh"
"g_random_start_shells"));
SetResourceAmount(random_start_ammo, RESOURCE_BULLETS, cvar(
"g_random_start_bullets"));
- SetResourceAmount(random_start_ammo, RESOURCE_ROCKETS,
+ SetResourceAmount(random_start_ammo, RESOURCE_ROCKETS,
cvar("g_random_start_rockets"));
SetResourceAmount(random_start_ammo, RESOURCE_CELLS, cvar(
"g_random_start_cells"));
});
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)
return false;
if(e.iscreature)
return true;
+ if (Item_IsLoot(e))
+ {
+ return true;
+ }
switch(e.classname)
{
case "body":
- case "droppedweapon":
return true;
case "bullet": // antilagged bullets can't hit this either
return false;
/**/
MUTATOR_HOOKABLE(ForbidSpawn, EV_ForbidSpawn);
+/** called when player spawns to determine whether to give them random start weapons. Return true to forbid giving them. */
+#define EV_ForbidRandomStartWeapons(i, o) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /**/
+MUTATOR_HOOKABLE(ForbidRandomStartWeapons, EV_ForbidRandomStartWeapons);
+
/** called when a player spawns as player, after shared setup, before his weapon is chosen (so items may be changed in here) */
#define EV_PlayerSpawn(i, o) \
/** player spawning */ i(entity, MUTATOR_ARGV_0_entity) \
MUT_ITEMTOUCH_PICKUP // return this flag to have the item "picked up" and taken even after mutator handled it
};
+/** called after the item has been touched. */
+#define EV_ItemTouched(i, o) \
+ /** item */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** toucher */ i(entity, MUTATOR_ARGV_1_entity) \
+ /**/
+MUTATOR_HOOKABLE(ItemTouched, EV_ItemTouched);
+
/** Called when the amount of entity resources changes. Can be used to override
resource limit. */
#define EV_GetResourceLimit(i, o) \
if(this.havocbot_attack_time>time)
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
navigation_goalrating_start(this);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
havocbot_goalrating_items(this, 15000, this.origin, 10000);
navigation_goalrating_end(this);
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_timeout_set(this);
}
}
if(this.havocbot_attack_time>time)
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
navigation_goalrating_start(this);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
havocbot_goalrating_items(this, 15000, this.origin, 10000);
navigation_goalrating_end(this);
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_timeout_set(this);
}
}
.int havocbot_role_flags;
.float havocbot_attack_time;
-.void(entity this) havocbot_role;
-.void(entity this) havocbot_previous_role;
-
void(entity this) havocbot_role_ast_defense;
void(entity this) havocbot_role_ast_offense;
ca_LastPlayerForTeam_Notify(frag_target);
if (!allowed_to_spawn)
- frag_target.respawn_flags = RESPAWN_SILENT;
+ {
+ frag_target.respawn_flags = RESPAWN_SILENT;
+ // prevent unwanted sudden rejoin as spectator and move of spectator camera
+ frag_target.respawn_time = time + 2;
+ }
if (!warmup_stage)
eliminatedPlayers.SendFlags |= 1;
if(IS_BOT_CLIENT(frag_target))
if (!IS_DEAD(player))
ca_LastPlayerForTeam_Notify(player);
- if (CS(player).killindicator_teamchange == -2) // player wants to spectate
+ if (player.killindicator_teamchange == -2) // player wants to spectate
player.caplayer = 0;
if (player.caplayer)
player.frags = FRAGS_LMS_LOSER;
if(CTF_DIFFTEAM(player, flag)) { return; }
if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
+ if (toucher.goalentity == flag.bot_basewaypoint)
+ toucher.goalentity_lock_timeout = 0;
+
if(ctf_oneflag)
for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
if(SAME_TEAM(tmp_entity, player))
{ GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
}
+ flag.enemy = toucher;
+
// reset the flag
player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
ctf_RespawnFlag(enemy_flag);
if(player.flagcarried == flag)
WaypointSprite_Kill(player.wps_flagcarrier);
+ flag.enemy = player;
+
// reset the flag
ctf_RespawnFlag(flag);
}
}
_sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
ctf_EventLog("returned", flag.team, NULL);
+ flag.enemy = NULL;
ctf_RespawnFlag(flag);
}
}
void ctf_Reset(entity this)
{
if(this.owner && IS_PLAYER(this.owner))
- ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+ ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+ this.enemy = NULL;
ctf_RespawnFlag(this);
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
navigation_goalrating_start(this);
+
if(ctf_oneflag)
havocbot_goalrating_ctf_enemybase(this, 50000);
else
navigation_goalrating_end(this);
+ navigation_goalrating_timeout_set(this);
+
+ entity head = ctf_worldflaglist;
+ while (head)
+ {
+ if (this.goalentity == head.bot_basewaypoint)
+ {
+ this.goalentity_lock_timeout = time + 5;
+ break;
+ }
+ head = head.ctf_worldflagnext;
+ }
+
if (this.goalentity)
this.havocbot_cantfindflag = time + 10;
else if (time > this.havocbot_cantfindflag)
}
// Chase the flag carrier
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
+
havocbot_goalrating_ctf_enemyflag(this, 30000);
havocbot_goalrating_ctf_ourstolenflag(this, 40000);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
+
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
+
havocbot_goalrating_ctf_ourstolenflag(this, 50000);
havocbot_goalrating_ctf_enemybase(this, 20000);
havocbot_goalrating_items(this, 5000, this.origin, 1000);
havocbot_goalrating_items(this, 1000, this.origin, 10000);
+
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
mf = havocbot_ctf_find_flag(this);
if(mf.ctf_status==FLAG_BASE)
{
- if(this.goalcurrent == mf)
- {
- navigation_clearroute(this);
- this.bot_strategytime = 0;
- }
+ if (mf.enemy == this) // did this bot return the flag?
+ navigation_goalrating_timeout_force(this);
havocbot_ctf_reset_role(this);
return;
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
float rt_radius;
rt_radius = 10000;
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
+
havocbot_goalrating_ctf_ourstolenflag(this, 50000);
havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
havocbot_goalrating_ctf_enemybase(this, 30000);
havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
vector org;
org = havocbot_middlepoint;
org.z = this.origin.z;
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
+
havocbot_goalrating_ctf_ourstolenflag(this, 50000);
havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
havocbot_goalrating_items(this, 2500, this.origin, 10000);
havocbot_goalrating_ctf_enemybase(this, 2500);
+
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
havocbot_ctf_reset_role(this);
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
vector org = mf.dropped_origin;
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
// if enemies are closer to our base, go there
havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
havocbot_goalrating_items(this, 5000, this.origin, 10000);
+
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
bot.havocbot_role = havocbot_role_ctf_carrier;
bot.havocbot_role_timeout = 0;
bot.havocbot_cantfindflag = time + 10;
- bot.bot_strategytime = 0;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_force(bot);
break;
case HAVOCBOT_CTF_ROLE_DEFENSE:
s = "defense";
bot.havocbot_previous_role = bot.havocbot_role;
bot.havocbot_role = havocbot_role_ctf_retriever;
bot.havocbot_role_timeout = time + 10;
- bot.bot_strategytime = 0;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_expire(bot, 2);
break;
case HAVOCBOT_CTF_ROLE_ESCORT:
s = "escort";
bot.havocbot_previous_role = bot.havocbot_role;
bot.havocbot_role = havocbot_role_ctf_escort;
bot.havocbot_role_timeout = time + 30;
- bot.bot_strategytime = 0;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_expire(bot, 2);
break;
}
LOG_TRACE(bot.netname, " switched to ", s);
.float next_take_time;
.bool ctf_flagdamaged_byworld;
int ctf_teams;
+.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
// passing/throwing properties
.float pass_distance;
#include "gamemode_cts.qh"
-#include <server/race.qh>
#include <server/race.qh>
+#include <server/items.qh>
float autocvar_g_cts_finish_kill_delay;
bool autocvar_g_cts_selfdamage;
if(IS_DEAD(this))
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
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_timeout_set(this);
}
}
{
entity item = M_ARGV(0, entity);
- if(item.classname == "droppedweapon")
+ if (Item_IsLoot(item))
+ {
return true;
+ }
}
MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
if(IS_DEAD(this))
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
havocbot_goalrating_items(this, 8000, this.origin, 8000);
//havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (time > this.bot_strategytime)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (time > this.bot_strategytime)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 8000, this.origin, 10000);
havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
if (IS_DEAD(this))
return;
- if (time > this.bot_strategytime)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
if (!this.ballcarried)
{
this.havocbot_role = havocbot_role_ka_collector;
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_expire(this, 2);
}
}
if (IS_DEAD(this))
return;
- if (time > this.bot_strategytime)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
havocbot_goalrating_ball(this, 20000, this.origin);
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
if (this.ballcarried)
{
this.havocbot_role = havocbot_role_ka_carrier;
- this.bot_strategytime = 0;
+ navigation_goalrating_timeout_expire(this, 2);
}
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
if(kh_Key_AllOwnedByWhichTeam() == this.team)
havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
float key_owner_team;
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
key_owner_team = kh_Key_AllOwnedByWhichTeam();
havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
float key_owner_team;
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
key_owner_team = kh_Key_AllOwnedByWhichTeam();
havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
return;
}
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
navigation_goalrating_start(this);
int key_owner_team = kh_Key_AllOwnedByWhichTeam();
havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
#include "gamemode_lms.qh"
-#include <common/mutators/mutator/instagib/items.qc>
+#include <common/mutators/mutator/instagib/items.qh>
#include <server/campaign.qh>
#include <server/command/_mod.qh>
// limit.
int WinningCondition_LMS()
{
- entity head, head2;
- bool have_player = false;
- bool have_players = false;
-
- int l = LMS_NewPlayerLives();
-
- head = find(NULL, classname, STR_PLAYER);
- if(head)
- have_player = true;
- head2 = find(head, classname, STR_PLAYER);
- if(head2)
- have_players = true;
+ entity first_player = NULL;
+ int total_players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if (!total_players)
+ first_player = it;
+ ++total_players;
+ });
- if(have_player)
+ if (total_players)
{
- // we have at least one player
- if(have_players)
+ if (total_players > 1)
{
// two or more active players - continue with the game
+
+ if (autocvar_g_campaign)
+ {
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
+ if (!pl_lives)
+ return WINNING_YES; // human player lost, game over
+ break;
+ });
+ }
}
else
{
ClearWinners();
SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
- if(l)
+ if (LMS_NewPlayerLives())
{
// game still running (that is, nobody got removed from the game by a frag yet)? then continue
return WINNING_NO;
{
// a winner!
// and assign him his first place
- GameRules_scoring_add(head, LMS_RANK, 1);
+ GameRules_scoring_add(first_player, LMS_RANK, 1);
if(warmup_stage)
return WINNING_NO;
else
else
{
// nobody is playing at all...
- if(l)
+ if (LMS_NewPlayerLives())
{
// wait for players...
}
if(IS_DEAD(this))
return;
- if (this.bot_strategytime < time)
+ if (navigation_goalrating_timeout(this))
{
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
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);
}
});
navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
}
}
{
delete(this.killindicator);
this.killindicator = NULL;
- if(CS(this).killindicator_teamchange)
+ if(this.killindicator_teamchange)
defer_ClientKill_Now_TeamChange = true;
if(this.classname == "body")
if(w != WEP_Null && accuracy_isgooddamage(attacker, this))
CS(attacker).accuracy.(accuracy_frags[w.m_id-1]) += 1;
+ this.respawn_time = 0;
MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage);
damage = M_ARGV(4, float);
excess = max(0, damage - take - save);
if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
return;
+ if (!this.respawn_time) // can be set in the mutator hook PlayerDies
+ calculate_player_respawn_time(this);
+
// when we get here, player actually dies
Unfreeze(this); // remove any icy remains
STAT(MOVEVARS_SPECIALCOMMAND, this) = false; // sweet release
- // when to allow respawn
- calculate_player_respawn_time(this);
-
this.death_time = time;
if (random() < 0.5)
animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD1, true);
PlayerStats_GameReport_AddTeam(t);
}
-float TeamScore_AddToTeam(float t, float scorefield, float score)
+float TeamScore_AddToTeam(int t, float scorefield, float score)
{
entity s;
* NEVER call this if team has not been set yet!
* Returns the new score.
*/
-float TeamScore_AddToTeam(float t, float scorefield, float score);
+float TeamScore_AddToTeam(int t, float scorefield, float score);
/**
* Returns a value indicating the team score (and higher is better).
ScoreInfo_SetLabel_PlayerScore(SP_DEATHS, "deaths", SFL_LOWER_IS_BETTER);
if (!INDEPENDENT_PLAYERS)
+ {
ScoreInfo_SetLabel_PlayerScore(SP_SUICIDES, "suicides", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TEAMKILLS, "teamkills", SFL_LOWER_IS_BETTER);
+ }
if(score_enabled)
ScoreInfo_SetLabel_PlayerScore(SP_SCORE, "score", sprio);
void CreatureFrame_All()
{
+ if(game_stopped || time < game_starttime)
+ return;
+
IL_EACH(g_damagedbycontents, it.damagedbycontents,
{
if (it.move_movetype == MOVETYPE_NOCLIP) continue;
if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE)
orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout
- skill = autocvar_skill;
-
// detect when the pre-game countdown (if any) has ended and the game has started
bool game_delay = (time < game_starttime);
if (autocvar_sv_eventlog && game_delay_last && !game_delay)
void WarpZone_PostInitialize_Callback()
{
// create waypoint links for warpzones
+ entity tracetest_ent = spawn();
+ setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+ tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
//for(entity e = warpzone_first; e; e = e.warpzone_next)
for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); )
{
dst = (e.enemy.absmin + e.enemy.absmax) * 0.5;
makevectors(e.enemy.warpzone_angles);
dst = dst + ((e.enemy.warpzone_origin - dst) * v_forward) * v_forward - 16 * v_right;
- waypoint_spawnforteleporter_v(e, src, dst, 0);
+ waypoint_spawnforteleporter_wz(e, src, dst, 0, -v_up, tracetest_ent);
}
+ delete(tracetest_ent);
}
bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
bool use_score)
{
+ if (!Team_IsValidNumber(team_a))
+ {
+ LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a);
+ }
+ if (!Team_IsValidNumber(team_b))
+ {
+ LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b);
+ }
if (team_a == team_b)
{
return false;
bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
{
+ if (!Team_IsValidNumber(team_a))
+ {
+ LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a);
+ }
+ if (!Team_IsValidNumber(team_b))
+ {
+ LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b);
+ }
if (team_a == team_b)
{
return true;
return RandomSelection_chosen_float;
}
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
+void JoinBestTeam(entity this, bool force_best_team)
{
// don't join a team if we're not playing a team game
if (!teamplay)
{
- return 0;
+ return;
}
// find out what teams are available
CheckAllowedTeams(this);
- // if we don't care what team he ends up on, put him on whatever team he entered as.
- // if he's not on a valid team, then let other code put him on the smallest team
+ // if we don't care what team they end up on, put them on whatever team they entered as.
+ // if they're not on a valid team, then let other code put them on the smallest team
if (!force_best_team)
{
int selected_team;
- if( c1 >= 0 && this.team == NUM_TEAM_1)
+ if ((c1 >= 0) && (this.team == NUM_TEAM_1))
+ {
selected_team = this.team;
- else if(c2 >= 0 && this.team == NUM_TEAM_2)
+ }
+ else if ((c2 >= 0) && (this.team == NUM_TEAM_2))
+ {
selected_team = this.team;
- else if(c3 >= 0 && this.team == NUM_TEAM_3)
+ }
+ else if ((c3 >= 0) && (this.team == NUM_TEAM_3))
+ {
selected_team = this.team;
- else if(c4 >= 0 && this.team == NUM_TEAM_4)
+ }
+ else if ((c4 >= 0) && (this.team == NUM_TEAM_4))
+ {
selected_team = this.team;
+ }
else
+ {
selected_team = -1;
+ }
if (selected_team > 0)
{
- if (!only_return_best)
- {
- SetPlayerTeamSimple(this, selected_team);
-
- // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
- // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
- LogTeamchange(this.playerid, this.team, 99);
- }
- return selected_team;
+ SetPlayerTeamSimple(this, selected_team);
+ LogTeamchange(this.playerid, this.team, 99);
+ return;
}
- // otherwise end up on the smallest team (handled below)
}
-
- int best_team = FindSmallestTeam(this, true);
- if (only_return_best || this.bot_forced_team)
+ // otherwise end up on the smallest team (handled below)
+ if (this.bot_forced_team)
{
- return best_team;
+ return;
}
+ int best_team = FindSmallestTeam(this, true);
best_team = Team_NumberToTeam(best_team);
if (best_team == -1)
{
TeamchangeFrags(this);
SetPlayerTeamSimple(this, best_team);
LogTeamchange(this.playerid, this.team, 2); // log auto join
- if (!IS_BOT_CLIENT(this))
+ if ((old_team != -1) && !IS_BOT_CLIENT(this))
{
AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
}
KillPlayerForTeamChange(this);
- return best_team;
}
void SV_ChangeTeam(entity this, float _color)
source_team = Team_TeamToNumber(source_color + 1);
destination_team = Team_TeamToNumber(destination_color + 1);
+ if (destination_team == -1)
+ {
+ return;
+ }
+
CheckAllowedTeams(this);
if (destination_team == 1 && c1 < 0) destination_team = 4;
void AutoBalanceBots(int source_team, int destination_team)
{
- if ((source_team == -1) || (destination_team == -1))
+ if (!Team_IsValidNumber(source_team))
{
+ LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team);
+ return;
+ }
+ if (!Team_IsValidNumber(destination_team))
+ {
+ LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f",
+ destination_team);
return;
}
if (!autocvar_g_balance_teams ||
break;
}
}
+ if (num_players_source_team < 0)
+ {
+ return;
+ }
switch (destination_team)
{
case 1:
// NOTE: Assumes CheckAllowedTeams has already been called!
int FindSmallestTeam(entity player, float ignore_player);
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team);
+void JoinBestTeam(entity this, bool force_best_team);
/// \brief Auto balances bots in teams after the player has changed team.
/// \param[in] source_team Previous team of the player (1, 2, 3, 4).
#define IS_VEHICLE(v) (v.vehicle_flags & VHF_ISVEHICLE)
#define IS_TURRET(v) (v.turret_flags & TUR_FLAG_ISTURRET)
+#define IS_MOVABLE(v) ((IS_PLAYER(v) || IS_MONSTER(v)) && !STAT(FROZEN, v))
+
// NOTE: FOR_EACH_CLIENTSLOT deprecated! Use the following instead: FOREACH_CLIENTSLOT(true, { code; });
// NOTE: FOR_EACH_CLIENT deprecated! Use the following instead: FOREACH_CLIENT(true, { code; });
// NOTE: FOR_EACH_REALCLIENT deprecated! Use the following instead: FOREACH_CLIENT(IS_REAL_CLIENT(it), { code; });
#include "weaponsystem.qh"
#include <common/t_items.qh>
+#include <server/items.qh>
#include <common/constants.qh>
#include <common/net_linked.qh>
#include <common/util.qh>
if (!autocvar_g_showweaponspawns) return;
IL_EACH(g_items, it.weapon == this.m_id && (!it.team || (it.ItemStatus & ITS_AVAILABLE)),
{
- if (it.classname == "droppedweapon" && autocvar_g_showweaponspawns < 2)
+ if (Item_IsLoot(it) && (autocvar_g_showweaponspawns < 2))
+ {
continue;
+ }
entity wp = WaypointSprite_Spawn(
WP_Weapon,
-2, 0,
#include "../resources.qh"
#include "../mutators/_mod.qh"
#include <common/t_items.qh>
+#include <server/items.qh>
#include <common/weapons/_all.qh>
+.bool m_isreplaced; ///< Holds whether the weapon has been replaced.
+
string W_Apply_Weaponreplace(string in)
{
string out = "";
void weapon_defaultspawnfunc(entity this, Weapon e)
{
Weapon wpn = e;
- if (this.classname != "droppedweapon" && this.classname != "replacedweapon")
+ e = wpn = wpn.m_spawnfunc_hookreplace(wpn, this);
+ this.classname = wpn.m_canonical_spawnfunc;
+ if (!Item_IsLoot(this) && !this.m_isreplaced)
{
if (e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
{
{
entity replacement = spawn();
copyentity(this, replacement);
- replacement.classname = "replacedweapon";
+ replacement.m_isreplaced = true;
weapon_defaultspawnfunc(replacement, it);
break;
}
#include "weaponsystem.qh"
#include "../resources.qh"
+#include "../items.qh"
#include "../mutators/_mod.qh"
#include <common/t_items.qh>
#include "../g_damage.qh"
Weapon info = Weapons_from(wpn);
int ammotype = info.ammo_type;
- entity wep = new(droppedweapon);
-
+ entity wep = spawn();
+ Item_SetLoot(wep, true);
setorigin(wep, org);
wep.velocity = velo;
wep.owner = wep.enemy = own;
if(WepSet_FromWeapon(Weapons_from(wpn)) & WEPSET_SUPERWEAPONS)
{
+ Item_SetExpiring(wep, true);
if(own.items & IT_UNLIMITED_SUPERWEAPONS)
{
wep.superweapons_finished = time + autocvar_g_balance_superweapons_time;
#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);
}
if(autocvar_g_antilag == 0 || noantilag)
lag = 0; // only do hitscan, but no antilag
if(lag)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_takeback(it, CS(it), time - lag));
- IL_EACH(g_monsters, it != this,
- {
- antilag_takeback(it, it, time - lag);
- });
- }
+ antilag_takeback_all(this, lag);
// change shooter to SOLID_BBOX so the shot can hit corpses
int oldsolid = this.dphitcontentsmask;
}
if(lag)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_restore(it, CS(it)));
- IL_EACH(g_monsters, it != this,
- {
- antilag_restore(it, it);
- });
- }
+ antilag_restore_all(this);
// restore shooter solid type
if(this)
#include <server/miscfunctions.qh>
float internalteam;
-float weaponswapping;
entity weapon_dropevent_item;
..entity weaponentity_fld;
--- /dev/null
+// Random items mutator config
+
+// Map items
+
+set g_random_items_replace_item_health_small "random" "Classnames to replace small health with."
+set g_random_items_replace_item_health_medium "random" "Classnames to replace medium health with."
+set g_random_items_replace_item_health_big "random" "Classnames to replace big health with."
+set g_random_items_replace_item_health_mega "random" "Classnames to replace mega health with."
+set g_random_items_replace_item_armor_small "random" "Classnames to replace small armor with."
+set g_random_items_replace_item_armor_medium "random" "Classnames to replace medium armor with."
+set g_random_items_replace_item_armor_big "random" "Classnames to replace big armor with."
+set g_random_items_replace_item_armor_mega "random" "Classnames to replace mega armor with."
+set g_random_items_replace_item_shells "random" "Classnames to replace shells with."
+set g_random_items_replace_item_bullets "random" "Classnames to replace bullets with."
+set g_random_items_replace_item_rockets "random" "Classnames to replace rockets with."
+set g_random_items_replace_item_cells "random" "Classnames to replace cells with."
+set g_random_items_replace_item_plasma "random" "Classnames to replace plasma with."
+set g_random_items_replace_item_fuel "random" "Classnames to replace fuel with."
+set g_random_items_replace_weapon_blaster "random" "Classnames to replace blaster with."
+set g_random_items_replace_weapon_shotgun "random" "Classnames to replace shotgun with."
+set g_random_items_replace_weapon_machinegun "random" "Classnames to replace machinegun with."
+set g_random_items_replace_weapon_mortar "random" "Classnames to replace mortar with."
+set g_random_items_replace_weapon_electro "random" "Classnames to replace electro with."
+set g_random_items_replace_weapon_crylink "random" "Classnames to replace crylink with."
+set g_random_items_replace_weapon_vortex "random" "Classnames to replace vortex with."
+set g_random_items_replace_weapon_hagar "random" "Classnames to replace hagar with."
+set g_random_items_replace_weapon_devastator "random" "Classnames to replace devastator with."
+set g_random_items_replace_weapon_shockwave "random" "Classnames to replace shockwave with."
+set g_random_items_replace_weapon_arc "random" "Classnames to replace arc with."
+set g_random_items_replace_weapon_hook "random" "Classnames to replace hook with."
+set g_random_items_replace_weapon_tuba "random" "Classnames to replace tuba with."
+set g_random_items_replace_weapon_porto "random" "Classnames to replace port-o-launch with."
+set g_random_items_replace_weapon_fireball "random" "Classnames to replace fireball with."
+set g_random_items_replace_weapon_minelayer "random" "Classnames to replace mine layer with."
+set g_random_items_replace_weapon_hlac "random" "Classnames to replace HLAC with."
+set g_random_items_replace_weapon_rifle "random" "Classnames to replace rifle with."
+set g_random_items_replace_weapon_seeker "random" "Classnames to replace TAG seeker with."
+set g_random_items_replace_weapon_vaporizer "random" "Classnames to replace vaporizer with."
+set g_random_items_replace_weapon_hmg "random" "Classnames to replace HMG with."
+set g_random_items_replace_weapon_rpc "random" "Classnames to replace RPC with."
+set g_random_items_replace_item_strength "random" "Classnames to replace strength with."
+set g_random_items_replace_item_shield "random" "Classnames to replace shield with."
+set g_random_items_replace_item_fuel_regen "random" "Classnames to replace fuel regeneration with."
+set g_random_items_replace_item_jetpack "random" "Classnames to replace jetpack with."
+set g_random_items_replace_item_vaporizer_cells "random" "Classnames to replace vaporizer cells with."
+set g_random_items_replace_item_invisibility "random" "Classnames to replace invisibility with."
+set g_random_items_replace_item_extralife "random" "Classnames to replace extra life with."
+set g_random_items_replace_item_speed "random" "Classnames to replace speed with."
+set g_random_items_health_probability 1 "Probability of random health items spawning in the map."
+set g_random_items_armor_probability 1 "Probability of random armor items spawning in the map."
+set g_random_items_resource_probability 1 "Probability of random resource items spawning in the map."
+set g_random_items_weapon_probability 1 "Probability of random weapons spawning in the map."
+set g_random_items_powerup_probability 0.15 "Probability of random powerups spawning in the map."
+set g_random_items_item_health_small_probability 10 "Probability of random small health spawning in the map."
+set g_random_items_item_health_medium_probability 4 "Probability of random medium health spawning in the map."
+set g_random_items_item_health_big_probability 2 "Probability of random big health spawning in the map."
+set g_random_items_item_health_mega_probability 1 "Probability of random mega health spawning in the map."
+set g_random_items_item_armor_small_probability 10 "Probability of random small armor spawning in the map."
+set g_random_items_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map."
+set g_random_items_item_armor_big_probability 2 "Probability of random big armor spawning in the map."
+set g_random_items_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map."
+set g_random_items_item_shells_probability 1 "Probability of random shells spawning in the map."
+set g_random_items_item_bullets_probability 1 "Probability of random bullets spawning in the map."
+set g_random_items_item_rockets_probability 1 "Probability of random rockets spawning in the map."
+set g_random_items_item_cells_probability 1 "Probability of random cells spawning in the map."
+set g_random_items_item_plasma_probability 0 "Probability of random plasma spawning in the map."
+set g_random_items_item_fuel_probability 0 "Probability of random fuel spawning in the map."
+set g_random_items_weapon_blaster_probability 0 "Probability of random blaster spawning in the map."
+set g_random_items_weapon_shotgun_probability 0 "Probability of random shotgun spawning in the map."
+set g_random_items_weapon_machinegun_probability 1 "Probability of random machinegun spawning in the map."
+set g_random_items_weapon_mortar_probability 1 "Probability of random mortar spawning in the map."
+set g_random_items_weapon_electro_probability 1 "Probability of random electro spawning in the map."
+set g_random_items_weapon_crylink_probability 1 "Probability of random crylink spawning in the map."
+set g_random_items_weapon_vortex_probability 1 "Probability of random vortex spawning in the map."
+set g_random_items_weapon_hagar_probability 1 "Probability of random hagar spawning in the map."
+set g_random_items_weapon_devastator_probability 1 "Probability of random devastator spawning in the map."
+set g_random_items_weapon_shockwave_probability 0 "Probability of random shockwave spawning in the map."
+set g_random_items_weapon_arc_probability 0 "Probability of random arc spawning in the map."
+set g_random_items_weapon_hook_probability 0 "Probability of random hook spawning in the map."
+set g_random_items_weapon_tuba_probability 0 "Probability of random tuba spawning in the map."
+set g_random_items_weapon_porto_probability 0 "Probability of random port-o-launch spawning in the map."
+set g_random_items_weapon_fireball_probability 0 "Probability of random fireball spawning in the map."
+set g_random_items_weapon_minelayer_probability 0 "Probability of random mine layer spawning in the map."
+set g_random_items_weapon_hlac_probability 0 "Probability of random HLAC spawning in the map."
+set g_random_items_weapon_rifle_probability 0 "Probability of random rifle spawning in the map."
+set g_random_items_weapon_seeker_probability 0 "Probability of random TAG seeker spawning in the map."
+set g_random_items_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning in the map."
+set g_random_items_item_strength_probability 1 "Probability of random strength spawning in the map."
+set g_random_items_item_shield_probability 1 "Probability of random shield spawning in the map."
+set g_random_items_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning in the map."
+set g_random_items_item_jetpack_probability 0 "Probability of random jetpack spawning in the map."
+set g_random_items_item_vaporizer_cells_probability 20 "Probability of random vaporizer cells spawning in the map."
+set g_random_items_item_invisibility_probability 1 "Probability of random invisibility spawning in the map."
+set g_random_items_item_extralife_probability 1 "Probability of random extra life spawning in the map."
+set g_random_items_item_speed_probability 1 "Probability of random speed spawning in the map."
+set g_random_items_overkill_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill."
+set g_random_items_overkill_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill."
+set g_random_items_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill."
+set g_random_items_overkill_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill."
+set g_random_items_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill."
+set g_random_items_overkill_weapon_hmg_probability 0.5 "Probability of random HMG spawning in the map during overkill."
+set g_random_items_overkill_weapon_rpc_probability 0.5 "Probability of random RPC spawning in the map during overkill."
+
+// Loot
+
+set g_random_loot_min 0 "Minimum amount of loot items."
+set g_random_loot_max 4 "Maximum amount of loot items."
+set g_random_loot_time 10 "Amount of time the loot will stay in seconds."
+set g_random_loot_spread 200 "How far can loot be thrown."
+set g_random_loot_health_probability 1 "Probability of random health items spawning as loot."
+set g_random_loot_armor_probability 1 "Probability of random armor items spawning as loot."
+set g_random_loot_resource_probability 1 "Probability of random ammo items spawning as loot."
+set g_random_loot_weapon_probability 1 "Probability of random weapons spawning as loot."
+set g_random_loot_powerup_probability 0.2 "Probability of random powerups spawning as loot."
+set g_random_loot_item_health_small_probability 4 "Probability of random small health spawning as loot."
+set g_random_loot_item_health_medium_probability 3 "Probability of random medium health spawning as loot."
+set g_random_loot_item_health_big_probability 2 "Probability of random big health spawning as loot."
+set g_random_loot_item_health_mega_probability 1 "Probability of random mega health spawning as loot."
+set g_random_loot_item_armor_small_probability 4 "Probability of random small armor spawning as loot."
+set g_random_loot_item_armor_medium_probability 3 "Probability of random medium armor spawning as loot."
+set g_random_loot_item_armor_big_probability 2 "Probability of random big armor spawning as loot."
+set g_random_loot_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot."
+set g_random_loot_item_shells_probability 1 "Probability of random shells spawning as loot."
+set g_random_loot_item_bullets_probability 1 "Probability of random bullets spawning as loot."
+set g_random_loot_item_rockets_probability 1 "Probability of random rockets spawning as loot."
+set g_random_loot_item_cells_probability 1 "Probability of random cells spawning as loot."
+set g_random_loot_item_plasma_probability 0 "Probability of random plasma spawning as loot."
+set g_random_loot_item_fuel_probability 0 "Probability of random fuel spawning as loot."
+set g_random_loot_weapon_blaster_probability 0 "Probability of random blaster spawning as loot."
+set g_random_loot_weapon_shotgun_probability 0 "Probability of random shotgun spawning as loot."
+set g_random_loot_weapon_machinegun_probability 1 "Probability of random machinegun spawning as loot."
+set g_random_loot_weapon_mortar_probability 1 "Probability of random mortar spawning as loot."
+set g_random_loot_weapon_electro_probability 1 "Probability of random electro spawning as loot."
+set g_random_loot_weapon_crylink_probability 1 "Probability of random crylink spawning as loot."
+set g_random_loot_weapon_vortex_probability 1 "Probability of random vortex spawning as loot."
+set g_random_loot_weapon_hagar_probability 1 "Probability of random hagar spawning as loot."
+set g_random_loot_weapon_devastator_probability 1 "Probability of random devastator spawning as loot."
+set g_random_loot_weapon_shockwave_probability 0 "Probability of random shockwave spawning as loot."
+set g_random_loot_weapon_arc_probability 0 "Probability of random arc spawning as loot."
+set g_random_loot_weapon_hook_probability 0 "Probability of random hook spawning as loot."
+set g_random_loot_weapon_tuba_probability 0 "Probability of random tuba spawning as loot."
+set g_random_loot_weapon_porto_probability 0 "Probability of random port-o-launch spawning as loot."
+set g_random_loot_weapon_fireball_probability 0 "Probability of random fireball spawning as loot."
+set g_random_loot_weapon_minelayer_probability 0 "Probability of random mine layer spawning as loot."
+set g_random_loot_weapon_hlac_probability 0 "Probability of random HLAC spawning as loot."
+set g_random_loot_weapon_rifle_probability 0 "Probability of random rifle spawning as loot."
+set g_random_loot_weapon_seeker_probability 0 "Probability of random TAG seeker spawning as loot."
+set g_random_loot_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning as loot."
+set g_random_loot_item_strength_probability 1 "Probability of random strength spawning as loot."
+set g_random_loot_item_shield_probability 1 "Probability of random shield spawning as loot."
+set g_random_loot_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning as loot."
+set g_random_loot_item_jetpack_probability 0 "Probability of random jetpack spawning as loot."
+set g_random_loot_item_vaporizer_cells_probability 20 "Probability of random vaporizer cells spawning as loot."
+set g_random_loot_item_invisibility_probability 1 "Probability of random invisibility spawning as loot."
+set g_random_loot_item_extralife_probability 1 "Probability of random extra life spawning as loot."
+set g_random_loot_item_speed_probability 1 "Probability of random speed spawning as loot."
+set g_random_loot_overkill_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill."
+set g_random_loot_overkill_weapon_hmg_probability 1 "Probability of random HMG spawning as loot during overkill."
+set g_random_loot_overkill_weapon_rpc_probability 1 "Probability of random RPC spawning as loot during overkill."