diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 000000000..3a32a0907 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,30 @@ +name: Compile and Upload + +on: + push: + branches: + - master + - staging + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Compile + run: | + wget http://fte.triptohell.info/moodles/linux_amd64/fteqcc64 + chmod +x fteqcc64 + export PATH=$GITHUB_WORKSPACE:$PATH + make + mkdir -p dats/${{ github.ref }} + cp *.dat dats/${{ github.ref }} + - uses: jakejarvis/s3-sync-action@master + with: + args: --acl public-read --follow-symlinks + env: + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: 'ap-southeast-2' + SOURCE_DIR: 'dats/refs/heads/' diff --git a/.gitignore b/.gitignore index 55ae4690d..c0ecc834c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ *.lno *.exe *.log -*.bat \ No newline at end of file +*.patch +fteqcc64 +.vscode/tasks.json +.gitignore diff --git a/Makefile b/Makefile index dd8a2f3d8..7c49e4201 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,9 @@ ifndef REV endif all: - gmqcc -std=fteqcc -fvariadic-args -funtyped-nil -DVER=\"$(VER)\" -DREV=\"$(REV)\" + fteqcc64 -DVER=\"$(VER)\" -DREV=\"$(REV)\" ./ssqc/progs.src + fteqcc64 -DVER=\"$(VER)\" -DREV=\"$(REV)\" ./csqc/csprogs.src + fteqcc64 -DVER=\"$(VER)\" -DREV=\"$(REV)\" ./menu/menu.src clean: rm -f $(TARGET) qwprogs.lno files.dat progdefs.h diff --git a/README.md b/README.md index 3fa5a1cef..884f67706 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,200 @@ -Classic Fortress v0.9 beta -========================== +FortressOne Server +================== -New features ------- +New commands +------------ + +* `fo_reloadvolume 0` 0 - 1 volume for reloading sounds +* `fo_reloadalpha x` alpha of viewmodel when reloading, 0 for invisible +* `fo_team_color_crosshair` change crosshair to team colour +* `cmd updateserver` tell server to pull latest progs and maps +* `+slot n` bind. fires nth weapon +* `fo_default_weapon 0` default weapon when using `+slot` binds +* `fo_hud_cache 1` less resource intensive hud +* `fo_hud_fps 60` set hud refresh rate +* `fo_grentimer_ping_frac 1` fraction of ping to correct for +* `fo_grentimer_nostack 0` when set, only play the oldest timer +* `fo_fte_hud 0` completely replace Quake engine hud with FO hud +* `fo_legacy_sbar 0` use oldschool Team Fortress status bar +* `fo_oldscoreboard 0` use oldschool Quake scoreboard +* `fo_adminrefresh 2` time in seconds for admin menu to refresh +* `fo_predict_weapons 1` client-side weapon prediction +* `fo_predict_projectiles 1` client-side projectile prediction +* `wpp_min_ping -1` minimum ping before `fo_wpp_beta` is enabled. +* `fo_client_sniper_sight 1` client side sniper dot +* `cl_p2r` use rocket model for incendiary launcher +* `cl_r2g` use grenade model for rockets +* `r_pyrotrail 0` ezquake-compatible trail selection for incendiary launcher +* `r_rockettrail 0` ezquake-compatible trail selection for rocket launcher +* `r_grenadetrail 0` ezquake-compatible trail selection for grenade launcher +* `wpp_phys_adv_ms 0` +* `wpp_phys_local_adv_ms 0` +* `wpp_setspeed 1` +* `wpp_debug 1` bitfield 0,1,2,4,8 for debugging +* `fo_phys_debug 1` bitfield 0,1 for debugging + +* Added in `fo_hittext_friendly 0` - setting to 1 shows text when damaging friendlys +* Added in `fo_hittext_colour3 "1 0 0"` - colour of friendly hittext if fo_hittext_friendly is set to 1 (rgb 0-1) + +* Added in csqc hitaudio and hittext for testing `localinfo zutmode 1` on server to enable. + +Client commands, default shown: + +=== Audio === +* `fo_hitaudio_enabled 1` - toggle on/off +* `fo_hitaudio_hurtself 1` - play a sound when you hurt yourself +* `fo_hitaudio_hurtteam 1` - play a sound when you hurt a teammate +* `fo_hitaudio_hurtenemy 1` - play a sound when you hurt an enemy +* `fo_hitaudio_killself 1` - play a sound when you kill yourself +* `fo_hitaudio_killteam 1` - play a sound when you kill a teammate +* `fo_hitaudio_killenemy 1` - play a sound when you kill an enemy +* `fo_hitaudio_noarmour 1` - play an extra sound if you hurt an enemy with no armour + +also added a headshot sound for snipers, only plays for the client +sound files are found in `fortress/sound/hitaudio/` and `fortress/sound/announcer/` + +=== Text === +* `fo_hittext_enabled 1` - toggle on/off +* `fo_hittext_size 32` - size of text +* `fo_hittext_speed 96` - how fast text scrolls up +* `fo_hittext_alpha 1` - alpha +* `fo_hittext_duration 2` - how long before text disappears +* `fo_hittext_rawdamage 1` - setting to 0 shows damage AFTER armour mitigation +* `fo_hittext_noarmour 1` - changes colour of text if enemy has no armour, see `fo_hittext_colour2` to set +* `fo_hittext_offset 32` - how high text appears above target +* `fo_hittext_friendly 0` - toggles text above friendlys if you damage them +* `fo_hittext_colour "1 1 1"` - default colour of enemy text (rgb 0-1) +* `fo_hittext_colour2 "1 0 1"` - overrides default colour of enemy text if `fo_hittext_noarmour 1` is set and target has no armour (rgb 0-1) +* `fo_hittext_colour3 "1 0 0"` - colour of friendly hittext if fo_hittext_friendly is set to 1 (rgb 0-1) +------------------------------- + +* Website backend for match results, stats. Get a token at fortressone.org, connect to a FortressOne server, and `login `. + + +* `localinfo play_to_completion 0` set to 1 to allow quad to continue to round end even after required score exceeded. +* `localinfo pipecooldown_time ` time in seconds for demo pipe cooldown +* `localinfo allpipes_cooldown on/off` whether cooldown is applied to individual pipes or all pipes. i.e. with this on the demo can not det any of his pipes immediately after firing. (default off) +* `localinfo discord_channel_id ` to specify discord_channel. Required for autoreporting. +* `localinfo fo_matchrated 2` whether match is rated / affects trueskill. 2 is false for 1v1 and 2v2 only. +* `localinfo backend_address ` to specify backend API endpoint. Default: https://www.fortressone.org/ +* All-time attack and all-time defence team options. +* `setinfo teamcolor ` sets player skins colours for team , where is quake palette 0-15 or hex code beginning with 0x. E.g. `setinfo team1colour 0` makes all team 1 players white, `setinfo team2color 0xFF8800` makes all team 2 players orange. `setinfo teamcolor ""` to restore defaults. Note that `teamcolor` and `enemycolor` cvars will take priority. +* `setinfo precise_grenades on/off` to enable precise timing when throwing grenades. This removes a random, up to, 100ms input delay. (default on) +* `localinfo forcereload 0/1` Option to prevent forced reloads. +* `+grenade1` and `+grenade2` grenade buttons (more reliable than impulses), push to prime, again to throw. +* `+dropflag` Allows player to hold button and flag will be thrown on contact. +* `+rj` Switches to rocket/incendiary weapon, jumps and shoots. `+aux_jump` is no longer required. +* `dlastspawn` Tells spy to disguise as enemy who last spawned. +* `setinfo cf_pyro_impulses 1` to swap Pyro's primary and secondary weapons. +* `setinfo autodisguise 1` Causes spy to `dlastspawn` after spawning or cover blown. +* `setinfo autodisguise 2` Causes spy to `dlast` after spawning or cover blown. +* `special2` Scout: `autoscan`, Demoman: `+det5`, Pyro: `+rj`, Spy: `dlastspawn`, Engineer: `togglesentry` +* New buttons (not impulses): +* `+special` Scout: `dash`, Demoman: `detpipe`, Medic: `aura`, Hwguy: `lock`, Pyro: `airblast`, Spy: `+feign`, Engineer: `toggledispenser`. +* `+special2` Same as `special2`, but also has `+rj` for Soldier and Pyro. +* `specialup` Engineer: `cmd sentry rotate -15`, Spy: `cmd disguise prev`. +* `specialdown` Engineer: `cmd sentry rotate 15`, Spy: `cmd disguise next`. +* `zoomin` and `zoomout` commands that adjust sens with zoom. 5 levels. +* `setinfo hold_grens` for press and hold `+grenade1` and `+grenade2` +* `setinfo hold_fiegn` for press and hold feigning +* `setinfo hold_detpack` for press and hold detpack +* `localinfo standardizedeathammo 1` server setting to make all backpack's dropped on death contain same ammo, regardless of victims ammo. If enabled defaults to 25 shells, 25 nails, 10 rockets, 50 cells. `localinfo deathammo_shells ` , `localinfo deathammo_nails ` , `localinfo deathammo_rockets `, `localinfo deathammo_cells ` to modify these values +* `localinfo splitbackpackmodels 1` server setting to have different visual models for backpack dropped on death ``progs/deathbag.mdl`` and discards ``progs/discard.mdl`` +* `localinfo allowpracspawns 1` option for players to set a personal spawnpoint for practice. Players can then use commands `placepracspawn` and `removepracspawn`. Suicide time penalties are removed while allowpracspawns is enabled. +* Added ``localinfo nomapcycle 1`` to stop all the confusion. +* Option to configure hwguy armor `localinfo max_armor_hwguy 250`. +* ``setinfo keepcells `` allows scout/med/pyro/eng/hwguy to include cells above ```` into discards. eg. an eng with 200 cells that has ``setinfo keepcells 50`` will discard 150 cells and keep 50. sold/spy/sniper/demo will throw all cells regardless of ```` (current behaviour). suggest players using this setinfo use scout/med/pyro/eng/hwguy class configs to set values for each class. ``localinfo nokeepcells 1`` - disables keepcells server-wide +* option to let engineer move while building `localinfo em on`. +* new brush ent ``trigger_jumper`` - an alias for ``trigger_push`` with spawnflags 16 - retains your x/y velocity, only boosting your z by the .speed value. +* new csqc command ``fo_menu_vote`` shows the list of maps available to vote. +* ``vote_addmap``\``vote_removemap`` now work client-side +* new cvar for zut ``fo_hud_idle_alpha`` - sets the minimum transparency for flaginfo inactive items +* optional solid nail/shock grenades - localinfo solid_nailgren on (default on) +* localinfo nohitsounds 1 - disables hitsounds server-wide +* localinfo noreturn 1 - prevents goalitems from returning (will still return from lava) +* localinfo superaxe on/off - causes the axe to hit on all four attack frames (instead of only on 3rd frame) +* localinfo supermedikit on/off - causes the medikit to hit on all four attack frames (instead of only on 3rd frame) +* localinfo superspanner on/off - causes the spanner to hit on all four attack frames (instead of only on 3rd frame) +* localinfo superknife on/off - causes the knife to hit on all four attack frames (instead of only on 3rd frame) +* localinfo superknife_multihit on/off - controls whether multiple hits are allowed in a single attack +* scout has "new" flash grenades - localinfo fo_flash on (default on) +* ability to set client side min and max flash amounts - localinfo minflash x/localinfo maxflash x (number as a percentage - 1.5 = 150%) +* `cmd votemap`/`cmd showvotes`/`cmd listmaps` can now be used at any time and are part of the same system +* new server command `vote_removemap ` removes them +* new server command `vote_addmap [mapgroup] [num_teams] [min_players] [max_players]` can be used to add maps to the below menu +* `cmd mapmenu` brings up a map selection menu, which can then either be voted for or changed immediately, provided you have adminpwd/rcon set up +* localinfo vote_threshold 0.5 will set the portion of players required to win a vote +* scout has "new" flash grenades - localinfo fo_flash on (default off) +* ability to set client side min and max flash amounts - setinfo minflash x/localinfo maxflash x (number as a percentage - 1.5 = 150%) +* localinfo quad_roles 1 enables the use of quad roles. Only works in quad mode: Blue gets the "attack" role first and Red gets the "defence" role. + These roles can be configured by adding the "att_" and "def_" prefix to localinfo settings. Only detpipe_limit, respawn_time, gren limits and class limits are currently supported. + Use `cmd showroles` to see the current configuration. + The team with the "attack" role also has its flag hidden to avoid confusion. +* localinfo keep_teams 1/2 - keeps teams upon map change. 1 = same team. 2 = rotate teams +* hud commands: `fo_hud [element] [setting] [value]` lets you manually configure the extra hud elements' settings and `fo_hud_save` saves them. +* new spectator command `tracktarget` lets you track whoever you're pointing at +* `setinfo killsound 1/2/3` 1 enemies, 2 enemies and teammates, 3 enemies teammates and self +* `cmd forcebreak` - new admin command to end the map (and go to vote) +* `break` will vote to end the current map or recind your vote in a vote map +* localinfo vote_time 60 - seconds since the first vote is cast until voting is decided. 0 means majority vote only. +* localinfo vote_map - map to designate for voting +* localinfo vote_style 1 - got to voting map after round is over instead of asking using menus, 2 - maps restart upon time/frag limit, but a `break` concensus will exit them to the votemap. +* CSQC - fo_main_menu main menu - either from menu.dat or engine +* CSQC - fo_menu_game in-game menu +* CSQC - fo_grentimer 0 - none, 1 - starts on server prime message, 2 - starts on prime button press +* CSQC - fo_grentimersound grentimer.wav +* CSQC - fo_grentimervolume 1 +* CSQC - fo_specgrentimervolume -1 [ignored when -1] +* CSQC - fo_jumpvolume 1 +* CSQC - fo_hud_reset resets HUD to defaults +* CSQC - fo_hud_reload reloads last-saved hud configuration +* CSQC - fo_hud_editor to move panels and save to config +* CSQC - fo_csjumpsounds 1 for client side jump sounds (not delayed by ping) +* `info_empblock` has a new field `goal_effects`. Setting it to 16 will prevent it from blocking emps if there is a wall between it and the explosion. +* New map point entity `info_empblock` with `t_length` field that specifies its radius of effect. An EMP explosion within a range of one will not go through walls. +* Server option for duelmode to allow draws on a double-ko `localinfo duel_allow_draw 1`/`localinfo dad 1` (default 1) +* Server option for duelmode to force a tie-break based on difference `localinfo duel_tie_break X`/`localinfo dtb X` (default 2) +* Server option for duelmode to force a countdown even if there is a double-kill `localinfo duel_draw_countdown 1`/`localinfo ddc 1` (default 1). If 0, both will respawn immediately upon the second player's death. +* Server option for duelmode to allow players to autoprime as soon as they are able `localinfo duel_autoprime 1`/`localinfo dap `. (default 0). Will only autoprime for players who have set `setinfo dap 1` +* Server option for duelmode to allow spawn protection `localinfo duel_spawn_guard 1`/`localinfo dsg 1` - it will not allow any fighting until both players have left the spawn. +* Server option for duelmode to print winner's health `localinfo duel_print_health 1`/`localinfo dph 1` +* Server option for duelmode to respawn with all grens `localinfo duel_all_grens 1`/`localinfo dag 1` +* Server option to remove packs in duel mode `localinfo duel_no_packs 1`/`localinfo dnp 1` +* Server setting for duelmode reset delay `localinfo drd 0.5` (`localinfo duel_reset_delay 0.5`) +* Admin option to enable duel mode. Will auto-reset/resup the not dead player. Main option - `localinfo duelmode on` +* Server option for making all walls block EMP. Off by default. `localinfo walls_block_emp 1` or `localinfo wbe 1`. (SPAWNFLAG_BLOCK_EMP 4096 will work regardless) +* Server option for setting detpack to solid (blocking). Off by default. `localinfo solid_detpack 1` or `localinfo sdp 1`. +* Server option for overriding map class restrictions (except civilian). `serverinfo override_mapclasses 1` or `serverinfo omc 1`. +* Option for maximum grenades for all classes. `localinfo max_gren1_ `, short `localinfo mg1_ `. Works for gren1s and gren2s. Eg `localinfo max_gren1_scout 0` to remove caltrops or `localinfo mg2_9 2` to reduce max EMPs to 2. Set to -1 for default. +* Option to fully restock player's clip and finish reload immediately if in progress. `localinfo stock_reload 1` (`localinfo srd 1`) will trigger only on flag capture (with stock_on_cap enabled). `2` will trigger whenever any tfgoal gives you the appropriate ammo. +* Option for statusbar flaginfo. `setinfo sbflaginfo 1` (default). Setting it to `2` will skip the tf tips on respawn and show flag info all the time. +* Admin system created to allow for easy setup of pub/clan/quad/duel games, kick players etc `localinfo adminpwd ` and `cmd adminpwd ; wait; adminmenu` +* Loc support added to server, show locations for dropped flag. +* Nailgrenades changed to "Shock/Laser Grenades" to lower spam/not stop bunnyhopping on hit (0 original, 1 laser, 2 burst). `localinfo nailgren_type 1` and `nginfo` in game for all configurable settings. +* Option for "blast medic". Secondary grenade is repaced with blast gren. It behaves like a concussion grenade, but doesn't apply a concussion effect. The blast medic moves at 280 units (instead of 320), but is not speed capped. `localinfo medic_type 1` (0 for normal, 1 for blast). +* Blast grenade velocity multiplier `localinfo blastgren_velocity_multiplier`. (default 1 is same as concussion grenade). +* Option for hitsounds (1 - enemies only, 2 - enemies and teammates). `setinfo hitsound 2` +* Option for medic to be immune from concussion effects. `localinfo medicnocuss on`. +* Option to adjust concussion grenade effect time in seconds. `localinfo cussgrentime n`. +* Increased nail velocity. Disable with `localinfo old_ng_velocity on`. +* Nailgun and Super-nailgun damage configurable with `localinfo ng_damage` and `localinfo sng_damage`. +* Keys and flags glow their colour. +* Option to adjust conussion grenade effect time in seconds. `localinfo cussgrentime`. +* Option to fully restock player on cap. `localinfo stock_on_cap on`. +* Option to strip ammo and grens from defenders within 1500 units of cap point when flag is capped. `localinfo cap_strip 1`. +* Option for packs to fully restock health and armor of player. `localinfo stockfull on`. +* Automatic server-side mvd recording of clan matches. Requires `localinfo serverdemo on`. * Map vote (4 random maps + current map) during last few minutes of game (shown for newly spawned or toggled with /togglevote). * Force early map vote using /votenext, /votetrick (trick maps) and /voterace (race maps). * Force map switch to the voted map early using /forcenext. * Auto ID feature (cf\_autoid 1 = on, 2 = teammates only, 3 = enemies only). * Show friendly Sentry Gun health in /id for Engineers. * Show max health and max armor in /id for Medics and Engineers. -* Grenade timers (disable with /cf\_notimers 1 = no timers, 2 = no timer sound). +* Grenade timers (disable with `setinfo nt 1` for no timers, or `setinfo nt 2` for no timer sound). * Grenade slot switching (/grenswitch). * Prime/throw grenades with one button (/gren1 and /gren2). * Weapon slots (1-4) where 1 is always primary and 4 is always melee. -* Quick attack aliases (+slot1-4). +* Quick attack aliases (+quick1-4 will switch weapon and fire). * Next/previous weapon (/weapprev and /weapnext). * Last weapon (/weaplast). * Remember current weapon and last weapon after dying. @@ -25,8 +206,36 @@ New features * Free class switch during first 10 seconds after spawning. * Updated class help (bindings, aliases and settings) reachable with /classhelp. * Dropping flag now possible on all maps using /dropflag. -* Class configs are now executed from /fortress/classes/ subdirectory. -* When using class configs, /fortress/classes/default.cfg gets executed first. +* Allow team changing. +* Any non-valid impulse now closes the active menu. +* Option to allow a demoman to place a detpack while reloading his weapon `localinfo detreload on` +* localinfo server_sbflaginfo : 0 - disables sbar flaginfo, 1 enables it [default: 1] +* localinfo reverse_cap : 0 - normal gameplay, 1: you have to take your flag and capture in the enemy base [default: 0] +* localinfo engineer_move / em : 0 - normal gameplay, 1: engineers can move while building [default: 1] +* localinfo grenade_lockout / gl : Time in seconds grenade throw is locked out [default: 0.1] +* localinfo round_delay_time : interval time between rounds in quadmode - seconds [default: 30] +* localinfo max_gren2_soldier : maximum number of active nail/shock grenades (TF 2.8 = 3, OzTF = 1) [default: 3] +* localinfo distance_based_cuss_duration : on/off - enables cuss duration to be proportional to the distance from the explosion [default: off] +* lag compensation: see antilag.md for more information + +== Removed === +* Removed weapon messages for weapons without weapon modes. +* Removed bioweapon (merged into medikit). +* Removed grapple hook. +* Removed birthday mode. +* Removed engineer mortar (not used anymore). +* Removed bindings menu. +* Removed class help. + +=== Fixed === +* Don't allow building in prematch. +* Gas no longer goes through walls. +* Fixed the spamming weapon messages (e.g. Tranquiliser gun selected). +* Fixed the sentry gun menu to not close prematurely. +* Fixed broken ammo display. +* Fixed endless intermission bug. +* Fixed bug where players got stuck in intermission mode upon map change and hence could not respawn. +* Major code cleanup and rewrites. * Team player count in team selection menu. * Class player count (and class restrictions) in class selection menu. * Changing teams is now allowed. @@ -43,14 +252,16 @@ Scout * New Scanner menu where Scanner settings can be changed. * Caltrop Canisters no longer "explode" in your hands. * Remember Scanner status across deaths. +* `+special2` to toggle scanner. Sniper ------ * Sniper Rifle range increased. -* Automatic sensitivity scaling while zoomed in. -* Use the special button as a zoom button. -* Use mouse wheel to adjust zoom while zoomed in. -* Sniper Rifle now needs to be reloaded between shots. +* Automatic sensitivity scaling while zoomed in. Use the special button as a zoom button. Use mouse wheel to adjust zoom while zoomed in. Sniper Rifle now needs to be reloaded between shots. + +Soldier +------ +* New command `+rj` (or `+special2`) to rocket jump. Demolitions Man ------ @@ -58,6 +269,7 @@ Demolitions Man * Changed maximum detpipes allowed per team to 6 per demoman instead of 7 total. * Decrease /detpipe cooldown to 0.5 seconds instead of 0.8. * Stop detpacking by pressing last weapon bind. +* `+special2` to set 5 second detpack. Combat Medic ------ @@ -76,20 +288,24 @@ Heavy Weapons Guy Pyro ------ -* Add knockback to Flamethrower. +* Add air-blast special. +* Rebalance weapons. +* New command +rj (or +special2) to rocket jump. Spy ------ * Improved disguise menu. +* Auto-disguise (setinfo autodisguise 0/1/2). * Change color and skin in one sequence. * Last disguise (reachable through disguise menu or using /dlast alias). * Stop disguising by pressing last weapon bind. -* New aliases for changing team color (/denemy (if 2 teams), /dblue, /dred, /dyellow, /dgreen). -* New aliases for changing skin (/dscout, /dsniper, /dsoldier, etc). -* Build your own disguise aliases (e.g. alias bsniper "dblue; wait; dsniper" for blue sniper). +* New aliases for changing team color (`denemy` (if 2 teams), `dblue,` `dred,` `dyellow,` `dgreen,` `dlastspawn`). +* New aliases for changing skin (`dscout`, `dsniper`, `dsoldier`, etc). +* Build your own disguise aliases (e.g. `alias bsniper "dblue; wait; dsniper"` for blue sniper). * Spy can now feign death in air. * Spy now drops an empty backpack when feigning death. * A fake death message (but relevant to current situation) is now shown when feigning death. +* `+special2` to disguise as last spawned enemy. Engineer ------ @@ -98,12 +314,52 @@ Engineer * Upgrade/repair/restock Sentry Gun on spanner hit. * Dispenser automatically stocks nearby team members. * Dispenser restock rate increases with more players on team. -* Rocket ammo in Dispenser increase explosions more than before. * Repair Dispenser on spanner hit. * Dismantle Sentry Gun/Dispenser using build menu when standing close. * Stop building by pressing last weapon key. * Added message when Dispenser is destroyed. * Added dismantle message to show how many cells were returned to Engineer. -* Changed class special to detonate dispenser. +* New command toggledispenser (or `+special`) to build or detonate dispenser. +* New command togglesentry (or `+special2`) to build or detonate sentry. * Engineers can now only dismantle own buildings and rotate own Sentry Gun. -* Railgun no longer penetrates targets. +* `+special` to build or destroy dispenser. +* `+special2` to build or destroy sentry. + + +Development +------ + +Compile +------ +Compile with [FTEQCC](http://fte.triptohell.info/downloads) + +Ensure that `fteqcc64` is available in `$PATH` and: + +``` +make +``` + +Generate ctags +------ +``` +./generate_ctags.sh +``` + +Note: I got an error in vim: +``` +E431: Format error in tags file "tags" +Before byte 364464 +``` +I just removed the one line at that byte address and it works fine now. + +Note: Fixed in fteqcc 6010 + + +List assets +----- + +Only works in ssqc + +``` +fteqcc64 ./ssqc/progs.src -fdumpfilenames +``` diff --git a/actions.qc b/actions.qc deleted file mode 100644 index 677017a52..000000000 --- a/actions.qc +++ /dev/null @@ -1,587 +0,0 @@ -//======================================================== -// Non Class-Specific Impulse Commands -//======================================================== - -void () TeamFortress_ClipTick; - -void () TeamFortress_Discard = { - newmis = spawn(); - if ((self.playerclass == PC_SCOUT) - || (self.playerclass == PC_ENGINEER) - || (self.playerclass == PC_MEDIC)) { - newmis.ammo_rockets = self.ammo_rockets; - } else if ((self.playerclass == PC_SNIPER) - || (self.playerclass == PC_SPY)) { - newmis.ammo_rockets = self.ammo_rockets; - newmis.ammo_cells = self.ammo_cells; - } else if ((self.playerclass == PC_SOLDIER) - || (self.playerclass == PC_DEMOMAN)) { - newmis.ammo_cells = self.ammo_cells; - newmis.ammo_nails = self.ammo_nails; - } else if (self.playerclass == PC_HVYWEAP) { - newmis.ammo_rockets = self.ammo_rockets; - newmis.ammo_nails = self.ammo_nails; - } else if (self.playerclass == PC_PYRO) { - newmis.ammo_nails = self.ammo_nails; - } - - if (self.playerclass == PC_MEDIC && !medicaura && old_medikit) - newmis.ammo_cells = self.ammo_cells; - - if (!(newmis.ammo_shells + newmis.ammo_nails + newmis.ammo_rockets + newmis.ammo_cells)) { - dremove(newmis); - Menu_Drop(); - return; - } - if (newmis.ammo_shells) { - self.ammo_shells = 0; - } - if (newmis.ammo_nails) { - self.ammo_nails = 0; - } - if (newmis.ammo_rockets) { - self.ammo_rockets = 0; - } - if (newmis.ammo_cells) { - self.ammo_cells = 0; - } - W_SetCurrentAmmo(self); - sound(self, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); - newmis.enemy = self; - newmis.health = time; - newmis.weapon = 0; - newmis.movetype = MOVETYPE_TOSS; - newmis.solid = SOLID_TRIGGER; - newmis.classname = "ammobox"; - newmis.team_no = self.team_no; - makevectors(self.v_angle); - if (self.v_angle_x) { - newmis.velocity = v_forward * 400 + v_up * 200; - } else { - newmis.velocity = aim(self, 10000); - newmis.velocity = newmis.velocity * 400; - newmis.velocity_z = 200; - } - newmis.avelocity = '0 300 0'; - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin); - newmis.nextthink = time + 30; - newmis.think = SUB_Remove; - newmis.touch = TeamFortress_AmmoboxTouch; - setmodel(newmis, "progs/backpack.mdl"); -}; - -void () TeamFortress_SaveMe = { - local entity te, tl; - - if (self.last_saveme_sound < time) { - if (random() < 0.8) - sound(self, CHAN_WEAPON, "speech/saveme1.wav", 1, ATTN_NORM); - else - sound(self, CHAN_WEAPON, "speech/saveme2.wav", 1, ATTN_NORM); - - self.last_saveme_sound = time + 4; - } - te = find(world, classname, "player"); - while (te) { - if ((self == te) - || (te.playerclass == PC_MEDIC) - || (te.playerclass == PC_ENGINEER) - || (te.playerclass == PC_SPY)) { - if (((te.team_no == self.team_no) && (self.team_no != 0)) || - (te.playerclass == PC_SPY)) { - if (visible(te)) { - msg_entity = te; - tl = spawn(); - tl.origin = self.origin; - tl.origin_z = tl.origin_z + 32; - WriteByte(MSG_ONE, SVC_TEMPENTITY); - WriteByte(MSG_ONE, TE_LIGHTNING3); - WriteEntity(MSG_ONE, tl); - WriteCoord(MSG_ONE, tl.origin_x); - WriteCoord(MSG_ONE, tl.origin_y); - WriteCoord(MSG_ONE, tl.origin_z + 24); - WriteCoord(MSG_ONE, self.origin_x); - WriteCoord(MSG_ONE, self.origin_y); - WriteCoord(MSG_ONE, self.origin_z); - dremove(tl); - } - } - } - te = find(te, classname, "player"); - } - self.saveme_time = time; -}; - -void (entity pe_player, float f_type) CF_Identify = { - local vector v_source; - - v_source = pe_player.origin + v_forward * 10; - v_source_z = pe_player.absmin_z + pe_player.size_z * 0.7; - - traceline(v_source, v_source + v_forward * 2048, 0, pe_player); - if (trace_ent != world) { - local string s_id_string = "", s_class = "", s_name = ""; - local float f_health = 0, f_maxhealth = 0, f_armor = 0, f_maxarmor = 0, f_friendly = 0, f_fakefriendly = 0, f_sentryhealth = 0, f_maxsentryhealth = 0; - - // don't identify targets above water if player is under water - if (pe_player.waterlevel == 3 && !trace_ent.waterlevel) - return; - - // don't identify targets under water if player is above water - if (pe_player.waterlevel < 3 && trace_ent.waterlevel == 3) - return; - - // show as friendly if target is on your team or disguised as your team - if (pe_player.team_no) { - - if (pe_player.team_no == trace_ent.team_no) { - - // ignore teammates if type is set to enemies only - if (f_type == 3) - return; - - f_friendly = 1; - - } else if (pe_player.team_no == trace_ent.undercover_team) { - - // ignore teammates if type is set to enemies only - if (f_type == 3) - return; - - f_fakefriendly = 1; - - // ignore enemies if type is set to team only - } else if (f_type == 2) - return; - - } - - // alive player is found - if (trace_ent.classname == "player" && trace_ent.health) { - - // set class and name - s_class = TeamFortress_GetClassName(trace_ent.playerclass); - s_name = trace_ent.netname; - - // set health if you're a medic - if (pe_player.playerclass == PC_MEDIC) { - f_health = trace_ent.health; - f_maxhealth = trace_ent.max_health; - } - - // set armor if you're an engineer - else if (pe_player.playerclass == PC_ENGINEER) { - f_armor = trace_ent.armorvalue; - f_maxarmor = trace_ent.maxarmor; - } - - // target is an enemy spy - if (trace_ent.playerclass == PC_SPY && !f_friendly) { - - // don't identify feigning enemy spies - if (trace_ent.is_feigning) - return; - - // use undercover name if available - if (trace_ent.undercover_name != string_null) - s_name = trace_ent.undercover_name; - - // set class to undercover skin - if (trace_ent.undercover_skin) - s_class = TeamFortress_GetClassName(trace_ent.undercover_skin); - - } - - // dispenser is found - } else if (trace_ent.classname == "building_dispenser") { - - if (pe_player == trace_ent.real_owner) - s_name = "Your dispenser"; - else - s_name = strcat(trace_ent.real_owner.netname, "'s dispenser"); - - s_class = ""; - - // sentry gun is found - } else if (trace_ent.classname == "building_sentrygun" || trace_ent.classname == "building_sentrygun_base") { - - if (pe_player == trace_ent.real_owner) - s_name = "Your sentry gun"; - else { - s_name = strcat(trace_ent.real_owner.netname, "'s sentry gun"); - - if (pe_player.team_no == trace_ent.team_no) { - f_sentryhealth = trace_ent.health; - f_maxsentryhealth = trace_ent.max_health; - } - } - - s_class = ""; - - } else { - - return; - - } - - // set name + health (if medic) - if (f_maxhealth && (f_friendly || f_fakefriendly)) { - - s_id_string = strcat(s_name, "\n"); - s_id_string = strcat(s_id_string, ftos(f_health)); - if (id_extended) { - s_id_string = strcat(s_id_string, "/"); - s_id_string = strcat(s_id_string, ftos(f_maxhealth)); - } - s_id_string = strcat(s_id_string, " hp\n"); - - // set name + armor (if engineer) - } else if (f_maxarmor && (f_friendly || f_fakefriendly)) { - - s_id_string = strcat(s_name, "\n"); - s_id_string = strcat(s_id_string, ftos(f_armor)); - if (id_extended) { - s_id_string = strcat(s_id_string, "/"); - s_id_string = strcat(s_id_string, ftos(f_maxarmor)); - } - s_id_string = strcat(s_id_string, " armor\n"); - - // set name + health (if sentry + engineer) - } else if (f_maxsentryhealth) { - - s_id_string = strcat(s_name, "\n"); - if (id_extended) { - s_id_string = strcat(s_id_string, ftos(floor(f_sentryhealth))); - s_id_string = strcat(s_id_string, "/"); - s_id_string = strcat(s_id_string, ftos(floor(f_maxsentryhealth))); - s_id_string = strcat(s_id_string, " health"); - } - s_id_string = strcat(s_id_string, "\n"); - - // just set name (if other class) - } else { - - s_id_string = strcat("\n", s_name); - s_id_string = strcat(s_id_string, "\n"); - - } - - // set friendly/enemy - if (f_friendly || f_fakefriendly) - s_id_string = strcat(s_id_string, "Friendly"); - else - s_id_string = strcat(s_id_string, "Hostile"); - - // set class - if (s_class != "") { - s_id_string = strcat(s_id_string, " "); - s_id_string = strcat(s_id_string, s_class); - } - - pe_player.ident_time = time + 0.5; - - // don't update memory when the id string is the same - if (pe_player.ident_string == s_id_string) { - Status_Refresh(pe_player); - return; - } - - // refresh status bar - pe_player.ident_string = strzone(s_id_string); - Status_Refresh(pe_player); - - } - -}; - -void (float weap) TeamFortress_ReloadWeapon = { - local float reloadtime = 0; - local float reloadamount = 0; - local entity tWeapon, tClip; - - if (self.tfstate & TFSTATE_RELOADING) { - return; - } - if (weap == WEAP_SHOTGUN) { - if (self.ammo_shells == 0) { - sprint(self, PRINT_HIGH, "Out of shells\n"); - return; - } - if (self.reload_shotgun == 0) { - sprint(self, PRINT_HIGH, "Clip full\n"); - return; - } - if ((8 - self.reload_shotgun) == self.ammo_shells) { - sprint(self, PRINT_HIGH, "All shells are in the clip\n"); - return; - } - if (self.ammo_shells >= 1) { - self.current_weapon = weap; - if (self.reload_shotgun >= self.ammo_shells) - reloadamount = self.ammo_shells; - else - reloadamount = self.reload_shotgun; - Attack_Finished(0.4); - self.reload_clipsize = (8 - reloadamount); - reloadtime = (8 - reloadamount) / 8; - reloadtime = 2 - 2 * reloadtime; - self.reload_shotgun = 0; - if (self.ammo_shells < 8) { - self.reload_shotgun = 8 - self.ammo_shells; - } - sprint(self, PRINT_HIGH, "Reloading Shotgun...\n"); - self.tfstate = self.tfstate | TFSTATE_RELOADING; - Status_Refresh(self); - tWeapon = spawn(); - tWeapon.owner = self; - tWeapon.classname = "timer"; - tWeapon.nextthink = time + reloadtime; - tWeapon.think = W_Reload_shotgun; - self.weaponmodel = ""; - self.weaponframe = 0; - } else { - sprint(self, PRINT_HIGH, "Not enough ammo to reload\n"); - } - } else if (weap == WEAP_SUPER_SHOTGUN) { - if (self.ammo_shells == 0) { - sprint(self, PRINT_HIGH, "Out of shells\n"); - return; - } - if (self.reload_super_shotgun == 0) { - sprint(self, PRINT_HIGH, "Clip full\n"); - return; - } - if ((16 - self.reload_super_shotgun) == self.ammo_shells) { - sprint(self, PRINT_HIGH, "All shells are in the clip\n"); - return; - } - if (self.ammo_shells >= 2) { - self.current_weapon = weap; - if (self.reload_super_shotgun >= self.ammo_shells) - reloadamount = self.ammo_shells; - else - reloadamount = self.reload_super_shotgun; - Attack_Finished(0.7); - self.reload_clipsize = (16 - reloadamount); - reloadtime = (16 - reloadamount) / 16; - reloadtime = 3 - (3 * reloadtime); - self.reload_super_shotgun = 0; - if (self.ammo_shells < 16) { - self.reload_super_shotgun = 16 - self.ammo_shells; - } - sprint(self, PRINT_HIGH, "Reloading Super Shotgun...\n"); - self.tfstate = self.tfstate | TFSTATE_RELOADING; - Status_Refresh(self); - tWeapon = spawn(); - tWeapon.owner = self; - tWeapon.classname = "timer"; - tWeapon.nextthink = time + reloadtime; - tWeapon.think = W_Reload_super_shotgun; - self.weaponmodel = ""; - self.weaponframe = 0; - } else { - sprint(self, PRINT_HIGH, "Not enough ammo to reload\n"); - } - } else if (weap == WEAP_SNIPER_RIFLE) { - if (self.ammo_shells == 0) { - sprint(self, PRINT_HIGH, "Out of shells\n"); - return; - } - if (self.reload_sniper_rifle == 0) { - sprint(self, PRINT_HIGH, "Clip full\n"); - return; - } - if ((16 - self.reload_sniper_rifle) == self.ammo_shells) { - sprint(self, PRINT_HIGH, "All shells are in the clip\n"); - return; - } - if (self.ammo_shells >= 1) { - self.current_weapon = weap; - if (self.reload_super_shotgun >= self.ammo_shells) - reloadamount = self.ammo_shells; - else - reloadamount = self.reload_sniper_rifle; - Attack_Finished(1.5); - self.reload_clipsize = (1 - reloadamount); - self.reload_sniper_rifle = 0; - self.reload_sniper_ticks = 0; - if (self.ammo_shells < 1) { - self.reload_sniper_rifle = 1 - self.ammo_shells; - } - sprint(self, PRINT_HIGH, "Reloading Sniper Rifle...\n"); - self.tfstate = self.tfstate | TFSTATE_RELOADING; - Status_Refresh(self); - tWeapon = spawn(); - tWeapon.owner = self; - tWeapon.classname = "timer"; - tWeapon.nextthink = time + 1; // this will tick four times before finishing reload - tWeapon.think = W_Reload_sniper_rifle_tick; - self.weaponmodel = ""; - self.weaponframe = 0; - } else { - sprint(self, PRINT_HIGH, "Not enough ammo to reload\n"); - } - } else if (weap == WEAP_GRENADE_LAUNCHER) { - if (self.ammo_rockets == 0) { - sprint(self, PRINT_HIGH, "Out of grenades\n"); - return; - } - if (self.reload_grenade_launcher == 0) { - sprint(self, PRINT_HIGH, "Clip full\n"); - return; - } - if ((6 - self.reload_grenade_launcher) == - self.ammo_rockets) { - sprint(self, PRINT_HIGH, "All grenades are in the clip\n"); - return; - } - if (self.ammo_rockets >= 1) { - self.current_weapon = weap; - if (self.reload_grenade_launcher >= self.ammo_rockets) - reloadamount = self.ammo_rockets; - else - reloadamount = self.reload_grenade_launcher; - Attack_Finished(0.6); - self.reload_clipsize = (6 - reloadamount); - reloadtime = (6 - reloadamount) / 6; - reloadtime = 4 - 4 * reloadtime; - self.reload_grenade_launcher = 0; - if (self.ammo_rockets < 6) { - self.reload_grenade_launcher = - 6 - self.ammo_rockets; - } - sprint(self, PRINT_HIGH, "Reloading Grenade Launcher...\n"); - self.tfstate = self.tfstate | TFSTATE_RELOADING; - Status_Refresh(self); - tWeapon = spawn(); - tWeapon.owner = self; - tWeapon.classname = "timer"; - tWeapon.nextthink = time + reloadtime; - tWeapon.think = W_Reload_grenade_launcher; - self.weaponmodel = ""; - self.weaponframe = 0; - } else { - sprint(self, PRINT_HIGH, "Not enough ammo to reload\n"); - } - } else if (weap == WEAP_ROCKET_LAUNCHER) { - if (self.ammo_rockets == 0) { - sprint(self, PRINT_HIGH, "Out of rockets\n"); - return; - } - if (self.reload_rocket_launcher == 0) { - sprint(self, PRINT_HIGH, "Clip full\n"); - return; - } - if ((4 - self.reload_rocket_launcher) == - self.ammo_rockets) { - sprint(self, PRINT_HIGH, "All rockets are in the clip\n"); - return; - } - if (self.ammo_rockets >= 1) { - self.current_weapon = weap; - if (self.reload_rocket_launcher >= self.ammo_rockets) - reloadamount = self.ammo_rockets; - else - reloadamount = self.reload_rocket_launcher; - Attack_Finished(0.8); - self.reload_clipsize = (4 - reloadamount); - reloadtime = (4 - reloadamount) / 4; - reloadtime = 5 - (5 * reloadtime); - self.reload_rocket_launcher = 0; - if (self.ammo_rockets < 4) { - self.reload_rocket_launcher = - 4 - self.ammo_rockets; - } - sprint(self, PRINT_HIGH, "Reloading Rocket Launcher...\n"); - self.tfstate = self.tfstate | TFSTATE_RELOADING; - Status_Refresh(self); - tWeapon = spawn(); - tWeapon.owner = self; - tWeapon.classname = "timer"; - tWeapon.nextthink = time + reloadtime; - tWeapon.think = W_Reload_rocket_launcher; - self.weaponmodel = ""; - self.weaponframe = 0; - } else { - sprint(self, PRINT_HIGH, "Not enough ammo to reload\n"); - } - } - if (reloadamount && reloadtime) { - self.reload_time = time + reloadtime; - self.reload_tick = reloadtime / reloadamount; - tClip = spawn(); - tClip.owner = self; - tClip.classname = "timer"; - tClip.nextthink = time + self.reload_tick; - tClip.think = TeamFortress_ClipTick; - } -}; - -void () TeamFortress_ReloadCurrentWeapon = { - TeamFortress_ReloadWeapon(self.current_weapon); -}; - -void (float slot) TeamFortress_ReloadSlot = { - local float weap = 0; - - if (slot < 1 || slot > 3) - return; - - if (slot == 1) - weap = W_WeaponSlot1(); - else if (slot == 2) - weap = W_WeaponSlot2(); - else if (slot == 3) { - if (self.playerclass == PC_SCOUT || self.playerclass == PC_ENGINEER) - return; - weap = W_WeaponSlot3(); - } - - TeamFortress_ReloadWeapon(weap); -}; - -void () TeamFortress_ReloadNext = { - local float slot, reload = 0, weap = 0; - - // reload current slot first - slot = W_GetSlot(self.current_weapon); - if (CheckForAmmo(self.current_weapon)) { - TeamFortress_ReloadWeapon(self.current_weapon); - reload = 1; - return; - } - - // then go through each slot - for (slot = 1; slot < 4; slot++) { - if (slot == 1) - weap = W_WeaponSlot1(); - else if (slot == 2) - weap = W_WeaponSlot2(); - else if (slot == 3) { - if (self.playerclass == PC_SCOUT || self.playerclass == PC_ENGINEER) - break; - weap = W_WeaponSlot3(); - } - - if (CheckForAmmo(weap)) { - self.current_weapon = weap; - TeamFortress_ReloadWeapon(weap); - reload = 1; - break; - } - } - - if (!reload) - sprint(self, PRINT_HIGH, "All clips full\n"); -}; - -void () TeamFortress_ClipTick = { - if (time < self.owner.reload_time) { - self.owner.reload_clipsize = self.owner.reload_clipsize + 1; - Status_Refresh(self.owner); - self.nextthink = time + self.owner.reload_tick; - } else { - dremove(self); - } -}; diff --git a/admin.qc b/admin.qc deleted file mode 100644 index 7141a49fc..000000000 --- a/admin.qc +++ /dev/null @@ -1,227 +0,0 @@ - -void (entity p) BanCheater; - -void () Admin_CountPlayers = { - local string st; - local float nump; - - nump = TeamFortress_GetNoPlayers(); - st = ftos(nump); - sprint3(self, PRINT_HIGH, "Players in game: ", st, "\n"); - if (number_of_teams > 0) { - nump = TeamFortress_TeamGetNoPlayers(1); - st = ftos(nump); - sprint3(self, PRINT_HIGH, "Players in blue team: ", st, "\n"); - } - if (number_of_teams > 1) { - nump = TeamFortress_TeamGetNoPlayers(2); - st = ftos(nump); - sprint3(self, PRINT_HIGH, "Players in red team: ", st, "\n"); - } - if (number_of_teams > 2) { - nump = TeamFortress_TeamGetNoPlayers(3); - st = ftos(nump); - sprint3(self, PRINT_HIGH, "Players in yellow team: ", st, "\n"); - } - if (number_of_teams > 3) { - nump = TeamFortress_TeamGetNoPlayers(4); - st = ftos(nump); - sprint3(self, PRINT_HIGH, "Players in green team: ", st, "\n"); - } -}; - -void () Admin_ListIPs = { - if (TeamFortress_GetNoPlayers() <= 1) { - sprint(self, PRINT_HIGH, "No other players in the game\n"); - self.admin_use = world; - return; - } - self.admin_use = find(self.admin_use, classname, "player"); - while (self.admin_use != world) { - if ((self.admin_use.netname != string_null) && - (self.admin_use != self)) { - self.admin_use.ip = infokey(self.admin_use, "ip"); - sprint(self, PRINT_HIGH, self.admin_use.netname, " (", - self.admin_use.ip, ")\n"); - } - self.admin_use = find(self.admin_use, classname, "player"); - } - self.admin_use = find(self.admin_use, classname, "observer"); - while (self.admin_use != world) { - if ((self.admin_use.netname != string_null) && - (self.admin_use != self)) { - self.admin_use.ip = infokey(self.admin_use, "ip"); - sprint(self, PRINT_HIGH, self.admin_use.netname, " (", - self.admin_use.ip, ") (SPECTATOR)\n"); - } - self.admin_use = find(self.admin_use, classname, "observer"); - } - sprint(self, PRINT_HIGH, "End of player list\n"); - self.admin_use = world; -}; - -void () Admin_CycleDeal = { - if (TeamFortress_GetNoPlayers() <= 1) { - sprint(self, PRINT_HIGH, "No other players in the game\n"); - self.admin_use = world; - self.admin_mode = 0; - return; - } - if (self.admin_use.classname != "observer") { - self.admin_use = find(self.admin_use, classname, "player"); - while ((self.admin_use != world) && - ((self.admin_use.netname == string_null) - || (self.admin_use == self))) { - self.admin_use = find(self.admin_use, classname, "player"); - } - if (self.admin_use == world) { - self.admin_use = find(self.admin_use, classname, "observer"); - while ((self.admin_use != world) && - ((self.admin_use.netname == string_null) - || (self.admin_use == self))) { - self.admin_use = - find(self.admin_use, classname, "observer"); - } - } - } else { - self.admin_use = find(self.admin_use, classname, "observer"); - while ((self.admin_use != world) && - ((self.admin_use.netname == string_null) - || (self.admin_use == self))) { - self.admin_use = find(self.admin_use, classname, "observer"); - } - } - if (self.admin_use) { - self.admin_use.ip = infokey(self.admin_use, "ip"); - self.admin_mode = 1; - sprint(self, PRINT_HIGH, self.admin_use.netname, " (", - self.admin_use.ip, ")"); - if (self.admin_use.classname == "observer") { - sprint(self, PRINT_HIGH, " (spectator)"); - } - sprint(self, PRINT_HIGH, "\n kick/ban/next?\n"); - } else { - self.admin_mode = 0; - sprint(self, PRINT_HIGH, "End of player list\n"); - } -}; - -void () Admin_DoKick = { - bprint4(PRINT_HIGH, self.admin_use.netname, " was kicked by ", - self.netname, "\n"); - KickCheater(self.admin_use); - self.admin_mode = 0; - self.admin_use = world; -}; - -void () Admin_DoBan = { - bprint4(PRINT_HIGH, self.admin_use.netname, " was banned by ", - self.netname, "\n"); - BanCheater(self.admin_use); - self.admin_mode = 0; - self.admin_use = world; -}; - -void () CeaseFire_think = { - local entity te; - - if ((time > cb_ceasefire_time) && (self.weapon == 1) && - (cease_fire == 1)) { - cease_fire = 0; - bprint(PRINT_HIGH, "Resume fire\n"); - te = find(world, classname, "player"); - while (te) { - CenterPrint3(te, "Resume fire\nCalled by: ", - self.owner.netname, "\n"); - te.immune_to_check = time + 10; - te.tfstate = te.tfstate - (te.tfstate & 65536); - TeamFortress_SetSpeed(te); - te = find(te, classname, "player"); - } - dremove(self); - return; - } - if (!cease_fire) { - dremove(self); - return; - } - te = find(world, classname, "player"); - while (te) { - CenterPrint3(te, "CEASE FIRE\nCalled by: ", self.owner.netname, - "\n"); - te = find(te, classname, "player"); - } - self.nextthink = time + 5; -}; - -void () Admin_CeaseFire = { - local entity te; - - if (!cease_fire) { - cease_fire = 1; - bprint(PRINT_HIGH, "Cease fire\n"); - te = find(world, classname, "player"); - while (te) { - CenterPrint3(te, "Cease fire\nCalled by: ", self.netname, - "\n"); - te.immune_to_check = time + 10; - te.tfstate = te.tfstate | 65536; - TeamFortress_SetSpeed(te); - te = find(te, classname, "player"); - } - te = spawn(); - te.owner = self; - te.classname = "ceasefire"; - te.think = CeaseFire_think; - te.nextthink = time + 5; - } else { - te = find(world, classname, "ceasefire"); - if (te) - dremove(te); - - cease_fire = 0; - bprint(PRINT_HIGH, "Resume fire\n"); - te = find(world, classname, "player"); - while (te) { - CenterPrint3(te, "Resume fire\nCalled by: ", self.netname, - "\n"); - te.immune_to_check = time + 10; - te.tfstate = te.tfstate - (te.tfstate & 65536); - TeamFortress_SetSpeed(te); - te = find(te, classname, "player"); - } - } -}; - -void (entity p) CheckAutoKick = { - local float rnum; - local entity te; - - if ((p.teamkills >= autokick_kills) && (autokick_kills != 0)) { - bprint2(PRINT_HIGH, p.netname, - " was kicked for killing teammates\n"); - sprint(p, PRINT_HIGH, "You were kicked for killing teammates\n"); - KickCheater(p); - } else if (autokick_kills != 0) { - if (p.teamkills == (autokick_kills - 1)) { - sprint(p, PRINT_HIGH, - "Kill one more teammate, and you will get kicked out\n"); - } - rnum = 0; - te = find(world, classname, "ak_timer"); - while (te) { - if (te.owner == p) { - rnum = 1; - te = world; - } else - te = find(te, classname, "ak_timer"); - } - if (rnum == 0) { - te = spawn(); - te.classname = "ak_timer"; - te.owner = p; - te.think = autokick_think; - te.nextthink = time + autokick_time; - } - } -}; diff --git a/antilag.md b/antilag.md new file mode 100644 index 000000000..63301110c --- /dev/null +++ b/antilag.md @@ -0,0 +1,44 @@ +FortressOne Lag Compenstation +========= +FortressOne supports lag compensation to improve player experience when playing across continents or across oceans. + +tl;dr +-- +``` +rcon sv_antilag 1 +rcon localinfo project_weapons on +``` + +There are two types of lag compensation available, and these are configured at the server level. + +Hitscan weapons +--------------- +Enabled with: ```sv_antilag 1``` (default: 0) + +Hitscan weapons are weapons where an instant trace is emitted from the attacker in the direction they are shooting. In FortressOne, this means the shotgun and super-shotgun only. + +Hitscan weapons use a rollback mechanism based on antilag support in FTE. This means that players can aim using their crosshair without compensating for latency, as it would be if they were playing on a local server. + +To enable, set sv_antilag to 1. + +Projectile weapons + +------------------ + +- Enabled with: ```localinfo project_weapons on``` (default: off) +- Configurable: ```localinfo project_weapons_max_latency``` (default: 0.1) + +Projectile weapons are everything else. These are weapons which emit a projectile with a velocity that travel through space, eventually impacting on a player or the environment. +In FortressOne, projectiles can be fast-forwarded, or 'projected' forward based on the ping of the player shooting the rocket. This means that a player on a ping with 100ms will have their projectiles origin fast-forwarded to the point that they would have spawned if the player had zero latency. + +For example: +Rockets travel at 900units / second. + +A player with 0ms ping has the rocket launcher out and clicks the shoot. The server immediately receives the message and spawns the rocket. 500ms after clicking the shoot button, the rocket will be 450units away from the player. + +A player with a 100ms ping has the rocket launcher out and clicks to shoot. The server receives the message 50ms later. Without projectile fast-forwarding, after 500ms since clicking the shoot button, the projectile will be only 405units away from the player after the button was clicked - it has only had 450ms of existence and therefore 450ms of travel time. + +With project_weapons turned on, FortressOne will calculate where the rocket would be if it was spawned 50ms ago (in this case 45 units from player), and then regular handling of projectile motion takes over. + +This means that 500ms from the player clicking the shoot button, the rocket is 45 units (spawn position) + 405 (450ms * 900) units from the player position - 450 - ie, the same position had the player had 0 ping. + diff --git a/clan.qc b/clan.qc deleted file mode 100644 index 30f13555a..000000000 --- a/clan.qc +++ /dev/null @@ -1,458 +0,0 @@ - -void () PreMatch_Think = { - local float time_left; - local string st; - local entity te; - local entity oldself; - local entity gren; - - time_left = rint(cb_prematch_time - time); - if (time_left > 60) { - st = ftos(time_left / 60); - bprint2(PRINT_HIGH, st, " minutes left till match begins\n"); - if (time_left < 120) { - self.nextthink = time + time_left - 60; - } else { - self.nextthink = time + 60; - } - return; - } else if (time_left >= 59) { - bprint(PRINT_HIGH, "1 minute left till match begins\n"); - self.nextthink = time + 30; - return; - } else if (time_left >= 29) { - bprint(PRINT_HIGH, "30 seconds left till match begins\n"); - self.nextthink = time + 20; - return; - } else if (time_left > 1) { - st = ftos(time_left); - bprint2(PRINT_HIGH, st, " seconds\n"); - self.nextthink = time + 1; - return; - } else if (time_left > 0) { - bprint(PRINT_HIGH, "1 second\n"); - self.nextthink = time + 1; - return; - } - bprint(PRINT_HIGH, "Match begins now\n"); - if (game_locked) - bprint(PRINT_HIGH, "Game is now locked\n"); - - team4score = 0; - team3score = 0; - team2score = 0; - team1score = 0; - team4frags = 0; - team3frags = 0; - team2frags = 0; - team1frags = 0; - - te = find(world, classname, "player"); - while (te) { - oldself = self; - self = te; - if (self.tf_id == 0) { - last_id = last_id + 20 + random() * 10; - self.tf_id = rint(random() * 10 + last_id); - st = ftos(self.tf_id); - stuffcmd(self, "setinfo tf_id "); - stuffcmd(self, st); - stuffcmd(self, "\n"); - sprint(self, PRINT_HIGH, "Your battle ID is ", st, "\n"); - } - TeamFortress_RemoveTimers(); - self.frags = 0; - self.real_frags = 0; - gren = find(world, classname, "grenade"); - while (gren) { - if (gren.owner == self) { - gren.nextthink = time + 0.1; - } - gren = find(gren, classname, "grenade"); - } - TF_T_Damage(self, world, world, self.health + 1, 1, 0); - self = oldself; - te = find(te, classname, "player"); - } -}; - -void () DumpClanScores = { - local float winners; - local float no_teams; - local float printed; - local float ti; - local float tno; - local float teamfrags; - local string st; - local entity te; - local float t1_pl; - local float t1_unacc; - local float t2_pl; - local float t2_unacc; - local float t3_pl; - local float t3_unacc; - local float t4_pl; - local float t4_unacc; - - t1_pl = TeamFortress_TeamGetNoPlayers(1); - t2_pl = TeamFortress_TeamGetNoPlayers(2); - t3_pl = TeamFortress_TeamGetNoPlayers(3); - t4_pl = TeamFortress_TeamGetNoPlayers(4); - - printed = 0; - no_teams = 0; - if (t1_pl) { - no_teams = no_teams + 1; - } - if (t2_pl) { - no_teams = no_teams + 1; - } - if (t3_pl) { - no_teams = no_teams + 1; - } - if (t4_pl) { - no_teams = no_teams + 1; - } - if (no_teams < 2) { - return; - } - t4_unacc = 0; - t3_unacc = 0; - t2_unacc = 0; - t1_unacc = 0; - - ti = 0; - - teamfrags = toggleflags & (TFLAG_TEAMFRAGS | TFLAG_FULLTEAMSCORE); - - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 1) { - t1_unacc = t1_unacc + te.real_frags; - } else if (te.team_no == 2) { - t2_unacc = t2_unacc + te.real_frags; - } else if (te.team_no == 3) { - t3_unacc = t3_unacc + te.real_frags; - } else if (te.team_no == 4) { - t4_unacc = t4_unacc + te.real_frags; - } - te = find(te, classname, "player"); - } - t1_unacc = team1frags - t1_unacc; - t2_unacc = team2frags - t2_unacc; - t3_unacc = team3frags - t3_unacc; - t4_unacc = team4frags - t4_unacc; - - winners = TeamFortress_TeamGetWinner(); - - bprint(PRINT_HIGH, "\n\n=------= Match results =------=\n"); - if ((no_teams == 2) && (((team1score == team2score) && teamfrags) - || ((team1frags == team2frags) && !teamfrags))) { - bprint(PRINT_HIGH, " draw "); - } else if ((no_teams == 3) && - ((((team1score == team2score) == team3score) && teamfrags) - || (((team1frags == team2frags) == team3frags) && - !teamfrags))) { - bprint(PRINT_HIGH, " draw "); - } else if ((no_teams == 3) - && - (((((team1score == team2score) == team3score) == team4score) - && teamfrags) - || - ((((team1frags == team2frags) == team3frags) == team4frags) - && !teamfrags))) { - bprint(PRINT_HIGH, " draw "); - } else { - st = GetTeamName(winners); - bprint(PRINT_HIGH, st, " defeated "); - if ((winners != 1) && (t1_pl != 0)) { - st = GetTeamName(1); - bprint(PRINT_HIGH, st); - printed = printed + 1; - } - if ((winners != 2) && (t2_pl != 0)) { - st = GetTeamName(2); - if (printed == no_teams) { - bprint2(PRINT_HIGH, " and ", st); - } else if (printed) { - bprint2(PRINT_HIGH, ", ", st); - } else { - bprint(PRINT_HIGH, st); - } - printed = printed + 1; - } - if ((winners != 3) && (t3_pl != 0)) { - st = GetTeamName(3); - if (printed == no_teams) { - bprint2(PRINT_HIGH, " and ", st); - } else if (printed) { - bprint2(PRINT_HIGH, ", ", st); - } else { - bprint(PRINT_HIGH, st); - } - printed = printed + 1; - } - if ((winners != 4) && (t4_pl != 0)) { - st = GetTeamName(4); - if (printed == no_teams) { - bprint2(PRINT_HIGH, " and ", st); - } else if (printed) { - bprint2(PRINT_HIGH, ", ", st); - } else { - bprint(PRINT_HIGH, st); - } - } - } - bprint(PRINT_HIGH, "\n\n"); - - st = infokey(world, "dtf"); - if (st == string_null) - st = infokey(world, "dont_tweak_frags"); - - if (st != "on") { - if (teamfrags != 0) { - if (t1_pl > 0) { - printed = floor(team1score / t1_pl); - ti = 0; - if ((printed * t1_pl) != team1score) { - ti = team1score - (printed * t1_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 1) { - te.frags = printed; - } - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - te = find(te, classname, "player"); - } - } - if (t2_pl > 0) { - printed = floor((team2score / t2_pl)); - ti = 0; - if ((printed * t2_pl) != team2score) { - ti = team2score - (printed * t2_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 2) { - te.frags = printed; - } - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - te = find(te, classname, "player"); - } - } - if (t3_pl > 0) { - printed = floor((team3score / t3_pl)); - ti = 0; - if ((printed * t3_pl) != team3score) { - ti = team3score - (printed * t3_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 3) { - te.frags = printed; - } - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - te = find(te, classname, "player"); - } - } - if (t4_pl > 0) { - printed = floor((team4score / t4_pl)); - ti = 0; - if ((printed * t4_pl) != team4score) { - ti = team4score - (printed * t4_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 4) { - te.frags = printed; - } - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - te = find(te, classname, "player"); - } - } - } else { - if ((t1_pl > 0) && (t1_unacc > 0)) { - printed = floor(t1_unacc / t1_pl); - ti = 0; - if ((printed * t1_pl) != t1_unacc) { - ti = t1_unacc - (printed * t1_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 1) { - te.frags = te.real_frags + printed; - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - } - te = find(te, classname, "player"); - } - } - if ((t2_pl > 0) && (t2_unacc > 0)) { - printed = floor((t2_unacc / t2_pl)); - ti = 0; - if ((printed * t2_pl) != t2_unacc) { - ti = t2_unacc - (printed * t2_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 2) { - te.frags = te.real_frags + printed; - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - } - te = find(te, classname, "player"); - } - } - if ((t3_pl > 0) && (t3_unacc > 0)) { - printed = floor((t3_unacc / t3_pl)); - ti = 0; - if ((printed * t3_pl) != t3_unacc) { - ti = t3_unacc - (printed * t3_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 3) { - te.frags = te.real_frags + printed; - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - } - te = find(te, classname, "player"); - } - } - if ((t4_pl > 0) && (t4_unacc > 0)) { - printed = floor((t4_unacc / t4_pl)); - ti = 0; - if ((printed * t4_pl) != t4_unacc) { - ti = t4_unacc - (printed * t4_pl); - } - te = find(world, classname, "player"); - while (te) { - if (te.team_no == 4) { - te.frags = te.real_frags + printed; - if (ti) { - te.frags = te.frags + 1; - ti = ti - 1; - } - } - te = find(te, classname, "player"); - } - } - } - } - if (t1_pl > 0) { - bprint(PRINT_HIGH, "\n=------= Blue team results =------=\n"); - tno = TeamFortress_TeamGetNoPlayers(1); - st = ftos(tno); - bprint2(PRINT_HIGH, st, " players\n"); - st = ftos(team1frags); - bprint2(PRINT_HIGH, st, " frags, "); - st = ftos(t1_unacc); - bprint2(PRINT_HIGH, st, " unaccounted for\n"); - st = ftos(team1score); - bprint2(PRINT_HIGH, st, " team score\n"); - } - if (t2_pl > 0) { - bprint(PRINT_HIGH, "\n=------= Red team results =------=\n"); - tno = TeamFortress_TeamGetNoPlayers(2); - st = ftos(tno); - bprint2(PRINT_HIGH, st, " players\n"); - st = ftos(team2frags); - bprint2(PRINT_HIGH, st, " frags, "); - st = ftos(t2_unacc); - bprint2(PRINT_HIGH, st, " unaccounted for\n"); - st = ftos(team2score); - bprint2(PRINT_HIGH, st, " team score\n"); - } - if (t3_pl > 0) { - bprint(PRINT_HIGH, "\n=------= Yellow team results =------=\n"); - tno = TeamFortress_TeamGetNoPlayers(3); - st = ftos(tno); - bprint2(PRINT_HIGH, st, " players\n"); - st = ftos(team3frags); - bprint2(PRINT_HIGH, st, " frags, "); - st = ftos(t3_unacc); - bprint2(PRINT_HIGH, st, " unaccounted for\n"); - st = ftos(team3score); - bprint2(PRINT_HIGH, st, " team score\n"); - } - if (t4_pl > 0) { - bprint(PRINT_HIGH, "\n=------= Green team results =------=\n"); - tno = TeamFortress_TeamGetNoPlayers(4); - st = ftos(tno); - bprint2(PRINT_HIGH, st, " players\n"); - st = ftos(team4frags); - bprint2(PRINT_HIGH, st, " frags, "); - st = ftos(t4_unacc); - bprint2(PRINT_HIGH, st, " unaccounted for\n"); - st = ftos(team4score); - bprint2(PRINT_HIGH, st, " team score\n"); - } - te = find(world, classname, "player"); - while (te) { - st = infokey(te, "take_sshot"); - if (st != string_null) { - stuffcmd(te, "screenshot\n"); - } - te = find(te, classname, "player"); - } -}; - -void () TeamFortress_ShowIDs = { - local entity te; - local float got_one; - local string st; - - if (self.team_no == 0) { - sprint(self, PRINT_HIGH, "You are not in a team\n"); - return; - } - got_one = 0; - sprint(self, PRINT_HIGH, "Existing team member IDs:\n"); - - te = find(world, classname, "player"); - while (te) { - if (te.team_no == self.team_no) { - got_one = 1; - st = ftos(te.tf_id); - sprint(self, PRINT_HIGH, te.netname, " : ", st, "\n"); - } - te = find(te, classname, "player"); - } - - if (!got_one) - sprint(self, PRINT_HIGH, "None\n"); - got_one = 0; - sprint(self, PRINT_HIGH, "Disconnected team member IDs:\n"); - - te = find(world, classname, "ghost"); - while (te) { - if (te.team_no == self.team_no) { - got_one = 1; - st = ftos(te.tf_id); - sprint2(self, 2, st, "\n"); - } - te = find(te, classname, "ghost"); - } - - if (!got_one) - sprint(self, PRINT_HIGH, "None\n"); -}; diff --git a/combat.qc b/combat.qc deleted file mode 100644 index 34489ee88..000000000 --- a/combat.qc +++ /dev/null @@ -1,539 +0,0 @@ -void () T_MissileTouch; -void () info_player_start; -void (entity targ, entity attacker) ClientObituary; - -void (entity Goal, entity AP, float addb) DoResults; -float (entity Goal, entity AP) Activated; -float (entity targ, entity attacker, float damage) TeamEqualiseDamage; - -void () monster_death_use = { - if (self.flags & FL_FLY) - self.flags = self.flags - FL_FLY; - - if (self.flags & FL_SWIM) - self.flags = self.flags - FL_SWIM; - - if (!self.target) - return; - - activator = self.enemy; - SUB_UseTargets(); -}; - -float (entity targ, entity inflictor) CanDamage = { - if (targ.movetype == MOVETYPE_PUSH) { - - traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), - TRUE, self); - if (trace_fraction == 1) - return (TRUE); - if (trace_ent == targ) - return (TRUE); - return (FALSE); - } - - traceline(inflictor.origin, targ.origin, TRUE, self); - if (trace_fraction == 1) - return (TRUE); - traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); - if (trace_fraction == 1) - return (TRUE); - traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); - if (trace_fraction == 1) - return (TRUE); - traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); - if (trace_fraction == 1) - return (TRUE); - traceline(inflictor.origin, targ.origin + '15 -15 0', 1, self); - if (trace_fraction == 1) - return (TRUE); - return (FALSE); -}; - -void (entity targ, entity attacker) Killed = { - local entity oself; - - if (attacker == world && (targ.classname == "building_dispenser" || targ.classname == "building_sentrygun")) - attacker = targ; - - oself = self; - self = targ; - if (self.health < -99) - self.health = -99; - - if ((self.movetype == MOVETYPE_PUSH) || - (self.movetype == MOVETYPE_NONE)) { - - self.th_die(); - self = oself; - return; - } - - self.enemy = attacker; - - if (self.flags & FL_MONSTER) { - - killed_monsters = killed_monsters + 1; - WriteByte(MSG_ALL, SVC_KILLEDMONSTER); - } - - ClientObituary(self, attacker); - self.takedamage = DAMAGE_NO; - self.touch = SUB_Null; - - monster_death_use(); - self.th_die(); - self = oself; -}; - -void (entity targ, entity inflictor, entity attacker, - float damage) T_Damage = { - local vector dir; - local entity oldself, te; - local float save; - local float take; - - if (!targ.takedamage) - return; - - if (attacker.classname == "player") - damage = damage * 0.9; - - if (attacker.classname == "player") { - - if (attacker.super_damage_finished > time) - damage = damage * 4; - - if ((targ.classname != "player") && (targ.classname != "bot")) { - - if (!Activated(targ, attacker)) { - - if (targ.else_goal != 0) { - - te = Findgoal(targ.else_goal); - if (te) - AttemptToActivate(te, attacker, targ); - } - return; - } - } - } - damage_attacker = attacker; - if (teamplay & (TEAMPLAY_LESSSCOREHELP | TEAMPLAY_LESSPLAYERSHELP)) - damage = TeamEqualiseDamage(targ, attacker, damage); - - save = ceil(targ.armortype * damage); - if (save >= targ.armorvalue) { - - save = targ.armorvalue; - targ.armortype = 0; - targ.armorclass = 0; - targ.items = - targ.items - - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); - } - targ.armorvalue = targ.armorvalue - save; - take = ceil(damage - save); - - if (targ.flags & FL_CLIENT) { - targ.dmg_take = targ.dmg_take + take; - targ.dmg_save = targ.dmg_save + save; - targ.dmg_inflictor = inflictor; - } - if ((inflictor != world) && (targ.movetype == MOVETYPE_WALK)) { - - targ.immune_to_check = time + (damage / 20); - dir = targ.origin - ((inflictor.absmin + inflictor.absmax) * 0.5); - dir = normalize(dir); - if (((damage < 60) && - ((attacker.classname == "player") && - (targ.classname == "player"))) - && (attacker.netname != targ.netname)) - targ.velocity = targ.velocity + dir * damage * 11; - else - targ.velocity = targ.velocity + dir * damage * 8; - - if (((rj > 1) && - ((attacker.classname == "player") && - (targ.classname == "player"))) - && (attacker.netname == targ.netname)) - targ.velocity = (targ.velocity + ((dir * damage) * rj)); - } - if (targ.flags & FL_GODMODE) - return; - - if (targ.invincible_finished >= time) { - if (self.invincible_sound < time) { - - sound(targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); - self.invincible_sound = time + 2; - } - return; - } - if ((attacker.classname == "player") - && ((targ.classname == "player") || - (targ.classname == "building_sentrygun"))) { - - if (((targ.team_no > 0) && (targ.team_no == attacker.team_no)) && - (targ != attacker)) { - - if (teamplay & TEAMPLAY_NOEXPLOSIVE) - return; - else if (teamplay & TEAMPLAY_HALFEXPLOSIVE) - take = take / 2; - } - } - if ((take < 1) && (take != 0)) - take = 1; - - targ.health = targ.health - take; - - if (targ.armorvalue < 1) { - targ.armorclass = 0; - targ.armorvalue = 0; - } - if (targ.health <= 0) { - Killed(targ, attacker); - return; - } - - oldself = self; - self = targ; - - if (self.th_pain) { - self.th_pain(attacker, take); - if (skill >= 3) - self.pain_finished = time + 5; - } - self = oldself; -}; - -void (entity targ, entity inflictor, entity attacker, float damage, - float T_flags, float T_AttackType) TF_T_Damage = { - local vector dir; - local entity oldself; - local entity te; - local float save; - local float take; - local float olddmsg; - local float no_damage; - local float moment; - - if (targ.takedamage == 0) - return; - - if (T_AttackType & 256) { - targ.health = damage; - return; - } - if (cease_fire) - return; - - no_damage = 0; - - if (attacker.classname == "player") { - - damage = damage * 0.9; - - if (attacker.super_damage_finished > time) - damage = damage * 4; - - if ((targ.classname != "player") - && (targ.classname != "bot") - && (targ.classname != "building_sentrygun") - && (targ.classname != "building_dispenser") - && (targ.classname != "building_teleporter_entrance") - && (targ.classname != "building_teleporter_exit")) { - - if (!Activated(targ, attacker)) { - if (targ.else_goal != 0) { - te = Findgoal(targ.else_goal); - if (te) - AttemptToActivate(te, attacker, targ); - } - return; - } - } - } - damage_attacker = attacker; - - if (teamplay & (TEAMPLAY_LESSSCOREHELP | TEAMPLAY_LESSPLAYERSHELP)) - damage = TeamEqualiseDamage(targ, attacker, damage); - - if ((targ.armorclass != 0) && (T_AttackType != 0)) { - - if ((targ.armorclass & AT_SAVESHOT) && (T_AttackType & TF_TD_SHOT)) { - damage = floor(damage * 0.5); - } - if ((targ.armorclass & AT_SAVENAIL) && (T_AttackType & TF_TD_NAIL)) { - damage = floor(damage * 0.5); - } - if ((targ.armorclass & AT_SAVEEXPLOSION) && - (T_AttackType & TF_TD_EXPLOSION)) { - damage = floor(damage * 0.5); - } - if ((targ.armorclass & AT_SAVEELECTRICITY) && - (T_AttackType & TF_TD_ELECTRICITY)) { - damage = floor(damage * 0.5); - } - if ((targ.armorclass & AT_SAVEFIRE) && (T_AttackType & TF_TD_FIRE)) { - damage = floor(damage * 0.5); - } - } - - if (T_flags & TF_TD_IGNOREARMOUR) { - - take = damage; - save = 0; - - } else { - - save = ceil(targ.armortype * damage); - if ((attacker.classname == "player") - && (targ.team_no > 0) - && (targ.team_no == attacker.team_no) - && (targ != attacker) - && (T_flags & 2)) { - - if (T_AttackType & TF_TD_EXPLOSION) { - - if (teamplay & 1024) { - save = 0; - } else if (teamplay & 512) { - save = save / 2; - } - - } else if (teamplay & 256) - save = 0; - else if (teamplay & 128) - save = save / 2; - - } - if (save >= targ.armorvalue) { - - save = targ.armorvalue; - targ.armortype = 0; - targ.armorclass = 0; - targ.items = - targ.items - - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); - - } - targ.armorvalue = targ.armorvalue - save; - take = ceil(damage - save); - - } - if (targ.flags & FL_CLIENT) { - - targ.dmg_take = targ.dmg_take + take; - targ.dmg_save = targ.dmg_save + save; - targ.dmg_inflictor = inflictor; - - } - if ((inflictor != world) && (targ.movetype == MOVETYPE_WALK) && - !(targ.tfstate & 65536)) { - - if (deathmsg != DMSG_GREN_NAIL) { - - targ.immune_to_check = time + damage / 20; - dir = - targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; - dir = normalize(dir); - - if (targ.playerclass == PC_HVYWEAP) { - moment = damage / 4; - if (damage <= 50) - moment = 0; - } else - moment = damage; - - if ((moment < 60) - && (attacker.classname == "player") - && (targ.classname == "player") - && (attacker.netname != targ.netname)) { - - targ.velocity = targ.velocity + dir * moment * 11; - - } else - targ.velocity = targ.velocity + dir * moment * 8; - - if ((rj > 1) && (attacker.classname == "player") - && (targ.classname == "player") - && (attacker.netname == targ.netname)) { - - targ.velocity = targ.velocity + dir * moment * rj; - } - } - } - if (targ.flags & FL_GODMODE) - return; - - if (targ.invincible_finished >= time) { - if (self.invincible_sound < time) { - sound(targ, 3, "items/protect3.wav", 1, 1); - self.invincible_sound = time + 2; - } - return; - } - if ((attacker.classname == "player") - && ((targ.classname == "player") - || (targ.classname == "building_sentrygun") - || (targ.classname == "building_dispenser") - || (targ.classname == "building_teleporter_entrance") - || (targ.classname == "building_teleporter_exit"))) { - - if ((targ.team_no > 0) && (targ.team_no == attacker.team_no) - && (targ != attacker) && (T_flags & TF_TD_NOTTEAM)) { - - if (T_AttackType & TF_TD_EXPLOSION) { - - if (teamplay & TEAMPLAY_NOEXPLOSIVE) - no_damage = TRUE; - else if (teamplay & TEAMPLAY_HALFEXPLOSIVE) - take = take / 2; - - } else if (teamplay & TEAMPLAY_NODIRECT) - no_damage = TRUE; - else if (teamplay & TEAMPLAY_HALFDIRECT) - take = take / 2; - } - - } - - - if (targ.playerclass == PC_SPY) { - targ.attacked_by = attacker; - targ.feignmsg = deathmsg; - } - - if (T_flags & TF_TD_NOTSELF) - if (targ == attacker) - return; - - if (take < 1) - take = 1; - - take = rint(take); - - if (no_damage == 0) - targ.health = (targ.health - take); - - if ((attacker.classname == "player") - && ((targ.classname == "player") - || (targ.classname == "building_sentrygun") - || (targ.classname == "building_dispenser") - || (targ.classname == "building_teleporter_entrance") - || (targ.classname == "building_teleporter_exit"))) { - - if ((targ.team_no > 0) && (targ.team_no == attacker.team_no) - && (targ != attacker) && (T_flags & 2)) { - - olddmsg = deathmsg; - - if (T_AttackType & TF_TD_EXPLOSION) { - - deathmsg = 37; - if (teamplay & 16384) - TF_T_Damage(attacker, world, world, take, 1, 0); - else if (teamplay & 8192) - TF_T_Damage(attacker, world, world, take / 2, 1, 0); - - } else { - - deathmsg = 37; - if (teamplay & 4096) - TF_T_Damage(attacker, world, world, take, 1, 0); - else if (teamplay & 2048) - TF_T_Damage(attacker, world, world, take / 2, 1, 0); - } - deathmsg = olddmsg; - } - } - - if (no_damage == TRUE) - return; - - if (targ.armorvalue < 1) { - targ.armorclass = 0; - targ.armorvalue = 0; - } - - if (targ.health <= 0) { - - if ((inflictor.classname == "detpack") - && (inflictor.weaponmode == 1) && (inflictor.enemy == targ)) - deathmsg = DMSG_DETPACK_DIS; - - Killed(targ, attacker); - return; - } - - oldself = self; - self = targ; - - if (self.th_pain) { - self.th_pain(attacker, take); - if (skill >= 3) { - self.pain_finished = time + 5; - } - } - self = oldself; -}; - -void (entity inflictor, entity attacker, float damage, - entity ignore) T_RadiusDamage = { - local float points; - local entity head; - local vector org; - - head = findradius(inflictor.origin, damage + 40); - while (head) { - if (head != ignore) { - if (head.takedamage) { - - org = head.origin + (head.mins + head.maxs) * 0.5; - points = 0.5 * vlen(inflictor.origin - org); - if (points < 0) - points = 0; - points = damage - points; - if (head == attacker) - points = points * 0.75; - - if (points > 0) { - if (CanDamage(head, inflictor)) { - // shambler takes half damage from all explosions - if (head.classname == "monster_shambler") - T_Damage(head, inflictor, attacker, - points * 0.5); - else - TF_T_Damage(head, inflictor, attacker, points, - TF_TD_NOTTEAM, TF_TD_EXPLOSION); - } - } - } - } - head = head.chain; - } -}; - -void (entity attacker, float damage) T_BeamDamage = { - local float points; - local entity head; - - head = findradius(attacker.origin, damage + 40); - while (head) { - if (head.takedamage) { - - points = 0.5 * vlen(attacker.origin - head.origin); - if (points < 0) - points = 0; - points = damage - points; - if (head == attacker) - points = points * 0.5; - if (points > 0) - if (CanDamage(head, attacker)) - T_Damage(head, attacker, attacker, points); - } - head = head.chain; - } -}; diff --git a/csqc/csdefs.qc b/csqc/csdefs.qc new file mode 100644 index 000000000..877aebc01 --- /dev/null +++ b/csqc/csdefs.qc @@ -0,0 +1,1441 @@ +/* +This file was automatically generated by FTE QuakeWorld v1.03 +This file can be regenerated by issuing the following command: +pr_dumpplatform -O csdefs -Tcs +Available options: +-Ffte - target only FTE (optimations and additional extensions) +-Tnq - dump specifically NQ fields +-Tqw - dump specifically QW fields +-Tcs - dump specifically CSQC fields +-Tmenu - dump specifically menuqc fields +-Fdefines - generate #defines instead of constants +-Faccessors - use accessors instead of basic types via defines +-O - write to a different qc file +*/ +#pragma noref 1 +#pragma warning error Q101 /*too many parms*/ +#pragma warning error Q105 /*too few parms*/ +#pragma warning enable F301 /*non-utf-8 strings*/ +#pragma warning enable F302 /*uninitialised locals*/ +#pragma target FTE +#ifndef CSQC +#define CSQC +#endif +#define strbuf float +#define searchhandle float +#define hashtable float +#define infostring string +#define filestream float +entity self; /* The magic me */ +entity other; /* Valid in touch functions, this is the entity that we touched. */ +entity world; /* The null entity. Hurrah. Readonly after map spawn time. */ +float time; /* The current game time. Stops when paused. */ +float cltime; /* A local timer that ticks relative to local time regardless of latency, packetloss, or pause. */ +float frametime; /* The time since the last physics/render/input frame. */ +float player_localentnum; /* This is entity number the player is seeing from/spectating, or the player themself, can change mid-map. */ +float player_localnum; /* The 0-based player index, valid for getplayerkeyvalue calls. */ +float maxclients; /* Maximum number of player slots on the server. */ +float clientcommandframe; /* This is the input-frame sequence. frames < clientcommandframe have been sent to the server. frame==clientcommandframe is still being generated and can still change. */ +float servercommandframe; /* This is the input-frame that was last acknowledged by the server. Input frames greater than this should be applied to the player's entity. */ +string mapname; /* The short name of the map. */ +float intermission; +vector v_forward, v_up, v_right; +vector view_angles; +float trace_allsolid, trace_startsolid, trace_fraction; +vector trace_endpos, trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; +float input_timelength; +vector input_angles; +vector input_movevalues; +float input_buttons; +float input_impulse; +void end_sys_globals; +float pmove_onground; +vector pmove_vel; +vector pmove_org; +.float modelindex; +.vector absmin; +.vector absmax; +.float entnum; /* The entity number as its known on the server. */ +.float drawmask; /* Acts as a filter in the addentities call. */ +.float() predraw; /* Called by addentities after the filter and before the entity is actually drawn. Do your interpolation and animation in here. Should return one of the PREDRAW_* constants. */ +.float movetype; +.float solid; +.vector origin; +.vector oldorigin; +.vector velocity; +.vector angles; +.vector avelocity; +.float pmove_flags; +.string classname; +.float renderflags; +.string model; +.float frame; +.float frame1time; /* The absolute time into the animation/framegroup specified by .frame. */ +.float frame2; +.float frame2time; /* The absolute time into the animation/framegroup specified by .frame2. */ +.float lerpfrac; /* If 0, use frame1 only. If 1, use frame2 only. Mix them together for values between. */ +.float skin; +.float effects; +.vector mins; +.vector maxs; +.vector size; +.void() touch; +.void() think; +.void() blocked; +.float nextthink; +.entity chain; +.entity enemy; +.float flags; +.float colormap; +.entity owner; +void end_sys_fields; + +.vector punchangle; +.float gravity; +.float hull; /* Overrides the hull used by the entity for walkmove/movetogoal and not traceline/tracebox. */ +.entity movechain; /* This is a linked list of entities which will be moved whenever this entity moves, logically they are attached to this entity. */ +.void() chainmoved; /* Called when the entity is moved as a result of being part of another entity's .movechain */ +.void(float old, float new) contentstransition; /* This function is called when the entity moves between water and air. If specified, default splash sounds will be disabled allowing you to provide your own. */ +.float dimension_solid; /* This is the bitmask of dimensions which the entity is solid within. */ +.float dimension_hit; /* This is the bitmask of dimensions which the entity will be blocked by. If other.dimension_solid & self.dimension_hit, our traces will impact and not proceed. If its false, the traces will NOT impact, allowing self to pass straight through. */ +.float hitcontentsmask; +.float scale; /* Multiplier that resizes the entity. 1 is normal sized, 2 is double sized. scale 0 is remapped to 1. In SSQC, this is limited to 1/16th precision, with a maximum just shy of 16. */ +.float fatness; /* How many QuakeUnits to push the entity's verticies along their normals by. */ +.float alpha; /* The transparency of the entity. 1 means opaque, 0.0001 means virtually invisible. 0 is remapped to 1, for compatibility. */ +.float modelflags; /* Used to override the flags set in the entity's model. Should be set according to the MF_ constants. Use effects|=EF_NOMODELFLAGS to ignore the model's flags completely. The traileffectnum field is more versatile. */ +.void() customphysics; /* Called once each physics frame, overriding the entity's .movetype field and associated logic. You'll probably want to use tracebox to move it through the world. Be sure to call .think as appropriate. */ +.entity tag_entity; +.float tag_index; +.float skeletonindex; /* This object serves as a container for the skeletal bone states used to override the animation data. */ +.vector colormod; +.vector glowmod; +.vector gravitydir; /* Specifies the direction in which gravity acts. Must be normalised. '0 0 0' also means down. Use '0 0 1' if you want the player to be able to run on ceilings. */ +.vector(vector org, vector ang) camera_transform; /* Provides portal transform information for portal surfaces attached to this entity. Also used to open up pvs in ssqc. */ +.float geomtype; +.float friction; +.float erp; +.float jointtype; +.float mass; +.float bouncefactor; +.float bouncestop; +.float idealpitch; +.float pitch_speed; +.float forceshader; /* Contains a shader handle used to replace all surfaces upon the entity. */ +.float baseframe; /* See basebone */ +.float baseframe2; /* See basebone */ +.float baseframe1time; /* See basebone */ +.float baseframe2time; /* See basebone */ +.float baselerpfrac; /* See basebone */ +.float basebone; /* The base* frame animations are equivelent to their non-base versions, except that they only affect bone numbers below the 'basebone' value. This means that the base* animation can affect the legs of a skeletal model independantly of the normal animation fields affecting the torso area. For more complex animation than this, use skeletal objects. */ +.float bonecontrol1; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol2; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol3; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol4; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol5; /* Halflife model format bone controller. This typically affects the mouth. */ +.float subblendfrac; /* Weird animation value specific to halflife models. On player models, this typically affects the spine's pitch. */ +.float basesubblendfrac; /* See basebone */ +noref void(float reqid, float responsecode, string resourcebody) URI_Get_Callback; /* Called as an eventual result of the uri_get builtin. */ +noref void(float apilevel, string enginename, float engineversion) CSQC_Init; /* Called at startup. enginename and engineversion are arbitary hints and can take any form. enginename should be consistant between revisions, but this cannot truely be relied upon. */ +noref void() CSQC_WorldLoaded; /* Called after model+sound precaches have been executed. Gives a chance for the qc to read the entity lump from the bsp. */ +noref void() CSQC_Shutdown; /* Specifies that the csqc is going down. Save your persistant settings here. */ +noref void(float vwidth, float vheight, float notmenu) CSQC_UpdateView; /* Called every single video frame. The CSQC is responsible for rendering the entire screen. */ +noref void(string msg) CSQC_Parse_StuffCmd; /* Gives the CSQC a chance to intercept stuffcmds. Use the tokenize builtin to parse the message. Unrecognised commands would normally be localcmded, but its probably better to drop unrecognised stuffcmds completely. */ +noref float(string msg) CSQC_Parse_CenterPrint; /* Gives the CSQC a chance to intercept centerprints. Return true if you wish the engine to otherwise ignore the centerprint. */ +noref void(string printmsg, float printlvl) CSQC_Parse_Print; /* Gives the CSQC a chance to intercept sprint/bprint builtin calls. CSQC should filter by the client's current msg setting and then pass the message on to the print command, or handle them itself. */ +noref void() CSQC_Parse_Event; /* Called when the client receives an SVC_CGAMEPACKET. The csqc should read the data or call the error builtin if it does not recognise the message. */ +noref float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent; /* Called whenever a key is pressed, the mouse is moved, etc. evtype will be one of the IE_* constants. The other arguments vary depending on the evtype. Key presses are not guarenteed to have both scan and unichar values set at the same time. */ +noref void() CSQC_Input_Frame; /* Called just before each time clientcommandframe is updated. You can edit the input_* globals in order to apply your own player inputs within csqc, which may allow you a convienient way to pass certain info to ssqc. */ +noref float(string cmd) CSQC_ConsoleCommand; /* Called if the user uses any console command registed via registercommand. */ +noref float(string text, string info) CSQC_ConsoleLink; /* Called if the user clicks a ^[text\infokey\infovalue^] link. Use infoget to read/check each supported key. Return true if you wish the engine to not attempt to handle the link itself. */ +noref void(float isnew) CSQC_Ent_Update; +noref void() CSQC_Ent_Remove; +noref float(float entnum, float channel, string soundname, float vol, float attenuation, vector pos, float pitchmod) CSQC_Event_Sound; +noref float(string resname, string restype) CSQC_LoadResource; /* Called each time some resource is being loaded. CSQC can invoke various draw calls to provide a loading screen, until WorldLoaded is called. */ +noref float() CSQC_Parse_TempEntity; /* Please don't use this. Use CSQC_Parse_Event and multicasts instead. */ +noref void(string cmdtext) GameCommand; +var float physics_mode = 2; /* 0: original csqc - physics are not run +1: DP-compat. Thinks occur, but not true movetypes. +2: movetypes occur just as they do in ssqc. */ +float gamespeed; /* Set by the engine, this is the value of the sv_gamespeed cvar */ +float numclientseats; /* This is the number of splitscreen clients currently running on this client. */ +var vector drawfontscale = '1 1 0'; /* Specifies a scaler for all text rendering. There are other ways to implement this. */ +float drawfont; /* Allows you to choose exactly which font is to be used to draw text. Fonts can be registered/allocated with the loadfont builtin. */ +const float FONT_DEFAULT = 0; +const float TRUE = 1; +const float FALSE = 0; /* File not found... */ +const float M_PI = 3.14159; +const float MOVETYPE_NONE = 0; +const float MOVETYPE_WALK = 3; +const float MOVETYPE_STEP = 4; +const float MOVETYPE_FLY = 5; +const float MOVETYPE_TOSS = 6; +const float MOVETYPE_PUSH = 7; +const float MOVETYPE_NOCLIP = 8; +const float MOVETYPE_FLYMISSILE = 9; +const float MOVETYPE_BOUNCE = 10; +const float MOVETYPE_BOUNCEMISSILE = 11; +const float MOVETYPE_FOLLOW = 12; +const float MOVETYPE_WALLWALK = 31; /* Players using this movetype will be able to orient themselves to walls, and then run up them. */ +const float MOVETYPE_PHYSICS = 32; /* Enable the use of ODE physics upon this entity. */ +const float SOLID_NOT = 0; +const float SOLID_TRIGGER = 1; +const float SOLID_BBOX = 2; +const float SOLID_SLIDEBOX = 3; +const float SOLID_BSP = 4; +const float SOLID_CORPSE = 5; +const float SOLID_LADDER = 20; /* Obsolete and may be removed at some point. Use skin=CONTENT_LADDER and solid_bsp or solid_trigger instead. */ +const float SOLID_PHYSICS_BOX = 32; +const float SOLID_PHYSICS_SPHERE = 33; +const float SOLID_PHYSICS_CAPSULE = 34; +const float SOLID_PHYSICS_TRIMESH = 35; +const float SOLID_PHYSICS_CYLINDER = 36; +const float GEOMTYPE_NONE = -1; +const float GEOMTYPE_SOLID = 0; +const float GEOMTYPE_BOX = 1; +const float GEOMTYPE_SPHERE = 2; +const float GEOMTYPE_CAPSULE = 3; +const float GEOMTYPE_TRIMESH = 4; +const float GEOMTYPE_CYLINDER = 5; +const float GEOMTYPE_CAPSULE_X = 6; +const float GEOMTYPE_CAPSULE_Y = 7; +const float GEOMTYPE_CAPSULE_Z = 8; +const float GEOMTYPE_CYLINDER_X = 9; +const float GEOMTYPE_CYLINDER_Y = 10; +const float GEOMTYPE_CYLINDER_Z = 11; +const float JOINTTYPE_FIXED = -1; +const float JOINTTYPE_POINT = 1; +const float JOINTTYPE_HINGE = 2; +const float JOINTTYPE_SLIDER = 3; +const float JOINTTYPE_UNIVERSAL = 4; +const float JOINTTYPE_HINGE2 = 5; +const float GE_MAXENTS = -1; /* Valid for getentity, ignores the entity argument. Returns the maximum number of entities which may be valid, to avoid having to poll 65k when only 100 are used. */ +const float GE_ACTIVE = 0; /* Valid for getentity. Returns whether this entity is known to the client or not. */ +const float GE_ORIGIN = 1; /* Valid for getentity. Returns the interpolated .origin. */ +const float GE_FORWARD = 2; /* Valid for getentity. Returns the interpolated forward vector. */ +const float GE_RIGHT = 3; /* Valid for getentity. Returns the entity's right vector. */ +const float GE_UP = 4; /* Valid for getentity. Returns the entity's up vector. */ +const float GE_SCALE = 5; /* Valid for getentity. Returns the entity .scale. */ +const float GE_ORIGINANDVECTORS = 6; /* Valid for getentity. Returns interpolated .origin, but also sets v_forward, v_right, and v_up accordingly. Use vectoangles(v_forward,v_up) to determine the angles. */ +const float GE_ALPHA = 7; /* Valid for getentity. Returns the entity alpha. */ +const float GE_COLORMOD = 8; /* Valid for getentity. Returns the colormod vector. */ +const float GE_PANTSCOLOR = 9; /* Valid for getentity. Returns the entity's lower color (from .colormap), as a palette range value. */ +const float GE_SHIRTCOLOR = 10; /* Valid for getentity. Returns the entity's lower color (from .colormap), as a palette range value. */ +const float GE_SKIN = 11; /* Valid for getentity. Returns the entity's .skin index. */ +const float GE_MINS = 12; /* Valid for getentity. Guesses the entity's .min vector. */ +const float GE_MAXS = 13; /* Valid for getentity. Guesses the entity's .max vector. */ +const float GE_ABSMIN = 14; /* Valid for getentity. Guesses the entity's .absmin vector. */ +const float GE_ABSMAX = 15; /* Valid for getentity. Guesses the entity's .absmax vector. */ +const float CONTENT_EMPTY = -1; +const float CONTENT_SOLID = -2; +const float CONTENT_WATER = -3; +const float CONTENT_SLIME = -4; +const float CONTENT_LAVA = -5; +const float CONTENT_SKY = -6; +const float CONTENT_LADDER = -16; /* If this value is assigned to a solid_bsp's .skin field, the entity will become a ladder volume. */ +const float CHAN_AUTO = 0; /* The automatic channel, play as many sounds on this channel as you want, and they'll all play, however the other channels will replace each other. */ +const float CHAN_WEAPON = 1; +const float CHAN_VOICE = 2; +const float CHAN_ITEM = 3; +const float CHAN_BODY = 4; +const float ATTN_NONE = 0; /* Sounds with this attenuation can be heard throughout the map */ +const float ATTN_NORM = 1; /* Standard attenuation */ +const float ATTN_IDLE = 2; /* Extra attenuation so that sounds don't travel too far. */ +const float ATTN_STATIC = 3; /* Even more attenuation to avoid torches drowing out everything else throughout the map. */ +const string INFOKEY_P_PING = "ping"; /* The player's ping time, in milliseconds. */ +const string INFOKEY_P_NAME = "name"; /* The player's name. */ +const string INFOKEY_P_TOPCOLOR = "topcolor"; /* The player's upper/shirt colour (palette index). */ +const string INFOKEY_P_BOTTOMCOLOR = "bottomcolor"; /* The player's lower/pants/trouser colour (palette index). */ +const string INFOKEY_P_TOPCOLOR_RGB = "topcolor_rgb"; /* The player's upper/shirt colour as an rgb value in a format usable with stov. */ +const string INFOKEY_P_BOTTOMCOLOR_RGB = "bottomcolor_rgb"; /* The player's lower/pants/trouser colour as an rgb value in a format usable with stov. */ +const string INFOKEY_P_MUTED = "ignored"; /* 0: we can see the result of the player's say/say_team commands. 1: we see no say/say_team messages from this player. Use the ignore command to toggle this value. */ +const string INFOKEY_P_VOIP_MUTED = "vignored"; /* 0: we can hear this player when they speak (assuming voip is generally enabled). 1: we ignore everything this player says. Use cl_voip_mute to change the values. */ +const string INFOKEY_P_ENTERTIME = "entertime"; /* Reads the timestamp at which the player entered the game, in terms of csqc's time global. */ +const string INFOKEY_P_FRAGS = "frags"; /* Reads a player's frag count. */ +const string INFOKEY_P_PACKETLOSS = "pl"; /* Reads a player's packetloss, as a percentage. */ +const string INFOKEY_P_VOIPSPEAKING = "voipspeaking"; /* Boolean value that says whether the given player is currently sending voice information. */ +const string INFOKEY_P_VOIPLOUDNESS = "voiploudness"; /* Only valid for the local player. Gives a value between 0 and 1 to indicate to the user how loud their mic is. */ +const float FL_FLY = 1; +const float FL_SWIM = 2; +const float FL_CLIENT = 8; +const float FL_INWATER = 16; +const float FL_MONSTER = 32; +const float FL_ITEM = 256; +const float FL_ONGROUND = 512; +const float FL_PARTIALGROUND = 1024; +const float FL_WATERJUMP = 2048; +const float FL_JUMPRELEASED = 4096; +const float FL_FINDABLE_NONSOLID = 16384; /* Allows this entity to be found with findradius */ +const float MOVE_NORMAL = 0; +const float MOVE_NOMONSTERS = 1; /* The trace will ignore all non-solid_bsp entities. */ +const float MOVE_MISSILE = 2; /* The trace will use a bbox size of +/- 15 against entities with FL_MONSTER set. */ +const float MOVE_HITMODEL = 4; /* Traces will impact the actual mesh of the model instead of merely their bounding box. Should generally only be used for tracelines. Note that this flag is unreliable as an object can animate through projectiles. The bounding box MUST be set to completely encompass the entity or those extra areas will be non-solid (leaving a hole for things to go through). */ +const float MOVE_TRIGGERS = 16; /* This trace type will impact only triggers. It will ignore non-solid entities. */ +const float MOVE_EVERYTHING = 32; /* This type of trace will hit solids and triggers alike. Even non-solid entities. */ +const float MOVE_ENTCHAIN = 128; /* Returns a list of entities impacted via the trace_ent.chain field */ +const float RESTYPE_MODEL = 0; /* RESTYPE_* constants are used as arguments with the resourcestatus builtin. */ +const float RESTYPE_SOUND = 1; /* precache_sound */ +const float RESTYPE_PARTICLE = 2; /* particleeffectnum */ +const float RESTYPE_PIC = 3; /* precache_pic. Status results are an amalgomation of the textures used by the named shader. */ +const float RESTYPE_SKIN = 4; /* setcustomskin */ +const float RESTYPE_TEXTURE = 5; /* Individual textures within shaders. These are not directly usable, but may be named as part of a skin file, or a shader. */ +const float RESSTATE_NOTKNOWN = 0; /* RESSTATE_* constants are return values from the resourcestatus builtin. The engine doesn't know about the resource if it is in this state. This means you will need to precache it. Attempting to use it anyway may result in warnings, errors, or silently succeed, depending on engine version and resource type. */ +const float RESSTATE_NOTLOADED = 1; /* The resource was precached, but has been flushed and there has not been an attempt to reload it. If you use the resource normally, chances are it'll be loaded but at the cost of a stall. */ +const float RESSTATE_LOADING = 2; /* Resources in this this state are queued for loading, and will be loaded at the engine's convienience. If you attempt to query the resource now, the engine will stall until the result is available. sounds in this state may be delayed, while models/pics/shaders may be invisible. */ +const float RESSTATE_FAILED = 3; /* Resources in this state are unusable/could not be loaded. You will get placeholders or dummy results. Queries will not stall the engine. The engine may display placeholder content. */ +const float RESSTATE_LOADED = 4; /* Resources in this state are finally usable, everything will work okay. Hurrah. Queries will not stall the engine. */ +const float EF_BRIGHTFIELD = 1; +const float EF_MUZZLEFLASH = 2; +const float EF_BRIGHTLIGHT = 4; +const float EF_DIMLIGHT = 8; +const float EF_ADDITIVE = 32; +const float EF_BLUE = 64; +const float EF_RED = 128; +const float EF_FULLBRIGHT = 512; +const float EF_NODEPTHTEST = 8192; +const float MF_ROCKET = 1; +const float MF_GRENADE = 2; +const float MF_GIB = 4; +const float MF_ROTATE = 8; +const float MF_TRACER = 16; +const float MF_ZOMGIB = 32; +const float MF_TRACER2 = 64; +const float MF_TRACER3 = 128; +const float PFLAGS_NOSHADOW = 1; /* Associated RT lights attached will not cast shadows, making them significantly faster to draw. */ +const float PFLAGS_CORONA = 2; /* Enables support of coronas on the associated rtlights. */ +const float EV_STRING = 1; +const float EV_FLOAT = 2; +const float EV_VECTOR = 3; +const float EV_ENTITY = 4; +const float EV_FUNCTION = 6; +const float EV_POINTER = 7; +const float EV_INTEGER = 8; +const float EV_VARIANT = 9; +hashtable gamestate; /* Special hash table index for hash_add and hash_get. Entries in this table will persist over map changes (and doesn't need to be created/deleted). */ +const float HASH_REPLACE = 256; /* Used with hash_add. Attempts to remove the old value instead of adding two values for a single key. */ +const float STAT_HEALTH = 0; +const float STAT_WEAPON = 2; +const float STAT_AMMO = 3; +const float STAT_ARMOR = 4; +const float STAT_WEAPONFRAME = 5; +const float STAT_SHELLS = 6; +const float STAT_NAILS = 7; +const float STAT_ROCKETS = 8; +const float STAT_CELLS = 9; +const float STAT_ACTIVEWEAPON = 10; +const float STAT_TOTALSECRETS = 11; +const float STAT_TOTALMONSTERS = 12; +const float STAT_FOUNDSECRETS = 13; +const float STAT_KILLEDMONSTERS = 14; +const float STAT_ITEMS = 15; +const float STAT_VIEWHEIGHT = 16; +const float STAT_VIEW2 = 20; /* This stat contains the number of the entity in the server's .view2 field. */ +const float STAT_VIEWZOOM = 21; +const float VF_MIN = 1; /* The top-left of the 3d viewport in screenspace. The VF_ values are used via the setviewprop/getviewprop builtins. */ +const float VF_MIN_X = 2; +const float VF_MIN_Y = 3; +const float VF_SIZE = 4; /* The width+height of the 3d viewport in screenspace. */ +const float VF_SIZE_X = 5; +const float VF_SIZE_Y = 6; +const float VF_VIEWPORT = 7; /* vector+vector. Two argument shortcut for VF_MIN and VF_SIZE */ +const float VF_FOV = 8; /* sets both fovx and fovy. consider using afov instead. */ +const float VF_FOVX = 9; /* horizontal field of view. does not consider aspect at all. */ +const float VF_FOVY = 10; /* vertical field of view. does not consider aspect at all. */ +const float VF_ORIGIN = 11; /* The origin of the view. Not of the player. */ +const float VF_ORIGIN_X = 12; +const float VF_ORIGIN_Y = 13; +const float VF_ORIGIN_Z = 14; +const float VF_ANGLES = 15; /* The angles the view will be drawn at. Not the angle the client reports to the server. */ +const float VF_ANGLES_X = 16; +const float VF_ANGLES_Y = 17; +const float VF_ANGLES_Z = 18; +const float VF_DRAWWORLD = 19; /* boolean. If set to 1, the engine will draw the world and static/persistant rtlights. If 0, the world will be skipped and everything will be fullbright. */ +const float VF_DRAWENGINESBAR = 20; /* boolean. If set to 1, the sbar will be drawn, and viewsize will be honoured automatically. */ +const float VF_DRAWCROSSHAIR = 21; /* boolean. If set to 1, the engine will draw its default crosshair. */ +const float VF_CL_VIEWANGLES = 33; +const float VF_CL_VIEWANGLES_X = 34; +const float VF_CL_VIEWANGLES_Y = 35; +const float VF_CL_VIEWANGLES_Z = 36; +const float VF_PERSPECTIVE = 200; /* 1: regular rendering. Fov specifies the angle. 0: isometric-style. Fov specifies the number of Quake Units each side of the viewport. */ +const float VF_LPLAYER = 202; /* The 'seat' number, used when running splitscreen. */ +const float VF_AFOV = 203; /* Aproximate fov. Matches the 'fov' cvar. The engine handles the aspect ratio for you. */ +const float VF_SCREENVSIZE = 204; /* Provides a reliable way to retrieve the current virtual screen size (even if the screen is automatically scaled to retain aspect). */ +const float VF_SCREENPSIZE = 205; /* Provides a reliable way to retrieve the current physical screen size (cvars need vid_restart for them to take effect). */ +const float VF_VIEWENTITY = 206; /* Changes the RF_EXTERNALMODEL flag on entities to match the new selection, and removes entities flaged with RF_VIEWENTITY. Requires cunning use of .entnum and typically requires calling addentities(MASK_VIEWMODEL) too. */ +const float VF_RT_DESTCOLOUR = 212; /* The texture name to write colour info into, this includes both 3d and 2d drawing. +Additional arguments are: format (rgba8=1,rgba16f=2,rgba32f=3), sizexy. +Written to by both 3d and 2d rendering. +Note that any rendertarget textures may be destroyed on video mode changes or so. Shaders can name render targets by prefixing texture names with '$rt:', or $sourcecolour. */ +const float VF_RT_SOURCECOLOUR = 209; /* The texture name to use with shaders that specify a $sourcecolour map. */ +const float VF_RT_DEPTH = 210; /* The texture name to use as a depth buffer. Also used for shaders that specify $sourcedepth. 1-based. Additional arguments are: format (16bit=4,24bit=5,32bit=6), sizexy. */ +const float VF_RT_RIPPLE = 211; /* The texture name to use as a ripplemap (target for shaders with 'sort ripple'). Also used for shaders that specify $ripplemap. 1-based. Additional arguments are: format, sizexy. */ +const float RF_VIEWMODEL = 1; /* Specifies that the entity is a view model, and that its origin is relative to the current view position. These entities are also subject to viewweapon bob. */ +const float RF_EXTERNALMODEL = 2; /* Specifies that this entity should be displayed in mirrors (and may still cast shadows), but will not otherwise be visible. */ +const float RF_DEPTHHACK = 4; /* Hacks the depth values such that the entity uses depth values as if it were closer to the screen. This is useful when combined with viewmodels to avoid weapons poking in to walls. */ +const float RF_ADDITIVE = 8; /* Shaders from this entity will temporarily be hacked to use an additive blend mode instead of their normal blend mode. */ +const float RF_USEAXIS = 16; /* The entity will be oriented according to the current v_forward+v_right+v_up vector values instead of the entity's .angles field. */ +const float RF_NOSHADOW = 32; /* This entity will not cast shadows. Often useful on view models. */ +const float RF_FRAMETIMESARESTARTTIMES = 64; /* Specifies that the frame1time, frame2time field are timestamps (denoting the start of the animation) rather than time into the animation. */ +const float IE_KEYDOWN = 0; /* Specifies that a key was pressed. Second argument is the scan code. Third argument is the unicode (printable) char value. Fourth argument denotes which keyboard(or mouse, if its a mouse 'scan' key) the event came from. Note that some systems may completely separate scan codes and unicode values, with a 0 value for the unspecified argument. */ +const float IE_KEYUP = 1; /* Specifies that a key was released. Arguments are the same as IE_KEYDOWN. On some systems, this may be fired instantly after IE_KEYDOWN was fired. */ +const float IE_MOUSEDELTA = 2; /* Specifies that a mouse was moved (touch screens and tablets typically give IE_MOUSEABS events instead, use _windowed_mouse 0 to test code to cope with either). Second argument is the X displacement, third argument is the Y displacement. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_MOUSEABS = 3; /* Specifies that a mouse cursor or touch event was moved to a specific location relative to the virtual screen space. Second argument is the new X position, third argument is the new Y position. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_ACCELEROMETER = 4; +const float IE_FOCUS = 5; /* Specifies that input focus was given. parama says mouse focus, paramb says keyboard focus. If either are -1, then it is unchanged. */ +const float IE_JOYAXIS = 6; /* Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller. */ +const float FILE_READ = 0; /* The file may be read via fgets to read a single line at a time. */ +const float FILE_APPEND = 1; /* Like FILE_WRITE, but writing starts at the end of the file. */ +const float FILE_WRITE = 2; /* fputs will be used to write to the file. */ +const float FILE_READNL = 4; /* Like FILE_READ, except newlines are not special. fgets reads the entire file into a tempstring. */ +const float FILE_MMAP_READ = 5; /* The file will be loaded into memory. fgets returns a pointer to the first byte (and will always return the same value for this file). Cast this to your datatype. */ +const float FILE_MMAP_RW = 6; /* Like FILE_MMAP_READ, except any changes to the data will be written back to disk once the file is closed. */ +const float MASK_ENGINE = 1; /* Valid as an argument for addentities. If specified, all non-csqc entities will be added to the scene. */ +const float MASK_VIEWMODEL = 2; /* Valid as an argument for addentities. If specified, the regular engine viewmodel will be added to the scene. */ +const float PREDRAW_AUTOADD = 0; /* Valid as a return value from the predraw function. Returning this will cause the engine to automatically invoke addentity(self) for you. */ +const float PREDRAW_NEXT = 1; /* Valid as a return value from the predraw function. Returning this will simply move on to the next entity without the autoadd behaviour, so can be used for particle/invisible/special entites, or entities that were explicitly drawn with addentity. */ +const float LFIELD_ORIGIN = 0; +const float LFIELD_COLOUR = 1; +const float LFIELD_RADIUS = 2; +const float LFIELD_FLAGS = 3; +const float LFIELD_STYLE = 4; +const float LFIELD_ANGLES = 5; +const float LFIELD_FOV = 6; +const float LFIELD_CORONA = 7; +const float LFIELD_CORONASCALE = 8; +const float LFIELD_CUBEMAPNAME = 9; +const float LFIELD_AMBIENTSCALE = 10; +const float LFIELD_DIFFUSESCALE = 11; +const float LFIELD_SPECULARSCALE = 12; +const float LFIELD_ROTATION = 13; +const float LFLAG_NORMALMODE = 1; +const float LFLAG_REALTIMEMODE = 2; +const float LFLAG_LIGHTMAP = 4; +const float LFLAG_FLASHBLEND = 8; +const float LFLAG_NOSHADOWS = 256; +const float LFLAG_SHADOWMAP = 512; +const float LFLAG_CREPUSCULAR = 1024; +const float TEREDIT_RELOAD = 0; +const float TEREDIT_SAVE = 1; +const float TEREDIT_SETHOLE = 2; +const float TEREDIT_HEIGHT_SET = 3; +const float TEREDIT_HEIGHT_SMOOTH = 4; +const float TEREDIT_HEIGHT_SPREAD = 5; +const float TEREDIT_HEIGHT_RAISE = 6; +const float TEREDIT_HEIGHT_FLATTEN = 18; +const float TEREDIT_HEIGHT_LOWER = 7; +const float TEREDIT_TEX_KILL = 8; +const float TEREDIT_TEX_GET = 9; +const float TEREDIT_TEX_BLEND = 10; +const float TEREDIT_TEX_UNIFY = 11; +const float TEREDIT_TEX_NOISE = 12; +const float TEREDIT_TEX_BLUR = 13; +const float TEREDIT_WATER_SET = 14; +const float TEREDIT_MESH_ADD = 15; +const float TEREDIT_MESH_KILL = 16; +const float TEREDIT_TINT = 17; +const float TEREDIT_TEX_REPLACE = 19; +const float TEREDIT_RESET_SECT = 20; +const float TEREDIT_RELOAD_SECT = 21; +const float SLIST_HOSTCACHEVIEWCOUNT = 0; +const float SLIST_HOSTCACHETOTALCOUNT = 1; +const float SLIST_MASTERQUERYCOUNT = 2; +const float SLIST_MASTERREPLYCOUNT = 3; +const float SLIST_SERVERQUERYCOUNT = 4; +const float SLIST_SERVERREPLYCOUNT = 5; +const float SLIST_SORTFIELD = 6; +const float SLIST_SORTDESCENDING = 7; +const float SLIST_TEST_CONTAINS = 0; +const float SLIST_TEST_NOTCONTAIN = 1; +const float SLIST_TEST_LESSEQUAL = 2; +const float SLIST_TEST_LESS = 3; +const float SLIST_TEST_EQUAL = 4; +const float SLIST_TEST_GREATER = 5; +const float SLIST_TEST_GREATEREQUAL = 6; +const float SLIST_TEST_NOTEQUAL = 7; +const float SLIST_TEST_STARTSWITH = 8; +const float SLIST_TEST_NOTSTARTSWITH = 9; + +void(vector vang) makevectors = #1; /* + Takes an angle vector (pitch,yaw,roll). Writes its results into v_forward, v_right, v_up vectors. */ + +void(entity e, vector o) setorigin = #2; /* + Changes e's origin to be equal to o. Also relinks collision state (as well as setting absmin+absmax), which is required after changing .solid */ + +void(entity e, string m) setmodel = #3; /* + Looks up m in the model precache list, and sets both e.model and e.modelindex to match. BSP models will set e.mins and e.maxs accordingly, other models depend upon the value of sv_gameplayfix_setmodelrealbox - for compatibility you should always call setsize after all pickups or non-bsp models. Also relinks collision state. */ + +void(entity e, vector min, vector max) setsize = #4; /* + Sets the e's mins and maxs fields. Also relinks collision state, which sets absmin and absmax too. */ + +float() random = #7; /* + Returns a random value between 0 and 1. Be warned, this builtin can return 1 in most engines, which can break arrays. */ + +void(entity e, float chan, string samp, float vol, float atten, optional float speedpct, optional float flags) sound = #8; /* + Starts a sound centered upon the given entity. + chan is the entity sound channel to use, channel 0 will allow you to mix many samples at once, others will replace the old sample + 'samp' must have been precached first + if specified, 'speedpct' should normally be around 100 (or =0), 200 for double speed or 50 for half speed. + flags&1 means the sound should be sent reliably. */ + +void(string soundname, optional float channel, optional float volume) localsound = #177; /* + Plays a sound... locally... probably best not to call this from ssqc. Also disables reverb. */ + +float(entity e, float channel, string newsample, float volume, float attenuation, float pitchpct, float flags, float timeoffset) soundupdate = #0:soundupdate; /* + Changes the properties of the current sound being played on the given entity channel. newsample may be empty, and will be ignored in this case. timeoffset is relative to the current position (subtract the result of getsoundtime for absolute positions). Negative volume can be used to stop the sound. Return value is a fractional value based upon the number of audio devices that could be updated - test against TRUE rather than non-zero. */ + +vector(vector v) normalize = #9; /* + Shorten or lengthen a direction vector such that it is only one quake unit long. */ + +void(string e) error = #10; /* + Ends the game with an easily readable error message. */ + +void(string e) objerror = #11; /* + Displays a non-fatal easily readable error message concerning the self entity, including a field dump. self will be removed! */ + +float(vector v) vlen = #12; /* + Returns the square root of the dotproduct of a vector with itself. Or in other words the length of a distance vector, in quake units. */ + +float(vector v, optional entity reference) vectoyaw = #13; /* + Given a direction vector, returns the yaw angle in which that direction vector points. If an entity is passed, the yaw angle will be relative to that entity's gravity direction. */ + +entity() spawn = #14; /* + Adds a brand new entity into the world! Hurrah, you're now a parent! */ + +void(entity e) remove = #15; /* + Destroys the given entity and clears some limited fields (including model, modelindex, solid, classname). Any references to the entity following the call are an error. After two seconds, the entity will be reused, in the interim you can unfortunatly still read its fields to see if the reference is no longer valid. */ + +void(vector v1, vector v2, float flags, entity ent) traceline = #16; /* + Traces an infinitely thin line through the world from v1 towards v2. + Will not collide with ent, ent.owner, or any entity who's owner field refers to ent. + There are no side effects beyond the trace_* globals being written. + flags&MOVE_NOMONSTERS will not impact on non-bsp entities. + flags&MOVE_MISSILE will impact with increased size. + flags&MOVE_HITMODEL will impact upon model meshes, instead of their bounding boxes. + flags&MOVE_TRIGGERS will also stop on triggers + flags&MOVE_EVERYTHING will stop if it hits anything, even non-solid entities. + flags&MOVE_LAGGED will backdate entity positions for the purposes of this builtin according to the indicated player ent's latency, to provide lag compensation. */ + +entity(entity start, .string fld, string match) find = #18; /* + Scan for the next entity with a given field set to the given 'match' value. start should be either world, or the previous entity that was found. Returns world on failure/if there are no more. */ + +string(string s) precache_sound = #19; /* + Precaches a sound, making it known to clients and loading it from disk. This builtin (strongly) should be called during spawn functions. This builtin must be called for the sound before the sound builtin is called, or it might not even be heard. */ + +string(string s) precache_model = #20; /* + Precaches a model, making it known to clients and loading it from disk if it has a .bsp extension. This builtin (strongly) should be called during spawn functions. This must be called for each model name before setmodel may use that model name. + Modelindicies precached in SSQC will always be positive. CSQC precaches will be negative if they are not also on the server. */ + +entity(vector org, float rad) findradius = #22; /* + Finds all entities within a distance of the 'org' specified. One entity is returned directly, while other entities are returned via that entity's .chain field. */ + +void(string s, ...) dprint = #25; /* + NQ: Prints the given message on the server's console, but only if the developer cvar is set. Arguments will be concatenated into a single message. */ + +void(string s, ...) dprint = #25; /* + QW: Unconditionally prints the given message on the server's console. Arguments will be concatenated into a single message. */ + +string(float val) ftos = #26; /* + Returns a tempstring containing a representation of the given float. Precision depends upon engine. */ + +string(vector val) vtos = #27; /* + Returns a tempstring containing a representation of the given vector. Precision depends upon engine. */ + +void() coredump = #28; /* + Writes out a coredump. This contains stack, globals, and field info for all ents. This can be handy for debugging. */ + +void() traceon = #29; /* + Enables tracing. This may be spammy, slow, and stuff. Set debugger 1 in order to use fte's qc debugger. */ + +void() traceoff = #30; /* + Disables tracing again. */ + +void(entity e) eprint = #31; /* + Debugging builtin that prints all fields of the given entity to the console. */ + +float(float yaw, float dist, optional float settraceglobals) walkmove = #32; /* + Attempt to walk the entity at a given angle for a given distance. + if settraceglobals is set, the trace_* globals will be set, showing the results of the movement. + This function will trigger touch events. */ + +float() droptofloor = #34; /* + Instantly moves the entity downwards until it hits the ground. If the entity would need to drop more than 'pr_droptofloorunits' quake units, its position will be considered invalid and the builtin will abort. */ + +void(float lightstyle, string stylestring, optional vector rgb) lightstyle = #35; /* + Specifies an auto-animating string that specifies the light intensity for entities using that lightstyle. + a is off, z is fully lit. Should be lower case only. + rgb will recolour all lights using that lightstyle. */ + +float(float) rint = #36; /* + Rounds the given float up or down to the closest integeral value. X.5 rounds away from 0 */ + +float(float) floor = #37; /* + Rounds the given float downwards, even when negative. */ + +float(float) ceil = #38; /* + Rounds the given float upwards, even when negative. */ + +float(entity ent) checkbottom = #40; /* + Expensive checks to ensure that the entity is actually sitting on something solid, returns true if it is. */ + +float(vector pos) pointcontents = #41; /* + Checks the given point to see what is there. Returns one of the SOLID_* constants. Just because a spot is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests. */ + +float(float) fabs = #43; /* + Removes the sign of the float, making it positive if it is negative. */ + +float(string) cvar = #45; /* + Returns the numeric value of the named cvar */ + +void(string, ...) localcmd = #46; /* + Adds the string to the console command queue. Commands will not be executed immediately, but rather at the start of the following frame. */ + +entity(entity) nextent = #47; /* + Returns the following entity. Skips over removed entities. Returns world when passed the last valid entity. */ + +void(vector pos, vector dir, float colour, float count) particle = #48; /* + Spawn 'count' particles around 'pos' moving in the direction 'dir', with a palette colour index between 'colour' and 'colour+8'. */ + +#define ChangeYaw changeyaw +void() changeyaw = #49; /* + Changes the self.angles_y field towards self.ideal_yaw by up to self.yawspeed. */ + +vector(vector fwd, optional vector up) vectoangles = #51; /* + Returns the angles required to orient an entity to look in the given direction. The 'up' argument is required if you wish to set a roll angle, otherwise it will be limited to just monster-style turning. */ + +float(float angle) sin = #60; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float angle) cos = #61; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float value) sqrt = #62; /* Part of DP_QC_SINCOSSQRTPOW*/ +void(entity ent) changepitch = #63; /* Part of DP_QC_CHANGEPITCH*/ +void(entity ent, entity ignore) tracetoss = #64; +string(entity ent) etos = #65; /* Part of DP_QC_ETOS*/ +void(float step) movetogoal = #67; +string(string s) precache_file = #68; /* + This builtin does nothing. It was used only as a hint for pak generation. */ + +void(entity e) makestatic = #69; /* + Sends a copy of the entity's renderable fields to all clients, and REMOVES the entity, preventing further changes. This means it will be unmutable and non-solid. */ + +void(string cvarname, string valuetoset) cvar_set = #72; /* + Instantly sets a cvar to the given string value. */ + +void (vector pos, string samp, float vol, float atten) ambientsound = #74; +string(string str) precache_model2 = #75; +string(string str) precache_sound2 = #76; +string(string str) precache_file2 = #77; +float(string) stof = #81; /* Part of FRIK_FILE, FTE_STRINGS, QW_ENGINE, ZQ_QC_STRINGS*/ +void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent) tracebox = #90; /* Part of DP_QC_TRACEBOX + Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values. */ + +vector() randomvec = #91; /* Part of DP_QC_RANDOMVEC + Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive. */ + +vector(vector org) getlight = #92; +void(string cvarname, string defaultvalue) registercvar = #93; /* Part of DP_REGISTERCVAR + Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op. + This builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop. + In engines that support it, you will generally find the autocvar feature easier and more efficient to use. */ + +float(float a, float b, ...) min = #94; /* Part of DP_QC_MINMAXBOUND + Returns the lowest value of its arguments. */ + +float(float a, float b, ...) max = #95; /* Part of DP_QC_MINMAXBOUND + Returns the highest value of its arguments. */ + +float(float minimum, float val, float maximum) bound = #96; /* Part of DP_QC_MINMAXBOUND + Returns val, unless minimum is higher, or maximum is less. */ + +float(float value, float exp) pow = #97; /* Part of DP_QC_SINCOSSQRTPOW*/ +entity(entity start, .float fld, float match) findfloat = #98; /* Part of DP_QC_FINDFLOAT + Equivelent to the find builtin, but instead of comparing strings, this builtin compares floats. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value. + world is returned when there are no more entities. */ + +float(string extname) checkextension = #99; /* + Checks for an extension by its name (eg: checkextension("FRIK_FILE") says that its okay to go ahead and use strcat). + Use cvar("pr_checkextension") to see if this builtin exists. */ + +float(float value) anglemod = #102; +filestream(string filename, float mode, optional float mmapminsize) fopen = #110; /* Part of FRIK_FILE*/ +void(filestream fhandle) fclose = #111; /* Part of FRIK_FILE*/ +string(filestream fhandle) fgets = #112; /* Part of FRIK_FILE*/ +void(filestream fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) fputs = #113; /* Part of FRIK_FILE*/ +float(string s) strlen = #114; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s1, optional string s2, ...) strcat = #115; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, float start, float length) substring = #116; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +vector(string s) stov = #117; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, ...) strzone = #118; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +void(string s) strunzone = #119; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +float(string modelname, optional float queryonly) getmodelindex = #200; /* + Acts as an alternative to precache_model(foo);setmodel(bar, foo); return bar.modelindex; + If queryonly is set and the model was not previously precached, the builtin will return 0 without needlessly precaching the model. */ + +__variant(float prnum, string funcname, ...) externcall = #201; /* Part of FTE_MULTIPROGS + Directly call a function in a different/same progs by its name. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +float(string progsname) addprogs = #202; /* Part of FTE_MULTIPROGS + Loads an additional .dat file into the current qcvm. The returned handle can be used with any of the externcall/externset/externvalue builtins. + There are cvars that allow progs to be loaded automatically. */ + +__variant(float prnum, string varname) externvalue = #203; /* Part of FTE_MULTIPROGS + Reads a global in the named progs by the name of that global. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +void(float prnum, __variant newval, string varname) externset = #204; /* Part of FTE_MULTIPROGS + Sets a global in the named progs by name. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +float(string input, string token) instr = #206; /* Part of FTE_MULTIPROGS + Returns substring(input, strstrpos(input, token), -1), or the null string if token was not found in input. You're probably better off using strstrpos. */ + +void(entity portal, float state) openportal = #207; /* + Opens or closes the portals associated with a door or some such on q2 or q3 maps. On Q2BSPs, the entity should be the 'func_areaportal' entity - its style field will say which portal to open. On Q3BSPs, the entity is the door itself, the portal will be determined by the two areas found from a preceding setorigin call. */ + +void(optional __variant ret) abort = #211; /* Part of FTE_MULTITHREADED + QC execution is aborted. Parent QC functions on the stack will be skipped, effectively this forces all QC functions to 'return ret' until execution returns to the engine. If ret is ommited, it is assumed to be 0. */ + +void(vector org, vector dmin, vector dmax, float colour, float effect, float count) particle2 = #215; /* Part of FTE_HEXEN2*/ +void(vector org, vector box, float colour, float effect, float count) particle3 = #216; /* Part of FTE_HEXEN2*/ +void(vector org, float radius, float colour, float effect, float count) particle4 = #217; /* Part of FTE_HEXEN2*/ +float(float number, float quantity) bitshift = #218; /* Part of EXT_BITSHIFT*/ +void(vector pos) te_lightningblood = #219; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ +float(string s1, string sub, optional float startidx) strstrofs = #221; /* Part of FTE_STRINGS + Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1. + If startidx is set, this builtin will ignore matches before that 0-based offset. */ + +float(string str, float index) str2chr = #222; /* Part of FTE_STRINGS + Retrieves the character value at offset 'index'. */ + +string(float chr, ...) chr2str = #223; /* Part of FTE_STRINGS + The input floats are considered character values, and are concatenated. */ + +string(float ccase, float redalpha, float redchars, string str, ...) strconv = #224; /* Part of FTE_STRINGS + Converts quake chars in the input string amongst different representations. + ccase specifies the new case for letters. + 0: not changed. + 1: forced to lower case. + 2: forced to upper case. + redalpha and redchars switch between colour ranges. + 0: no change. + 1: Forced white. + 2: Forced red. + 3: Forced gold(low) (numbers only). + 4: Forced gold (high) (numbers only). + 5+6: Forced to white and red alternately. + You should not use this builtin in combination with UTF-8. */ + +string(float pad, string str1, ...) strpad = #225; /* Part of FTE_STRINGS + Pads the string with spaces, to ensure its a specific length (so long as a fixed-width font is used, anyway). If pad is negative, the spaces are added on the left. If positive the padding is on the right. */ + +string(infostring old, string key, string value) infoadd = #226; /* Part of FTE_STRINGS + Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \ character. */ + +string(infostring info, string key) infoget = #227; /* Part of FTE_STRINGS + Reads a named value from an infostring. The returned value is a tempstring */ + +#define strcmp strncmp +float(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs) strncmp = #228; /* Part of FTE_STRINGS + Compares up to 'len' chars in the two strings. s1ofs allows you to treat s2 as a substring to compare against, or should be 0. + Returns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher. */ + +float(string s1, string s2) strcasecmp = #229; /* Part of FTE_STRINGS + Compares the two strings without case sensitivity. + Returns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon. */ + +float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs) strncasecmp = #230; /* Part of FTE_STRINGS + Compares up to 'len' chars in the two strings without case sensitivity. s1ofs allows you to treat s2 as a substring to compare against, or should be 0. + Returns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon. */ + +void() calltimeofday = #231; /* Part of FTE_CALLTIMEOFDAY + Asks the engine to instantly call the qc's 'timeofday' function, before returning. For compatibility with mvdsv. + timeofday should have the prototype: void(float secs, float mins, float hour, float day, float mon, float year, string strvalue) + The strftime builtin is more versatile and less weird. */ + +void(vector angle) rotatevectorsbyangle = #235; +void(vector fwd, vector right, vector up) rotatevectorsbyvectors = #236; +float(float mdlindex, string skinname) skinforname = #237; +float(string shadername, optional string defaultshader, ...) shaderforname = #238; /* Part of FTE_FORCESHADER + Caches the named shader and returns a handle to it. + If the shader could not be loaded from disk (missing file or ruleset_allow_shaders 0), it will be created from the 'defaultshader' string if specified, or a 'skin shader' default will be used. + defaultshader if not empty should include the outer {} that you would ordinarily find in a shader. */ + +void(vector org, optional float count) te_bloodqw = #239; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ +float(vector viewpos, entity entity) checkpvs = #240; /* Part of FTE_QC_CHECKPVS*/ +vector(entity ent, float tagnum) rotatevectorsbytag = #244; +int(string) stoi = #259; /* Part of FTE_QC_INTCONV + Converts the given string into an integer. Base 8, 10, or 16 is determined based upon the format of the string. */ + +string(int) itos = #260; /* Part of FTE_QC_INTCONV + Converts the passed integer into a base10 string. */ + +int(string) stoh = #261; /* Part of FTE_QC_INTCONV + Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P */ + +string(int) htos = #262; /* Part of FTE_QC_INTCONV + Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters. */ + +float(float modlindex, optional float useabstransforms) skel_create = #263; /* Part of FTE_CSQC_SKELETONOBJECTS + Allocates a new uninitiaised skeletal object, with enough bone info to animate the given model. + eg: self.skeletonobject = skel_create(self.modelindex); */ + +float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addfrac) skel_build = #264; /* Part of FTE_CSQC_SKELETONOBJECTS + Animation data (according to the entity's frame info) is pulled from the specified model and blended into the specified skeletal object. + If retainfrac is set to 0 on the first call and 1 on the others, you can blend multiple animations together according to the addfrac value. The final weight should be 1. Other values will result in scaling and/or other weirdness. You can use firstbone and lastbone to update only part of the skeletal object, to allow legs to animate separately from torso, use 0 for both arguments to specify all, as bones are 1-based. */ + +float(float skel) skel_get_numbones = #265; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrives the number of bones in the model. The valid range is 1<=bone<=numbones. */ + +string(float skel, float bonenum) skel_get_bonename = #266; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves the name of the specified bone. Mostly only for debugging. */ + +float(float skel, float bonenum) skel_get_boneparent = #267; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves which bone this bone's position is relative to. Bone 0 refers to the entity's position rather than an actual bone */ + +float(float skel, string tagname) skel_find_bone = #268; /* Part of FTE_CSQC_SKELETONOBJECTS + Finds a bone by its name, from the model that was used to create the skeletal object. */ + +vector(float skel, float bonenum) skel_get_bonerel = #269; /* Part of FTE_CSQC_SKELETONOBJECTS + Gets the bone position and orientation relative to the bone's parent. Return value is the offset, and v_forward, v_right, v_up contain the orientation. */ + +vector(float skel, float bonenum) skel_get_boneabs = #270; /* Part of FTE_CSQC_SKELETONOBJECTS + Gets the bone position and orientation relative to the entity. Return value is the offset, and v_forward, v_right, v_up contain the orientation. + Use gettaginfo for world coord+orientation. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_set_bone = #271; /* Part of FTE_CSQC_SKELETONOBJECTS + Sets a bone position relative to its parent. If the orientation arguments are not specified, v_forward+v_right+v_up are used instead. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_mul_bone = #272; /* Part of FTE_CSQC_SKELETONOBJECTS + Transforms a single bone by a matrix. You can use makevectors to generate a rotation matrix from an angle. */ + +void(float skel, float startbone, float endbone, vector org, optional vector fwd, optional vector right, optional vector up) skel_mul_bones = #273; /* Part of FTE_CSQC_SKELETONOBJECTS + Transforms an entire consecutive range of bones by a matrix. You can use makevectors to generate a rotation matrix from an angle, but you'll probably want to divide the angle by the number of bones. */ + +void(float skeldst, float skelsrc, float startbone, float entbone) skel_copybones = #274; /* Part of FTE_CSQC_SKELETONOBJECTS + Copy bone data from one skeleton directly into another. */ + +void(float skel) skel_delete = #275; /* Part of FTE_CSQC_SKELETONOBJECTS + Deletes a skeletal object. The actual delete is delayed, allowing the skeletal object to be deleted in an entity's predraw function yet still be valid by the time the addentity+renderscene builtins need it. Also uninstanciates any ragdoll currently in effect on the skeletal object. */ + +float(float modidx, string framename) frameforname = #276; /* Part of FTE_CSQC_SKELETONOBJECTS + Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error. */ + +float(float modidx, float framenum) frameduration = #277; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves the duration (in seconds) of the specified framegroup. */ + +void(float action, optional vector pos, optional float radius, optional float quant, ...) terrain_edit = #278; /* + Realtime terrain editing. Actions are the TEREDIT_ constants. */ + +void() touchtriggers = #279; /* + Triggers a touch events between self and every entity that it is in contact with. This should typically just be the triggers touch functions. */ + +float(entity skelent, string dollcmd, float animskel) skel_ragupdate = #281; /* + Updates the skeletal object attached to the entity according to its origin and other properties. + if animskel is non-zero, the ragdoll will animate towards the bone state in the animskel skeletal object, otherwise they will pick up the model's base pose which may not give nice results. + If dollcmd is not set, the ragdoll will update (this should be done each frame). + If the doll is updated without having a valid doll, the model's default .doll will be instanciated. + commands: + doll foo.doll : sets up the entity to use the named doll file + dollstring TEXT : uses the doll file directly embedded within qc, with that extra prefix. + cleardoll : uninstanciates the doll without destroying the skeletal object. + animate 0.5 : specifies the strength of the ragdoll as a whole + animatebody somebody 0.5 : specifies the strength of the ragdoll on a specific body (0 will disable ragdoll animations on that body). + enablejoint somejoint 1 : enables (or disables) a joint. Disabling joints will allow the doll to shatter. */ + +float*(float skel) skel_mmap = #282; /* + Map the bones in VM memory. They can then be accessed via pointers. Each bone is 12 floats, the four vectors interleaved (sadly). */ + +void(entity ent, float bonenum, vector org, optional vector angorfwd, optional vector right, optional vector up) skel_set_bone_world = #283; /* + Sets the world position of a bone within the given entity's attached skeletal object. The world position is dependant upon the owning entity's position. If no orientation argument is specified, v_forward+v_right+v_up are used for the orientation instead. If 1 is specified, it is understood as angles. If 3 are specified, they are the forawrd/right/up vectors to use. */ + +string(float modidx, float framenum) frametoname = #284; +string(float modidx, float skin) skintoname = #285; +float(float resourcetype, float tryload, string resourcename) resourcestatus = #286; /* + resourcetype must be one of the RESTYPE_ constants. Returns one of the RESSTATE_ constants. Tryload 0 is a query only. Tryload 1 will attempt to reload the content if it was flushed. */ + +hashtable(float tabsize, optional float defaulttype) hash_createtab = #287; /* Part of FTE_QC_HASHTABLES + Creates a hash table object with at least 'tabsize' slots. hash table with index 0 is a game-persistant table and will NEVER be returned by this builtin (except as an error return). */ + +void(hashtable table) hash_destroytab = #288; /* Part of FTE_QC_HASHTABLES + Destroys a hash table object. */ + +void(hashtable table, string name, __variant value, optional float typeandflags) hash_add = #289; /* Part of FTE_QC_HASHTABLES + Adds the given key with the given value to the table. + If flags&HASH_REPLACE, the old value will be removed, if not set then multiple values may be added for a single key, they won't overwrite. + The type argument describes how the value should be stored and saved to files. While you can claim that all variables are just vectors, being more precise can result in less issues with tempstrings or saved games. */ + +__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index) hash_get = #290; /* Part of FTE_QC_HASHTABLES + looks up the specified key name in the hash table. returns deflt if key was not found. If stringsonly=1, the return value will be in the form of a tempstring, otherwise it'll be the original value argument exactly as it was. If requiretype is specified, then values not of the specified type will be ignored. Hurrah for multiple types with the same name. */ + +__variant(hashtable table, string name) hash_delete = #291; /* Part of FTE_QC_HASHTABLES + removes the named key. returns the value of the object that was destroyed, or 0 on error. */ + +string(hashtable table, float idx) hash_getkey = #292; /* Part of FTE_QC_HASHTABLES + gets some random key name. add+delete can change return values of this, so don't blindly increment the key index if you're removing all. */ + +float(string name) checkcommand = #294; /* Part of FTE_QC_CHECKCOMMAND + Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist. */ + +string(string s) argescape = #295; /* + Marks up a string so that it can be reliably tokenized as a single argument later. */ + +void() clearscene = #300; /* + Forgets all rentities, polygons, and temporary dlights. Resets all view properties to their default values. */ + +void(float mask) addentities = #301; /* + Walks through all entities effectively doing this: + if (ent.drawmask&mask){ ent.predaw(); if (wasremoved(ent)||(ent.renderflags&RF_NOAUTOADD))continue; addentity(ent); } + If mask&MASK_DELTA, non-csqc entities, particles, and related effects will also be added to the rentity list. + If mask&MASK_STDVIEWMODEL then the default view model will also be added. */ + +void(entity ent) addentity = #302; /* + Copies the entity fields into a new rentity for later rendering via addscene. */ + +#define setviewprop setproperty +float(float property, ...) setproperty = #303; /* + Allows you to override default view properties like viewport, fov, and whether the engine hud will be drawn. Different VF_ values have slightly different arguments, some are vectors, some floats. */ + +void() renderscene = #304; /* + Draws all entities, polygons, and particles on the rentity list (which were added via addentities or addentity), using the various view properties set via setproperty. There is no ordering dependancy. + The scene must generally be cleared again before more entities are added, as entities will persist even over to the next frame. + You may call this builtin multiple times per frame, but should only be called from CSQC_UpdateView. */ + +float(vector org, float radius, vector lightcolours, optional float style, optional string cubemapname, optional float pflags) dynamiclight_add = #305; /* + Adds a temporary dlight, ready to be drawn via addscene. Cubemap orientation will be read from v_forward/v_right/v_up. */ + +void(string texturename, optional float flags) R_BeginPolygon = #306; /* + Specifies the shader to use for the following polygons, along with optional flags. + If flags&4, the polygon will be drawn as soon as the EndPolygon call is made, rather than waiting for renderscene. This allows complex 2d effects. */ + +void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex = #307; /* + Specifies a polygon vertex with its various properties. */ + +void() R_EndPolygon = #308; /* + Ends the current polygon. At least 3 verticies must have been specified. You do not need to call beginpolygon if you wish to draw another polygon with the same shader. */ + +#define getviewprop getproperty +__variant(float property) getproperty = #309; /* + Retrieve a currently-set (typically view) property, allowing you to read the current viewport or other things. Due to cheat protection, certain values may be unretrievable. */ + +vector (vector v) unproject = #310; /* + Transform a 2d screen-space point (with depth) into a 3d world-space point, according the various origin+angle+fov etc settings set via setproperty. */ + +vector (vector v) project = #311; /* + Transform a 3d world-space point into a 2d screen-space point, according the various origin+angle+fov etc settings set via setproperty. */ + +void(float width, vector pos1, vector pos2, vector rgb, float alpha, optional float drawflag) drawline = #315; /* + Draws a 2d line between the two 2d points. */ + +float(string name) iscachedpic = #316; /* + Checks to see if the image is currently loaded. Engines might lie, or cache between maps. */ + +string(string name, optional float trywad) precache_pic = #317; /* + Forces the engine to load the named image. If trywad is specified, the specified name must any lack path and extension. */ + +#define draw_getimagesize drawgetimagesize +vector(string picname) drawgetimagesize = #318; /* + Returns the dimensions of the named image. Images specified with .lmp should give the original .lmp's dimensions even if texture replacements use a different resolution. */ + +void(string name) freepic = #319; /* + Tells the engine that the image is no longer needed. The image will appear to be new the next time its needed. */ + +float(vector position, float character, vector size, vector rgb, float alpha, optional float drawflag) drawcharacter = #320; /* + Draw the given quake character at the given position. + If flag&4, the function will consider the char to be a unicode char instead (or display as a ? if outside the 32-127 range). + size should normally be something like '8 8 0'. + rgb should normally be '1 1 1' + alpha normally 1. + Software engines may assume the named defaults. + Note that ALL text may be rescaled on the X axis due to variable width fonts. The X axis may even be ignored completely. */ + +float(vector position, string text, vector size, vector rgb, float alpha, optional float drawflag) drawrawstring = #321; /* + Draws the specified string without using any markup at all, even in engines that support it. + If UTF-8 is globally enabled in the engine, then that encoding is used (without additional markup), otherwise it is raw quake chars. + Software engines may assume a size of '8 8 0', rgb='1 1 1', alpha=1, flag&3=0, but it is not an error to draw out of the screen. */ + +float(vector position, string pic, vector size, vector rgb, float alpha, optional float drawflag) drawpic = #322; /* + Draws an shader within the given 2d screen box. Software engines may omit support for rgb+alpha, but must support rescaling, and must clip to the screen without crashing. */ + +float(vector position, vector size, vector rgb, float alpha, optional float drawflag) drawfill = #323; /* + Draws a solid block over the given 2d box, with given colour, alpha, and blend mode (specified via flags). + flags&3=0 simple blend. + flags&3=1 additive blend */ + +void(float x, float y, float width, float height) drawsetcliparea = #324; /* + Specifies a 2d clipping region (aka: scissor test). 2d draw calls will all be clipped to this 2d box, the area outside will not be modified by any 2d draw call (even 2d polygons). */ + +void(void) drawresetcliparea = #325; /* + Reverts the scissor/clip area to the whole screen. */ + +float(vector position, string text, vector size, vector rgb, float alpha, float drawflag) drawstring = #326; /* + Draws a string, interpreting markup and recolouring as appropriate. */ + +float(string text, float usecolours, optional vector fontsize) stringwidth = #327; /* + Calculates the width of the screen in virtual pixels. If usecolours is 1, markup that does not affect the string width will be ignored. Will always be decoded as UTF-8 if UTF-8 is globally enabled. + If the char size is not specified, '8 8 0' will be assumed. */ + +void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, optional float drawflag) drawsubpic = #328; /* + Draws a rescaled subsection of an image to the screen. */ + +float(float stnum) getstati = #330; /* + Retrieves the numerical value of the given EV_INTEGER or EV_ENTITY stat (converted to a float). */ + +#define getstatbits getstatf +float(float stnum, optional float firstbit, optional float bitcount) getstatf = #331; /* + Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat. */ + +string(float firststnum) getstats = #332; /* + Retrieves the value of the given EV_STRING stat, as a tempstring. + Older engines may use 4 consecutive integer stats, with a limit of 15 chars (yes, really. 15.), but FTE QuakeWorld uses a separate namespace for string stats and has a much higher length limit. */ + +void(entity e, float mdlindex) setmodelindex = #333; /* + Sets a model by precache index instead of by name. Otherwise identical to setmodel. */ + +string(float mdlindex) modelnameforindex = #334; /* + Retrieves the name of the model based upon a precache index. This can be used to reduce csqc network traffic by enabling model matching. */ + +float(string effectname) particleeffectnum = #335; /* + Precaches the named particle effect. If your effect name is of the form 'foo.bar' then particles/foo.cfg will be loaded by the client if foo.bar was not already defined. + Different engines will have different particle systems, this specifies the QC API only. */ + +void(float effectnum, entity ent, vector start, vector end) trailparticles = #336; /* + Draws the given effect between the two named points. If ent is not world, distances will be cached in the entity in order to avoid framerate dependancies. The entity is not otherwise used. */ + +void(float effectnum, vector origin, optional vector dir, optional float count) pointparticles = #337; /* + Spawn a load of particles from the given effect at the given point traveling or aiming along the direction specified. The number of particles are scaled by the count argument. */ + +void(string s, ...) cprint = #338; /* + Print into the center of the screen just as ssqc's centerprint would appear. */ + +void(string s, ...) print = #339; /* + Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar). */ + +string(float keynum) keynumtostring = #340; /* + Returns a hunam-readable name for the given keycode, as a tempstring. */ + +float(string keyname) stringtokeynum = #341; /* + Looks up the key name in the same way that the bind command would, returning the keycode for that key. */ + +string(float keynum) getkeybind = #342; /* + Finds the current binding for the given key (ignores modifiers like shift/alt/ctrl). */ + +void(float usecursor, optional string cursorimage, optional vector hotspot, optional float scale) setcursormode = #343; /* + Pass TRUE if you want the engine to release the mouse cursor (absolute input events + touchscreen mode). Pass FALSE if you want the engine to grab the cursor (relative input events + standard looking). If the image name is specified, the engine will use that image for a cursor (use an empty string to clear it again), in a way that will not conflict with the console. Images specified this way will be hardware accelerated, if supported by the platform/port. */ + +vector() getmousepos = #344; /* + Nasty convoluted DP extension. Typically returns deltas instead of positions. Use CSQC_InputEvent for such things in csqc mods. */ + +float(float inputsequencenum) getinputstate = #345; /* + Looks up an input frame from the log, setting the input_* globals accordingly. + The sequence number range used for prediction should normally be servercommandframe < sequence <= clientcommandframe. + The sequence equal to clientcommandframe will change between input frames. */ + +void(float sens) setsensitivityscaler = #346; /* + Temporarily scales the player's mouse sensitivity based upon something like zoom, avoiding potential cvar saving and thus corruption. */ + +void(entity ent) runstandardplayerphysics = #347; /* + Perform the engine's standard player movement prediction upon the given entity using the input_* globals to describe movement. */ + +string(float playernum, string keyname) getplayerkeyvalue = #348; /* + Look up a player's userinfo, to discover things like their name, topcolor, bottomcolor, skin, team, *ver. + Also includes scoreboard info like frags, ping, pl, userid, entertime, as well as voipspeaking and voiploudness. */ + +float() isdemo = #349; /* + Returns if the client is currently playing a demo or not */ + +float() isserver = #350; /* + Returns if the client is acting as the server (aka: listen server) */ + +void(vector origin, vector forward, vector right, vector up, optional float inwater) SetListener = #351; /* + Sets the position of the view, as far as the audio subsystem is concerned. This should be called once per CSQC_UpdateView as it will otherwise revert to default. */ + +void(string cmdname) registercommand = #352; /* + Register the given console command, for easy console use. + Console commands that are later used will invoke CSQC_ConsoleCommand. */ + +float(entity ent) wasfreed = #353; /* + Quickly check to see if the entity is currently free. This function is only valid during the two-second non-reuse window, after that it may give bad results. Try one second to make it more robust. */ + +string(string key) serverkey = #354; /* + Look up a key in the server's public serverinfo string */ + +string(optional string resetstring) getentitytoken = #355; /* + Grab the next token in the map's entity lump. + If resetstring is not specified, the next token will be returned with no other sideeffects. + If empty, will reset from the map before returning the first token, probably {. + If not empty, will tokenize from that string instead. + Always returns tempstrings. */ + +float(string s) findfont = #356; /* + Looks up a named font slot. Matches the actual font name as a last resort. */ + +float(string fontname, string fontmaps, string sizes, float slot, optional float fix_scale, optional float fix_voffset) loadfont = #357; /* + too convoluted for me to even try to explain correct usage. Try drawfont = loadfont("foo", "cour", "16", 0, 0, 0); to switch to the courier font, if you have the freetype2 library in windows.. */ + +void(string evname, string evargs, ...) sendevent = #359; /* + Invoke Cmd_evname_evargs in ssqc. evargs must be a string of initials refering to the types of the arguments to pass. v=vector, e=entity(.entnum field is sent), f=float, i=int. 6 arguments max - you can get more if you pack your floats into vectors. */ + +float() readbyte = #360; +float() readchar = #361; +float() readshort = #362; +float() readlong = #363; +float() readcoord = #364; +float() readangle = #365; +string() readstring = #366; +float() readfloat = #367; +float() readentitynum = #368; +float(string modelname, float(float isnew) updatecallback, float flags) deltalisten = #371; /* + Specifies a per-modelindex callback to listen for engine-networking entity updates. Such entities are automatically interpolated by the engine (unless flags specifies not to). + The various standard entity fields will be overwritten each frame before the updatecallback function is called. */ + +__variant(float lno, float fld) dynamiclight_get = #372; /* + Retrieves a property from the given dynamic/rt light. Return type depends upon the light field requested. */ + +void(float lno, float fld, __variant value) dynamiclight_set = #373; /* + Changes a property on the given dynamic/rt light. Value type depends upon the light field to be changed. */ + +string(float efnum, float body) particleeffectquery = #374; /* + Retrieves either the name or the body of the effect with the given number. The effect body is regenerated from internal state, and can be changed before being reapplied via the localcmd builtin. */ + +void(string shadername, vector origin, vector up, vector side, vector rgb, float alpha) adddecal = #375; /* + Adds a temporary clipped decal shader to the scene, centered at the given point with given orientation. Will be drawn by the next renderscene call, and freed by the next clearscene call. */ + +void(entity e, string skinfilename, optional string skindata) setcustomskin = #376; /* + Sets an entity's skin overrides. These are custom per-entity surface->shader lookups. The skinfilename/data should be in .skin format: + surfacename,shadername - makes the named surface use the named shader + replace "surfacename" "shadername" - same. + compose "surfacename" "shader" "imagename@x,y:w,h?r,g,b,a" - compose a skin texture from multiple images. The texture is determined to be sufficient to hold the first named image, additional images can be named as extra tokens on the same line. Use a + at the end of the line to continue reading image tokens from the next line also, the named shader must use 'map $diffuse' to read the composed texture (compatible with the defaultskin shader). */ + +__variant*(int size) memalloc = #384; /* Part of FTE_MEMALLOC + Allocate an arbitary block of memory */ + +void(__variant *ptr) memfree = #385; /* Part of FTE_MEMALLOC + Frees a block of memory that was allocated with memfree */ + +void(__variant *dst, __variant *src, int size) memcpy = #386; /* Part of FTE_MEMALLOC + Copys memory from one location to another */ + +void(__variant *dst, int val, int size) memfill8 = #387; /* Part of FTE_MEMALLOC + Sets an entire block of memory to a specified value. Pretty much always 0. */ + +__variant(__variant *dst, float ofs) memgetval = #388; /* + Looks up the 32bit value stored at a pointer-with-offset. */ + +void(__variant *dst, float ofs, __variant val) memsetval = #389; /* + Changes the 32bit value stored at the specified pointer-with-offset. */ + +__variant*(__variant *base, float ofs) memptradd = #390; /* + Perform some pointer maths. Woo. */ + +string(string conname, string field, optional string newvalue) con_getset = #391; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Reads or sets a property from a console object. The old value is returned. Iterrate through consoles with the 'next' field. Valid properties: title, name, next, unseen, markup, forceutf8, close, clear, hidden, linecount */ + +void(string conname, string messagefmt, ...) con_printf = #392; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Prints onto a named console. */ + +void(string conname, vector pos, vector size, float fontsize) con_draw = #393; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Draws the named console. */ + +float(string conname, float inevtype, float parama, float paramb, float paramc) con_input = #394; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Forwards input events to the named console. Mouse updates should be absolute only. */ + +void(entity from, entity to) copyentity = #400; /* Part of DP_QC_COPYENTITY*/ +entity(.string field, string match) findchain = #402; /* Part of DP_QC_FINDCHAIN*/ +entity(.float fld, float match) findchainfloat = #403; /* Part of DP_QC_FINDCHAINFLOAT*/ +void(vector org, string modelname, float startframe, float endframe, float framerate) effect = #404; /* Part of DP_SV_EFFECT + Spawns a self-animating sprite */ + +void(vector org, vector dir, float count) te_blood = #405; /* Part of DP_TE_BLOOD*/ +void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower = #406; /* Part of DP_TE_BLOODSHOWER*/ +void(vector org, vector color) te_explosionrgb = #407; /* Part of DP_TE_EXPLOSIONRGB*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube = #408; /* Part of DP_TE_PARTICLECUBE*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain = #409; /* Part of _DP_TE_PARTICLERAIN*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow = #410; /* Part of _DP_TE_PARTICLESNOW*/ +void(vector org, vector vel, float howmany) te_spark = #411; /* Part of DP_TE_SPARK*/ +void(vector org) te_gunshotquad = #412; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_spikequad = #413; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_superspikequad = #414; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_explosionquad = #415; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_smallflash = #416; /* Part of DP_TE_SMALLFLASH*/ +void(vector org, float radius, float lifetime, vector color) te_customflash = #417; /* Part of DP_TE_CUSTOMFLASH*/ +void(vector org, optional float count) te_gunshot = #418; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_spike = #419; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_superspike = #420; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_explosion = #421; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_tarexplosion = #422; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_wizspike = #423; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_knightspike = #424; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_lavasplash = #425; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_teleport = #426; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org, float color, float colorlength) te_explosion2 = #427; /* Part of DP_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning1 = #428; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning2 = #429; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning3 = #430; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_beam = #431; /* Part of DP_TE_STANDARDEFFECTBUILTINS*/ +void(vector dir) vectorvectors = #432; /* Part of DP_QC_VECTORVECTORS*/ +void(vector org) te_plasmaburn = #433; /* Part of _DP_TE_PLASMABURN*/ +float(entity e, float s) getsurfacenumpoints = #434; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s, float n) getsurfacepoint = #435; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s) getsurfacenormal = #436; /* Part of DP_QC_GETSURFACE*/ +string(entity e, float s) getsurfacetexture = #437; /* Part of DP_QC_GETSURFACE*/ +float(entity e, vector p) getsurfacenearpoint = #438; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; /* Part of DP_QC_GETSURFACE*/ +float(string s) tokenize = #441; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +string(float n) argv = #442; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +void(entity e, entity tagentity, string tagname) setattachment = #443; /* Part of DP_GFX_QUAKE3MODELTAGS*/ +searchhandle(string pattern, float caseinsensitive, float quiet) search_begin = #444; /* Part of DP_QC_FS_SEARCH + initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle. */ + +void(searchhandle handle) search_end = #445; /* Part of DP_QC_FS_SEARCH*/ +float(searchhandle handle) search_getsize = #446; /* Part of DP_QC_FS_SEARCH + Retrieves the number of files that were found. */ + +string(searchhandle handle, float num) search_getfilename = #447; /* Part of DP_QC_FS_SEARCH + Retrieves name of one of the files that was found by the initial search. */ + +string(string cvarname) cvar_string = #448; /* Part of DP_QC_CVAR_STRING*/ +entity(entity start, .float fld, float match) findflags = #449; /* Part of DP_QC_FINDFLAGS*/ +entity(.float fld, float match) findchainflags = #450; /* Part of DP_QC_FINDCHAINFLAGS*/ +float(entity ent, string tagname) gettagindex = #451; /* Part of DP_MD3_TAGSINFO*/ +vector(entity ent, float tagindex) gettaginfo = #452; /* Part of DP_MD3_TAGSINFO + Obtains the current worldspace position+orientation of the bone or tag from the given entity. The return value is the world coord, v_forward, v_right, v_up are also set according to the bone/tag's orientation. */ + +entity(float entnum) edict_num = #459; /* Part of DP_QC_EDICT_NUM*/ +strbuf() buf_create = #460; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle) buf_del = #461; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle) buf_getsize = #462; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle_from, strbuf bufhandle_to) buf_copy = #463; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float sortprefixlen, float backward) buf_sort = #464; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, string glue) buf_implode = #465; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, float string_index) bufstr_get = #466; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index, string str) bufstr_set = #467; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle, string str, float order) bufstr_add = #468; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index) bufstr_free = #469; /* Part of DP_QC_STRINGBUFFERS*/ +float(float s) asin = #471; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float c) acos = #472; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float t) atan = #473; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float c, float s) atan2 = #474; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float a) tan = #475; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(string s) strlennocol = #476; /* Part of DP_QC_STRINGCOLORFUNCTIONS*/ +string(string s) strdecolorize = #477; /* Part of DP_QC_STRINGCOLORFUNCTIONS*/ +string(float uselocaltime, string format, ...) strftime = #478; /* Part of DP_QC_STRFTIME*/ +float(string s, string separator1, ...) tokenizebyseparator = #479; /* Part of DP_QC_TOKENIZEBYSEPARATOR*/ +string(string s) strtolower = #480; /* Part of DP_QC_STRING_CASE_FUNCTIONS*/ +string(string s) strtoupper = #481; /* Part of DP_QC_STRING_CASE_FUNCTIONS*/ +string(string s) cvar_defstring = #482; /* Part of DP_QC_CVAR_DEFSTRING*/ +void(vector origin, string sample, float volume, float attenuation) pointsound = #483; /* Part of DP_SV_POINTSOUND*/ +string(string search, string replace, string subject) strreplace = #484; /* Part of DP_QC_STRREPLACE*/ +string(string search, string replace, string subject) strireplace = #485; /* Part of DP_QC_STRREPLACE*/ +vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; /* Part of DP_QC_GETSURFACEPOINTATTRIBUTE*/ +float(string name) gecko_create = #487; /* Part of DP_GECKO_SUPPORT*/ +void(string name) gecko_destroy = #488; /* Part of DP_GECKO_SUPPORT*/ +void(string name, string URI) gecko_navigate = #489; /* Part of DP_GECKO_SUPPORT*/ +float(string name, float key, float eventtype) gecko_keyevent = #490; /* Part of DP_GECKO_SUPPORT*/ +void(string name, float x, float y) gecko_mousemove = #491; /* Part of DP_GECKO_SUPPORT*/ +void(string name, float w, float h) gecko_resize = #492; /* Part of DP_GECKO_SUPPORT*/ +vector(string name) gecko_get_texture_extent = #493; /* Part of DP_GECKO_SUPPORT*/ +float(float caseinsensitive, string s, ...) crc16 = #494; /* Part of DP_QC_CRC16*/ +float(string name) cvar_type = #495; /* Part of DP_QC_CVAR_TYPE*/ +float() numentityfields = #496; /* Part of DP_QC_ENTITYDATA*/ +string(float fieldnum) entityfieldname = #497; /* Part of DP_QC_ENTITYDATA*/ +float(float fieldnum) entityfieldtype = #498; /* Part of DP_QC_ENTITYDATA*/ +string(float fieldnum, entity ent) getentityfieldstring = #499; /* Part of DP_QC_ENTITYDATA*/ +float(float fieldnum, entity ent, string s) putentityfieldstring = #500; /* Part of DP_QC_ENTITYDATA*/ +void(float effectindex, entity own, vector org_from, vector org_to, vector dir_from, vector dir_to, float countmultiplier, optional float flags) boxparticles = #502; +string(string filename, optional float makereferenced) whichpack = #503; /* Part of DP_QC_WHICHPACK + Returns the pak file name that contains the file specified. progs/player.mdl will generally return something like 'pak0.pak'. If makereferenced is true, clients will automatically be told that the returned package should be pre-downloaded and used, even if allow_download_refpackages is not set. */ + +__variant(float entnum, float fieldnum) getentity = #504; /* + Looks up fields from non-csqc-visible entities. The entity will need to be within the player's pvs. fieldnum should be one of the GE_ constants. */ + +string(string in) uri_escape = #510; /* Part of DP_QC_URI_ESCAPE*/ +string(string in) uri_unescape = #511; /* Part of DP_QC_URI_ESCAPE*/ +float(entity ent) num_for_edict = #512; +float(string uril, float id, optional string postmimetype, optional string postdata) uri_get = #513; /* Part of DP_QC_URI_GET + uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned + returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string */ + +float(string str) tokenize_console = #514; +float(float idx) argv_start_index = #515; +float(float idx) argv_end_index = #516; +void(strbuf strbuf) buf_cvarlist = #517; +string(string cvarname) cvar_description = #518; +float(optional float timetype) gettime = #519; +string(float keynum) keynumtostring_omgwtf = #520; +string(string command, optional float bindmap) findkeysforcommand = #521; +void(string s) loadfromdata = #529; /* + Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +void(string s) loadfromfile = #530; /* + Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +float(entity e, float channel) getsoundtime = #533; +float(string sample) soundlength = #534; +float(string filename, strbuf bufhandle) buf_loadfile = #535; +float(filestream filehandle, strbuf bufhandle, optional float startpos, optional float numstrings) buf_writefile = #536; +void(entity e, float physics_enabled) physics_enable = #540; /* + Enable or disable the physics attached to a MOVETYPE_PHYSICS entity. Entities which have been disabled in this way will stop taking so much cpu time. */ + +void(entity e, vector force, vector relative_ofs) physics_addforce = #541; /* + Apply some impulse directional force upon a MOVETYPE_PHYSICS entity. */ + +void(entity e, vector torque) physics_addtorque = #542; /* + Apply some impulse rotational force upon a MOVETYPE_PHYSICS entity. */ + +void(float trg) setmousetarget = #603; +float() getmousetarget = #604; +void(.../*, string funcname*/) callfunction = #605; /* + Invokes the named function. The function name is always passed as the last parameter and must always be present. The others are passed to the named function as-is */ + +void(filestream fh, entity e) writetofile = #606; /* + Writes an entity's fields to the named frik_file file handle. */ + +float(string s) isfunction = #607; +vector(float vidmode, optional float forfullscreen) getresolution = #608; +string(float keynum) keynumtostring_menu = #609; +string(string command, optional float bindmap) findkeysforcommand_dp = #610; +float(float type) gethostcachevalue = #611; /* Part of FTE_CSQC_SERVERBROWSER*/ +string(float type, float hostnr) gethostcachestring = #612; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(entity e, string s) parseentitydata = #613; /* + Reads a single entity's fields into an already-spawned entity. s should contain field pairs like in a saved game: {"foo1" "bar" "foo2" "5"} */ + +float(string key) stringtokeynum_menu = #614; +void() resethostcachemasks = #615; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float mask, float fld, string str, float op) sethostcachemaskstring = #616; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float mask, float fld, float num, float op) sethostcachemasknumber = #617; /* Part of FTE_CSQC_SERVERBROWSER*/ +void() resorthostcache = #618; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float fld, float descending) sethostcachesort = #619; /* Part of FTE_CSQC_SERVERBROWSER*/ +void() refreshhostcache = #620; /* Part of FTE_CSQC_SERVERBROWSER*/ +float(float fld, float hostnr) gethostcachenumber = #621; /* Part of FTE_CSQC_SERVERBROWSER*/ +float(string key) gethostcacheindexforkey = #622; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(string key) addwantedhostcachekey = #623; /* Part of FTE_CSQC_SERVERBROWSER*/ +string() getextresponse = #624; /* Part of FTE_CSQC_SERVERBROWSER*/ +string(string dnsname, optional float defport) netaddress_resolve = #625; +string(string fmt, ...) sprintf = #627; +float(entity e, float s) getsurfacenumtriangles = #628; +vector(entity e, float s, float n) getsurfacetriangle = #629; +string(string digest, string data, ...) digest_hex = #639; +#if defined(CSQC) || defined(MENU) +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 +#define K_LALT 132 +#define K_RALT -245 +#define K_LCTRL 133 +#define K_RCTRL -246 +#define K_LSHIFT 134 +#define K_RSHIFT -247 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 +#define K_KP_HOME 164 +#define K_KP_UPARROW 165 +#define K_KP_PGUP 166 +#define K_KP_LEFTARROW 161 +#define K_KP_5 162 +#define K_KP_RIGHTARROW 163 +#define K_KP_END 158 +#define K_KP_DOWNARROW 159 +#define K_KP_PGDN 160 +#define K_KP_ENTER 172 +#define K_KP_INS 157 +#define K_KP_DEL 167 +#define K_KP_SLASH 168 +#define K_KP_MINUS 170 +#define K_KP_PLUS 171 +#define K_KP_NUMLOCK 154 +#define K_KP_STAR 169 +#define K_KP_EQUALS 173 +#define K_MOUSE1 512 +#define K_MOUSE2 513 +#define K_MOUSE3 514 +#define K_MOUSE4 517 +#define K_MOUSE5 518 +#define K_MOUSE6 519 +#define K_MOUSE7 520 +#define K_MOUSE8 521 +#define K_MOUSE9 522 +#define K_MOUSE10 523 +#define K_LWIN 239 +#define K_RWIN 240 +#define K_APP -241 +#define K_SEARCH -242 +#define K_POWER 130 +#define K_VOLUP -243 +#define K_VOLDOWN -244 +#define K_JOY1 768 +#define K_JOY2 769 +#define K_JOY3 770 +#define K_JOY4 771 +#define K_AUX1 784 +#define K_AUX2 785 +#define K_AUX3 786 +#define K_AUX4 787 +#define K_AUX5 788 +#define K_AUX6 789 +#define K_AUX7 790 +#define K_AUX8 791 +#define K_AUX9 792 +#define K_AUX10 793 +#define K_AUX11 794 +#define K_AUX12 795 +#define K_AUX13 796 +#define K_AUX14 797 +#define K_AUX15 798 +#define K_AUX16 799 +#define K_AUX17 800 +#define K_AUX18 801 +#define K_AUX19 802 +#define K_AUX20 803 +#define K_AUX21 804 +#define K_AUX22 805 +#define K_AUX23 806 +#define K_AUX24 807 +#define K_AUX25 808 +#define K_AUX26 809 +#define K_AUX27 810 +#define K_AUX28 811 +#define K_AUX29 812 +#define K_AUX30 813 +#define K_AUX31 814 +#define K_AUX32 815 +#define K_PAUSE 153 +#define K_MWHEELUP 515 +#define K_MWHEELDOWN 516 +#define K_PRINTSCREEN 174 +#define K_CAPSLOCK 155 +#define K_SCROLLLOCK 156 +#define K_SEMICOLON 59 +#define K_TILDE 126 +#define K_BACKQUOTE 96 +#define K_BACKSLASH 92 +#endif +#pragma noref 0 diff --git a/csqc/csextradefs.qc b/csqc/csextradefs.qc new file mode 100644 index 000000000..6af540f44 --- /dev/null +++ b/csqc/csextradefs.qc @@ -0,0 +1,927 @@ +#include "../share/defs.h" + +#define DEFCVAR_FLOAT(n,d) var float autocvar_##n = d; +#define DEFCVAR_STRING(n,d) var string autocvar_##n = d; +#define CVARF(n) autocvar_##n +#define CVARS(n) autocvar_##n + +vector MENU_BG = '0.2 0.3 0.4'; +vector MENU_BG_DARK = '0.1 0.15 0.2'; +vector MENU_BORDER = '0.3 0.4 0.5'; +vector MENU_BG_WARNING = '0.4 0.3 0.2'; +vector MENU_BORDER_WARNING = '0.5 0.4 0.3'; +vector MENU_SELECTED = '0 1 0'; +vector MENU_UNSELECTED = '1 0 0'; +vector MENU_BUTTON = '0.3 0.4 0.5'; +//vector MENU_BUTTON_BORDER = '0.35 0.45 0.55'; +vector MENU_TEXT_1 = '1 1 1'; +vector MENU_TEXT_2 = '0.7 0.75 0.75'; +vector MENU_TEXT_3 = '1 1 0'; +vector MENU_TEXT_4 = '0.8 0.5 0'; +vector MENU_HIGHLIGHT = '1 1 1'; +vector MENU_TEXT_WARNING = '0.8 0 0'; +vector MENU_TEXT_GREEN = '0 0.8 0'; +vector MENU_DARKEN = '1 1 1'; + +vector SCOREB_HEADER = '0.7 0.7 0.7'; +vector SCOREB_NAME = '1 1 1'; +vector SCOREB_FIELD = '0.9 0.9 0.9'; +vector SCOREB_SELF_BG = '0.8 0.8 0.8'; +vector SCOREB_SPECTATED_BG = '0.8 0.8 0.2'; + +vector MENU_TEXT_SMALL = '8 8 0'; +vector MENU_TEXT_MEDIUM = '16 16 0'; +vector MENU_TEXT_LARGE = '24 24 0'; +float MENU_START_CONTENT = 32; + +vector MENU_TEXT_BLUE_FO = '0.3 0.4 0.7'; +vector MENU_TEXT_RED_FO = '0.7 0.4 0.3'; +vector MENU_TEXT_YELLOW_FO = '0.7 0.7 0.3'; +vector MENU_TEXT_GREEN_FO = '0.4 0.7 0.3'; +vector MENU_TEXT_SPEC_FO = '0.1 0.1 0.1'; + +vector TEXT_TEAM_COLOUR[] = { + MENU_TEXT_BLUE_FO, + MENU_TEXT_RED_FO, + MENU_TEXT_YELLOW_FO, + MENU_TEXT_GREEN_FO, + MENU_TEXT_SPEC_FO, +}; + +const float DEFAULT_VIEWHEIGHT = 22; +const vector PLAYER_MINS = [-16, -16, -24]; +const vector PLAYER_MAXS = [16, 16, 32]; + +#define FO_MENU_FLAG_USE_MOUSE 1 +#define FO_MENU_FLAG_CENTER 2 +#define FO_MENU_FLAG_SHOW_SHORTCUTS 4 +#define FO_MENU_FLAG_SHOW_VALUES 8 +#define FO_MENU_FLAG_WARNING 16 +#define FO_MENU_FLAG_ALLOW_INTERMISSION 32 + +#define FO_MENU_STATE_HIDDEN 0 +#define FO_MENU_STATE_NORMAL 1 +#define FO_MENU_STATE_DISABLED 2 +#define FO_MENU_STATE_SPACER 3 + +#define FO_MENU_MAX_OPTIONS 20 +#define FO_MENU_TRANSPARENCY 0.2 + +#define MOTD_TIME 30 + +#define TEAM_SPECTATOR 9 +#define TEAM_OBSERVER 99 + +#define MAP_MAX_CHARS 20 + +#define MAX_SLOT_HISTORY_SIZE 10 + +.void() removefunc; +.float owned_by; +.string netname; +.float playerid; +float fo_hud_editor; +float fo_hud_menu_active; +vector ScreenSize; +float number_of_teams; +float last_selected_skin; +float last_team; +vector sentry_pos; +vector dispenser_pos; +vector touched_dispenser_pos; +//entity player_list_head; +float player_menu_type; +float admin_menu_next_update; +float captain1; +float captain2; +float captain1_temp; +float captain2_temp; +float is_admin; +float motd_expiry; +float quad_round; +float quad_rounds_total; +//float game_started; +float prematch; +float round_active; +float round_over; +float round_ending; +float showingscores; +float mapvote_expiry; +float num_mapvotes; +float num_mapvotes_filtered; +entity vote_selected_item; +float vote_selected_index; +float vote_list_offset; +entity current_vote; +string vote_list_filter; +float oldbuttons; +float zoom_scale; +float pick_up_time; +float flag_team; + +float slot_history[MAX_SLOT_HISTORY_SIZE]; +float slot_history_top, slot_under_stack; + +.string name; +.string description; +.string groupname; +.entity group; +.float team_num; +.float min_val; +.float max_val; +.float votecount; +.float localmap; + +.string hittext_str; +.float hittext_hitflags; +.float hittext_expires; + +void () SUB_Null = { +}; + +enum { + HUD_ALIGN_RIGHT, + HUD_ALIGN_LEFT, + HUD_ALIGN_CENTER +}; + +string HUD_ALIGN[] = { + "Right", + "Left", + "Center" +}; + +string HUD_HORIZONTAL_ALIGN[] = { + "Left", + "Center", + "Right" +}; + +string HUD_VERTICAL_ALIGN[] = { + "Top", + "Middle", + "Bottom" +}; + +string MENU_OPTION[] = { + "1","2","3","4","5","6","7","8","9","0","-","+" +}; + +const float HUD_SNAP_NONE = 0; +const float HUD_SNAP_LEFT = 1; +const float HUD_SNAP_CENTER = 2; +const float HUD_SNAP_RIGHT = 4; +const float HUD_SNAP_TOP = 8; +const float HUD_SNAP_VCENTER = 16; +const float HUD_SNAP_BOTTOM = 32; + +const float HUD_SNAP_TOP_LEFT = 9; +const float HUD_SNAP_TOP_CENTER = 10; +const float HUD_SNAP_TOP_RIGHT = 12; +const float HUD_SNAP_VCENTER_LEFT = 17; +const float HUD_SNAP_VCENTER_CENTER = 18; +const float HUD_SNAP_VCENTER_RIGHT = 20; +const float HUD_SNAP_BOTTOM_LEFT = 33; +const float HUD_SNAP_BOTTOM_CENTER = 34; +const float HUD_SNAP_BOTTOM_RIGHT = 36; + +const float HUD_SNAP_NORTH = 64; +const float HUD_SNAP_EAST = 128; +const float HUD_SNAP_SOUTH = 256; +const float HUD_SNAP_WEST = 512; + +typedef struct +{ + string icon; +} FO_Hud_Icons; + +FO_Hud_Icons HudIcons2[39] = { + {ICON_CLIPSIZE}, + {ICON_FRAGSTREAK}, + {ICON_CAPS}, + {ICON_GREN_NONE}, + {ICON_GREN_NORMAL}, + {ICON_GREN_NAIL}, + {ICON_GREN_EMP}, + {ICON_GREN_GAS}, + {ICON_GREN_CONCUSSION}, + {ICON_GREN_FLASH}, + {ICON_GREN_FLARE}, + {ICON_GREN_NAPALM}, + {ICON_GREN_CALTROP}, + {ICON_GREN_BLAST}, + {ICON_GREN_SHOCK}, + {ICON_GREN_BURST}, + {ICON_IDENTIFY}, + {ICON_SCOUT}, + {ICON_SNIPER}, + {ICON_SOLDIER}, + {ICON_DEMOMAN}, + {ICON_MEDIC}, + {ICON_HWGUY}, + {ICON_PYRO}, + {ICON_SPY}, + {ICON_ENGINEER_SG}, + {ICON_ENGINEER_DISP}, + {ICON_SECURITY_BUTTON_GREEN}, + {ICON_SECURITY_BUTTON_YELLOW}, + {ICON_SECURITY_BUTTON_RED}, + {ICON_SECURITY_BUTTON_BLUE}, + {ICON_SECURITY_BUTTON_ANY}, + {ICON_FLAG_ANY}, + {ICON_FLAG_BLUE}, + {ICON_FLAG_RED}, + {ICON_FLAG_YELLOW}, + {ICON_FLAG_GREEN}, + {"textures/wad/num_percent"}, + {"textures/wad/anum_percent"} +}; + +FO_Hud_Icons FaceIcons[] = { + //normal + {"textures/wad/face1"}, + {"textures/wad/face2"}, + {"textures/wad/face3"}, + {"textures/wad/face4"}, + {"textures/wad/face5"}, + //pain + {"textures/wad/face_p1"}, + {"textures/wad/face_p2"}, + {"textures/wad/face_p3"}, + {"textures/wad/face_p4"}, + {"textures/wad/face_p5"}, +}; + +FO_Hud_Icons FaceInvisibleIcon = {"textures/wad/face_invis"}; +FO_Hud_Icons FaceInvisibleInvulnerableIcon = {"textures/wad/face_inv2"}; +FO_Hud_Icons FaceInvulnerableIcon = {"textures/wad/face_invul2"}; +FO_Hud_Icons FaceQuadIcon = {"textures/wad/face_quad"}; + +FO_Hud_Icons ArmourIcons[] = { + {"textures/wad/disc"}, + {"textures/wad/sb_armor1"}, + {"textures/wad/sb_armor2"}, + {"textures/wad/sb_armor3"}, +}; +FO_Hud_Icons AmmoIcons[] = { + {"textures/wad/sb_shells"}, + {"textures/wad/sb_nails"}, + {"textures/wad/sb_rocket"}, + {"textures/wad/sb_cells"}, +}; + + +typedef struct { + string ClipSize; + float FragStreak; + float Caps; + string Identify; + string MOTD; + string Hint; + //1 = Clanmode, 2 = Quadmode, 4 = Duelmode, 8 = Prematch + float GameMode; + float ReadyStatus; + float CountdownStarted; + // scout + float ScannerOn; + float ScannerRange; + float ScannerTeamNo; + float ScannerPlayerClass; + float ScannerTFItemsFlags; + // sniper + float SniperDam; + // demoman + float IsDetpacking; + float DetpackLeft; + // medic + float AuraActive; + float HealCount; + float HealAmount; + float AuraStatus; + // hwguy + float LockedCannon; + // pyro + float AirBlast; + // spy + float IsUndercover; + float InvisOnly; + float UndercoverTeam; + float UndercoverSkin; + float UndercoverTimer; + float DisguiseTeam; + float QueueTeam; + float DisguiseSkin; + float QueueSkin; + // engineer + float IsBuilding; + float BuildingPercentage; + float HasSentry; + float SentryLevel; + float SentryHealth; + float SentryAmmoShells; + float SentryAmmoRockets; + float HasDispenser; + float DispenserHealth; +} FO_SBAR; +FO_SBAR SBAR; + +typedef struct { + float ceasefire; + float pubmode; + float clanmode; + float quadmode; + float duelmode; + float new_balance; + float quad_rounds; + float quad_round_time; + float fo_login_required; + float fo_matchrated; + float play_to_completion; + float captainmode; + float timelimit; + float fraglimit; + float class_override; + float class_limit_scout; + float class_limit_sniper; + float class_limit_soldier; + float class_limit_demoman; + float class_limit_medic; + float class_limit_hwguy; + float class_limit_pyro; + float class_limit_spy; + float class_limit_engineer; +} SERVER_SETTINGS; +SERVER_SETTINGS SERVER_ADMIN; + +typedef struct +{ + float x; + float y; +} MouseStruct; + +MouseStruct Mouse; +MouseStruct PrevMouse; + +typedef struct { + string filename; + vector colour; +} HUDIcon; + +typedef struct { + float id; + string message; + //string model; + entity model; + float timeleft; + float state; + vector loc; + string carrier; + string locname; + HUDIcon icon; +} FlagInfoLine; + +FlagInfoLine FlagInfoLines[10]; + +/* +typedef struct { + float team1score; + float team2score; + float team3score; + float team4score; +} FO_TeamScores; + +FO_TeamScores TS; +*/ + +float TeamScore[4]; + +enum PanelID:float { + HUDP_INVALID = 0, // Reserve 0 so we can recognize an uninit menu + HUDP_FIRST = 100, + HUDP_TEAMSCORE = HUDP_FIRST, // We put this first so that we open to below. + HUDP_MAP_MENU, // We put these up front so that they are + HUDP_SHOWSCORES, // rendered under other elements when editing + HUDP_MENU, + HUDP_MENU_HINT, + HUDP_CLIPSIZE, + HUDP_RELOADPROGRESS, + HUDP_FRAGSTREAK, + HUDP_CAPS, + HUDP_GREN1, + HUDP_GREN2, + HUDP_SPECIAL, + HUDP_IDENTIFY, + HUDP_FLAGINFO, + HUDP_GRENTIMER, + HUDP_MOTD, + HUDP_GAME_MODE, + HUDP_NOTIFICATION, + HUDP_HEALTH, + HUDP_FACE, + HUDP_AMMO, + HUDP_AMMOICON, + HUDP_ARMOUR, + HUDP_ARMOURICON, + HUDP_INVSHELLS, + HUDP_INVNAILS, + HUDP_INVROCKETS, + HUDP_INVCELLS, + HUDP_GAMECLOCK, + HUDP_QUAD, + HUDP_PENT, + HUDP_RING, + HUDP_SUIT, + HUDP_KEY1, + HUDP_KEY2, + HUDP_RUNE1, + HUDP_RUNE2, + HUDP_RUNE3, + HUDP_RUNE4, + HUDP_GUN2, + HUDP_GUN3, + HUDP_GUN4, + HUDP_GUN5, + HUDP_GUN6, + HUDP_GUN7, + HUDP_GUN8, + HUDP_SPEED, + HUDP_SPEEDBAR, + HUDP_PING, + HUDP_OPTIONS, // Should be the last HUD element so that it's always on top. + HUDP_LAST = HUDP_OPTIONS, + + HUDP_GAP = 500, // Just to make ids larger than this distinguishable. + + HUDP_OPTION_LIST, + HUDP_OPTION_PANEL, + + HUDB_FIRST, + HUDB_MAP_VOTE, + HUDB_MAP_CHANGE, + HUDB_OPTION_LIST, + HUDB_OPTION_SCROLLUP, + HUDB_OPTION_SCROLLDOWN, + HUDB_LAST, + + HUDE_FIRST, + HUDE_OPTION_SCALE_SLIDER, + HUDE_OPTION_TEXTSCALE_SLIDER, + HUDE_OPTION_SCALE_SCROLL, + HUDE_OPTION_TEXTALIGN_TOGGLE, + HUDE_OPTION_SHOWHIDE_TOGGLE, + HUDE_OPTION_HSNAP_TOGGLE, + HUDE_OPTION_VSNAP_TOGGLE, + HUDE_OPTION_SHOWALL_TOGGLE, + HUDE_LAST, + + HUDL_FIRST = HUDB_LAST, + HUDL_OPTION_BASE = 1000, + HUDL_MAP_BASE = 2000, + + HUD_LAST = 10000, +}; + +// hud stuff +typedef struct { + PanelID id; + string SaveName; + string Name; + vector Position; + vector FillSize; + float Scale; + float TextScale; + float Display; + float Orientation; + float System; // System panel display is not user managed (e.g. vote menu) + void(float display, string text) drawPanel; + string() getValue; + float Style; + float Snap; + float Status; + float SnapID; +} FO_Hud_Panel; + +FO_Hud_Panel NullPanel; +FO_Hud_Panel NewPanel; +FO_Hud_Panel* MenuPanel; +float Editor_SelectedPanel_Index; + +typedef struct { + vector MousePos; +} FO_Hud_Settings; +FO_Hud_Settings HudSettings; + +enumflags { + FL_GT_SOUND, + FL_GT_THROWN, + FL_GT_ADJPING, + FL_GT_LOCAL, +}; + +void(string identify) Hud_DrawIdentifyPanel; +void (float display, string text) Hud_DrawHudOptionsPanel; +void (float playerclass) Hud_DrawClassInfoPanel; +void () Hud_DrawFlagStatusBar; +void () Hud_DrawTeamScorePanel; +void () Hud_DrawMapMenuPanel; +float(PanelID id, vector pos, vector size, float alpha, float enabled) hud_panel; +vector(PanelID) getPosition; +vector(PanelID) getFillSize; + +enum { + D_skip, + D_drawpic, + D_drawstring, + D_drawfill, +}; + +struct Draw_Args { + PanelID id; + int type; + vector pos; + string s_arg; + vector size; + vector rgb; + float alpha; + float drawflag; +}; + +struct { + int enabled; + PanelID active_panel; + float last_update; + int draw_direct; + Draw_Args draw_cache[1000]; + int draw_count; + float free_cache[1000]; + int free_count; +} hud_render_cache; + +DEFCVAR_FLOAT(fo_hud_cache, 1) +DEFCVAR_FLOAT(fo_hud_fps, 60) + +enum { + HRC_NF_NO_UPDATE = 0, + HRC_NF_FULL, + HRC_NF_PARTIAL, +}; + +inline float HRC_fps_time() { return 1/max(min(CVARF(fo_hud_fps), 77 * 2), 30); } + +void HRC_Invalidate(PanelID panel) { + float i, j; + + for (i = 0; i < hud_render_cache.draw_count; i++) { + if (hud_render_cache.draw_cache[i].id == panel + 1) { + hud_render_cache.draw_cache[i].id = 0; + hud_render_cache.draw_cache[i].type = D_skip; + hud_render_cache.free_cache[hud_render_cache.free_count++] = i; + } + } + + // Ascending order sort so that we don't end up inverting rendering order + // when pulling from the free-list. + for (i = 0; i < hud_render_cache.free_count; i++) + for (j = i + 1; j < hud_render_cache.free_count; j++) + if (hud_render_cache.free_cache[i] < hud_render_cache.free_cache[j]) { + float tmp = hud_render_cache.free_cache[i]; + hud_render_cache.free_cache[i] = hud_render_cache.free_cache[j]; + hud_render_cache.free_cache[j] = tmp; + } +} + +void HRC_InvalidateAll() { + hud_render_cache.draw_count = 0; + hud_render_cache.free_count = 0; +} + +void HRC_SetActive(PanelID panel) { + hud_render_cache.active_panel = panel; +} + +FO_Hud_Panel* getHudPanel(PanelID panelid); + +// Returns true if HUD should be re-rendered this frame. +float HRC_NewFrame() { + // Rate limit even the more complicated enable checks. + /* if (time < hud_render_cache.last_update + HRC_fps_time()) */ + /* return !hud_render_cache.enabled; */ + /* hud_render_cache.last_update = time; */ + + if (!CVARF(fo_hud_cache) || + fo_hud_menu_active || fo_hud_editor || + getHudPanel(HUDP_MAP_MENU)->Display) { + // Either explicitly disabled, or rendering a more complicated scene + // like the editor for which we'll just display in place. + hud_render_cache.enabled = FALSE; + return TRUE; + } + + hud_render_cache.enabled = TRUE; + return FALSE; +} + +static Draw_Args* next_args() { + float idx; + if (hud_render_cache.free_count) + idx = hud_render_cache.free_cache[--hud_render_cache.free_count]; + else + idx = hud_render_cache.draw_count++; + + Draw_Args* result = &hud_render_cache.draw_cache[idx]; + result->id = hud_render_cache.active_panel + 1; // 1-based, 0 => free. + return result; +} + +void HRC_drawpic(vector pos, string pic, vector size, vector rgb='1 1 1', + float alpha=1, float drawflag=0) { + if (pic == "") + return; + if (!hud_render_cache.enabled) { + drawpic(pos, pic, size, rgb, alpha, drawflag); + } else { + Draw_Args* args = next_args(); + args->type = D_drawpic; + args->pos = pos; + args->s_arg = pic; + args->size = size; + args->rgb = rgb; + args->alpha = alpha; + args->drawflag = drawflag; + } +} + +void HRC_drawstring(vector pos, string text, vector size, vector rgb='1 1 1', + float alpha=1, float drawflag=0) { + if (text == "") + return; + if (!hud_render_cache.enabled) { + drawstring(pos, text, size, rgb, alpha, drawflag); + } else { + Draw_Args* args = next_args(); + args->type = D_drawstring; + args->pos = pos; + args->s_arg = text; + args->size = size; + args->rgb = rgb; + args->alpha = alpha; + args->drawflag = drawflag; + } +} + +void HRC_drawfill(vector pos, vector size, vector rgb, + float alpha=1, float drawflag=0) { + if (!hud_render_cache.enabled) { + drawfill(pos, size, rgb, alpha, drawflag); + } else { + Draw_Args* args = next_args(); + args->type = D_drawfill; + args->pos = pos; + args->size = size; + args->rgb = rgb; + args->alpha = alpha; + args->drawflag = drawflag; + } +} + +void HRC_Render() { + if (!hud_render_cache.enabled) + return; + + for (int i = 0; i < hud_render_cache.draw_count; i++) { + Draw_Args* args = &hud_render_cache.draw_cache[i]; + if (args->type == D_skip) + continue; + else if (args->type == D_drawpic) + drawpic(args->pos, args->s_arg, args->size, args->rgb, args->alpha, args->drawflag); + else if (args->type == D_drawstring) + drawstring(args->pos, args->s_arg, args->size, args->rgb, args->alpha, args->drawflag); + else if (args->type == D_drawfill) + drawfill(args->pos, args->size, args->rgb, args->alpha, args->drawflag); + } +} + +DEFCVAR_FLOAT(fo_grentimer_ping_frac, 1); // Fraction of ping to correct for. +DEFCVAR_FLOAT(fo_grentimer_nostack, 0); // When set, only play soonest timer. + +class CsGrenTimer; +CsGrenTimer grentimers[NUM_GREN_TIMERS]; + +class CsGrenTimer { + float index_; + float expires_at_; + float raw_expires_at_; + float flags_; + float grentype_; + float playing_; + + nonvirtual float() index { return index_; }; + nonvirtual float() active { return expires_at_ > time; }; + nonvirtual float() expiry { return expires_at_; }; + nonvirtual float() raw_expiry { return raw_expires_at_; }; + nonvirtual float() grentype { return grentype_; }; + + nonvirtual float(float flag) test_flag { return flags_ & flag; }; + nonvirtual void(float flag) set_flag { flags_ |= flag; }; + nonvirtual void(float flag) clear_flag { flags_ &= ~flag; }; + + nonvirtual float() sound_offset { + float offset = max(3.8 - (expires_at_ - time), 0); + return offset; + }; + + nonvirtual void(float expires_at, float grentype, float timer_flags) Set; + nonvirtual void() Stop; + + nonvirtual void() PauseSound { + playing_ = FALSE; + if (flags_ & FL_GT_SOUND) // -volume => stop. + soundupdate(this, CHAN_VOICE, "", -1, 0, 0, 0, 0); + }; + nonvirtual void() StartSound; + + nonvirtual void(float offset, float adj_sound) adj_sync { + if (!playing_) + return; + + raw_expires_at_ += offset; + if (adj_sound) { // Not yet working well + expires_at_ += offset; + StartSound(); + } + }; + + static float next_index_; + static CsGrenTimer last_; + + static void() Init { + for (float i = 0; i < grentimers.length; i++) + grentimers[i] = spawn(CsGrenTimer, index_: i); + }; + + static void() StopAll { + for (float i = 0; i < grentimers.length; i++) + grentimers[i].Stop(); + }; + + static CsGrenTimer() GetNext { + next_index_ = (next_index_ + 1) % grentimers.length; + last_ = grentimers[next_index_]; + return last_; + }; + + static CsGrenTimer(float exp, float eps) Match { + CsGrenTimer result = world; + float best = eps; + + for (float i = 0; i < grentimers.length; i++) { + float d = fabs(exp - grentimers[i].raw_expiry()); + if (d < best) { + best = d; + result = grentimers[i]; + } + } + return result; + }; + + static void() SyncPause { + // Time froze, but sound kept running, realign. + for (float i = 0; i < grentimers.length; i++) + if (grentimers[i].active()) + grentimers[i].PauseSound(); + }; + + static void() SyncUnpause { + // Time froze, but sound kept running, realign. + for (float i = 0; i < grentimers.length; i++) + if (grentimers[i].active()) + grentimers[i].StartSound(); + }; + + static void() UpdateSoundStack { + if (!CVARF(fo_grentimer_nostack)) + return; + + CsGrenTimer best = __NULL__; + + for (float i = 0; i < grentimers.length; i++) { + CsGrenTimer c = grentimers[i]; + if (c.active() && (best == __NULL__ || c.expiry() < best.expiry())) + best = c; + } + + if (best != __NULL__ && !best.playing_) + best.StartSound(); + }; + + static CsGrenTimer() GetLast { return last_; }; +}; + +// scoreboard stuff +struct FO_ScoreBoardLine{ + float ping; + float pl; + string classtext; + string name; + float team_no; + float score; + float caps; + float touches; + float kills; + float teamkills; + float deaths; + float afflicted; + float teamafflicted; + float damagegiven; + float damagetaken; + float ready; + float chat; +}; + +// fte maxclients is 255, right? +#define FO_SCOREBOARDLINES_LENGTH 32 + +string FO_ScoreBoardColumns[] = { + "ping", + "pl", + "class", + "score", + "name", + "caps", + "tchs", + "kils", + "tkil", + "dths", + "affl", + "tafl", + "dmgg", + "dmgt" +}; + +struct { + string icon_ready; + string icon_chat; + string icon_afk; +} FO_ScoreBoardAssets; + +DEFCVAR_FLOAT(fo_fte_hud, 0); +DEFCVAR_FLOAT(fo_legacy_sbar, 0); +DEFCVAR_FLOAT(fo_csjumpsounds, 1); +DEFCVAR_FLOAT(fo_forward_facing_sentry, 0); + +struct GameState { + float localentnum; + float is_player; + float is_alive; + float is_spectator; + float is_ceasefire; + float spawn_gen; +}; + +GameState game_state, prev_game_state; + +float team_no; +float crosshair_team_no; +float all_time; +float painfinished; +float jump_counter; +float last_id_time; + +vector FO_Hud_Icon_Size = [24, 24, 0]; +vector FO_Hud_Icon_Font_Size = [8, 8, 0]; + +#pragma noref 1 +#pragma warning error Q101 /*too many parms*/ +#pragma warning error Q105 /*too few parms*/ +#pragma warning enable F301 /*non-utf-8 strings*/ +#pragma warning enable F302 /*uninitialised locals*/ +#pragma target FTE +#ifndef CSQC +#define CSQC +#endif + +string(string s) strtrim = #0:strtrim; /* + Trims the whitespace from the start+end of the string. */ +float(float playernum, string keyname, optional float assumevalue) getplayerkeyfloat = #0:getplayerkeyfloat; /* + Cheaper version of getplayerkeyvalue that avoids the need for so many tempstrings. */ +float(string key, optional float assumevalue) serverkeyfloat = #0:serverkeyfloat; /* + Version of serverkey that returns the value as a float (which avoids tempstrings). */ + + +vector PM_Org(); +vector PM_Vel(); + +struct veci { + vector v; + float i; +}; + +void cvar_parse4(string s, __out veci target) { + tokenize(s); + target.v[0] = stof(argv(0)); + target.v[1] = stof(argv(1)); + target.v[2] = stof(argv(2)); + target.i = stof(argv(3)); +} + +float servertime; diff --git a/csqc/csprogs.src b/csqc/csprogs.src new file mode 100644 index 000000000..6870ce29e --- /dev/null +++ b/csqc/csprogs.src @@ -0,0 +1,39 @@ +#pragma target fte_5768 +#pragma optimise 3 +#pragma flag enable subscope +#pragma flag enable iffloat +#pragma flag enable lo + +#pragma progs_dat "../csprogs.dat" + +#includelist +csdefs.qc +csextradefs.qc + +../share/debug.qc +profile.qc +../share/commondefs.qc +../share/common_helpers.qc +../share/common_vote.qc +../share/physics.qc +../share/weapons.qc +../share/prediction.qc +../share/classes.qc +../share/animate.qc +../share/mcp_precache.qc +../share/engineer.qc +weapon_predict.qc +pmove.qc +tfx.qc +sui_sys.qc +vote.qc +status.qc +settings.qc +menu.qc +main.qc +events.qc +hitfeedback.qc +hud_helpers.qc +hud.qc +input.qc +#endlist diff --git a/csqc/events.qc b/csqc/events.qc new file mode 100644 index 000000000..0af4acc48 --- /dev/null +++ b/csqc/events.qc @@ -0,0 +1,671 @@ +void ParseSBAR(); +CsGrenTimer ParseGrenPrimed(float grentype, float explodes_at, + float timer_flags = 0); +void ParseHitFlag(vector targpos, float mitdmg, float rawdmg, float hitflag); +float StartGrenTimer(float primed_at, float expires_at, float grentype, float play_sound); +float FoLogin(string token, float print_error); + +void() CSQC_Parse_Event = { + float msgtype = readbyte(); + local float goalno; + entity te; + switch (msgtype) { + case MSG_FLAGINFOINIT: + float index = readfloat(); + goalno = readfloat(); + string mdl = readstring(); + float skinindex = readfloat(); + float ownerteam = readfloat(); + float iconindex = readfloat(); + + //use next available + if(index < 0) { + for(float i = 0; i < FlagInfoLines.length; i++) { + if(FlagInfoLines[i].id == 0) { + index = i; + break; + } + } + } + if(index >= 0 && index < MAX_FLAGINFO_LINES) { + FlagInfoLines[index].id = goalno; + FlagInfoLines[index].message = ""; + if(mdl) + precache_model(mdl); + te = spawn(); + te.renderflags = RF_VIEWMODEL | RF_DEPTHHACK | RF_NOSHADOW; + te.origin = [5, 0, 0]; + te.angles = '-60 0 0'; + te.skin = skinindex; + te.owned_by = ownerteam; + + string iconname = "sb_key1"; + vector iconcolour = '1 1 1'; + if(iconindex == FLAGINFO_ICON_FLAG) { + iconname = strcat("flag_", ftos(ownerteam)); + iconcolour = '1 1 1'; + } else if(iconindex == FLAGINFO_ICON_BUTTON) { + iconname = strcat("off_icon_glow_", ftos(ownerteam)); + iconcolour = '1 1 1'; + } + FlagInfoLines[index].icon.filename = iconname; + FlagInfoLines[index].icon.colour = iconcolour; + FlagInfoLines[index].model = te; + } + break; + case MSG_FLAGINFO: + string message = ""; + goalno = readfloat(); + float state = readfloat(); + float timeleft = -1; + vector droploc = '0 0 0'; + string carrier = ""; + string locname = ""; + switch (state) { + case FLAGINFO_HOME: + message = "^2HOME"; + break; + case FLAGINFO_CARRIED: + carrier = readstring(); + message = strcat("^1CARRIED^7 by ",carrier); + break; + case FLAGINFO_DROPPED: + message = "^3DROPPED^7"; + timeleft = readfloat(); + float showloc = readfloat(); + if(showloc == FLAGINFO_LOCATION) { + droploc_x = readcoord(); + droploc_y = readcoord(); + droploc_z = readcoord(); + locname = readstring(); + message = strcat(message," at ", locname); + } + break; + case FLAGINFO_RETURNING: + message = "^4RETURNING"; + break; + } + for(float i = 0; i < FlagInfoLines.length; i++) { + if(FlagInfoLines[i].id == goalno) { + FlagInfoLines[i].message = message; + FlagInfoLines[i].timeleft = timeleft; + FlagInfoLines[i].state = state; + FlagInfoLines[i].loc = droploc; + FlagInfoLines[i].carrier = carrier; + FlagInfoLines[i].locname = locname; + } + } + break; + case MSG_SBAR: + ParseSBAR(); + break; + case MSG_ID: + last_id_time = time; + SBAR.Identify = readstring(); + break; + case MSG_PLAYER_DIE: + zoom_scale = 1; + break; + case MSG_PLAYER_SPAWN: + zoom_scale = 1; + break; + case MSG_GRENPRIMED: + float entno = readentitynum(); + float grentype = readbyte(); + float explodes_at = readfloat(); + break; + case MSG_GRENTHROWN: + float entno = readentitynum(); + break; + case MSG_TFX_GRENTIMER: + float entnum = readentitynum(); + float explodes_at = readfloat(); + TFxGrenTimerUpdate(entnum, explodes_at); + break; + case MSG_HITFLAG: + vector targpos; + targpos_x = readcoord(); + targpos_y = readcoord(); + targpos_z = readcoord(); + float mitdmg = readshort(); + float rawdmg = readshort(); + float hitflag = readshort(); + ParseHitFlag(targpos, mitdmg, rawdmg, hitflag); + break; + case MSG_CLIENT_MENU: + float menutype = readfloat(); + switch (menutype) { + case CLIENT_MENU_TEAM: + number_of_teams = readfloat(); + for(float i = 0; i < 4; i++) { + if(i < number_of_teams) { + FO_MENU_TEAM.options[i].value = ftos(readbyte()); + if(((i + 1) != team_no) || (all_time != ALL_TIME_COLOUR)) { + FO_MENU_TEAM.options[i].state = FO_MENU_STATE_NORMAL; + } else { + FO_MENU_TEAM.options[i].state = FO_MENU_STATE_DISABLED; + } + } else { + FO_MENU_TEAM.options[i].state = FO_MENU_STATE_DISABLED; + } + } + + if(SBAR.GameMode & GAMEMODE_QUAD) { + switch (all_time) { + case ALL_TIME_ATTACK: + FO_MENU_TEAM.options[6].state = FO_MENU_STATE_DISABLED; + FO_MENU_TEAM.options[7].state = FO_MENU_STATE_NORMAL; + break; + case ALL_TIME_DEFENCE: + FO_MENU_TEAM.options[6].state = FO_MENU_STATE_NORMAL; + FO_MENU_TEAM.options[7].state = FO_MENU_STATE_DISABLED; + break; + default: + FO_MENU_TEAM.options[6].state = FO_MENU_STATE_NORMAL; + FO_MENU_TEAM.options[7].state = FO_MENU_STATE_NORMAL; + } + } else { + FO_MENU_TEAM.options[6].state = FO_MENU_STATE_DISABLED; + FO_MENU_TEAM.options[7].state = FO_MENU_STATE_DISABLED; + } + + FO_Menu_Team(2); + break; + case CLIENT_MENU_CLASS: + FO_Menu_Class(2); + break; + case CLIENT_MENU_DROPAMMO: + FO_Menu_DropAmmo(FALSE); + break; + case CLIENT_MENU_SCOUT: + float scanner_on = readbyte(); + float scanner_flags = readfloat(); + + FO_Menu_Scout(2, scanner_on, scanner_flags); + break; + case CLIENT_MENU_SPY: + SBAR.InvisOnly = readfloat(); + last_selected_skin = readfloat(); + last_team = readfloat(); + FO_Menu_Spy(2); + break; + case CLIENT_MENU_SPY_SKIN: + FO_Menu_Spy_Skin(2); + break; + case CLIENT_MENU_SPY_TEAM: + FO_Menu_Spy_Team(FALSE); + break; + case CLIENT_MENU_DETPACK: + FO_Menu_Detpack(2, readbyte()); + break; + case CLIENT_MENU_BUILD: + FO_Menu_Build(2); + break; + case CLIENT_MENU_ROTATE_SENTRY: + FO_Menu_Rotate_Sentry(FALSE); + break; + case CLIENT_MENU_FIX_DISPENSER: + FO_Menu_Dispenser_Fix(FALSE, readbyte()); + break; + case CLIENT_MENU_USE_DISPENSER: + touched_dispenser_pos = [readfloat(),readfloat(),readfloat()]; + FO_Menu_Dispenser_Use(FALSE); + break; + case CLIENT_MENU_ADMIN: + FO_Menu_Admin_Main(TRUE); + break; + case CLIENT_MENU_ADMIN_KICK: + FO_Menu_Admin_Players(TRUE, CLIENT_MENU_ADMIN_KICK, 0); + break; + case CLIENT_MENU_VOTE: + mapvote_expiry = time + readfloat(); + FO_MENU_VOTE.options[0].name = readstring(); + FO_MENU_VOTE.options[0].value = ftos(readfloat()); + FO_MENU_VOTE.options[1].name = readstring(); + FO_MENU_VOTE.options[1].value = ftos(readfloat()); + FO_MENU_VOTE.options[2].name = readstring(); + FO_MENU_VOTE.options[2].value = ftos(readfloat()); + FO_MENU_VOTE.options[3].name = readstring(); + FO_MENU_VOTE.options[3].value = ftos(readfloat()); + FO_MENU_VOTE.options[5].name = readstring(); + FO_MENU_VOTE.options[5].value = ftos(readfloat()); + FO_Menu_Vote(FALSE); + break; + case CLIENT_MENU_CAPTAIN_PICK: + FO_Menu_Admin_Players(TRUE, CLIENT_MENU_CAPTAIN_PICK, 0); + break; + case CLIENT_MENU_MAPS: + showVoteMenu(!getHudPanel(HUDP_MAP_MENU)->Display); + break; + } + break; + case MSG_CLASSES_UPDATE: + float civonly = readbyte(); + for(float i = 0; i < 10; i++) { + if(civonly) { + FO_MENU_CLASS.options[i].value = "-"; + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_CLASS) { + Menu_Cancel(); + } + } else { + float class_max = readfloat(); + if(class_max < 0) { + FO_MENU_CLASS.options[i].value = "-"; + FO_MENU_CLASS.options[i].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_CLASS.options[i].value = strcat(ftos(readbyte()),"/",ftos(class_max)); + FO_MENU_CLASS.options[i].state = FO_MENU_STATE_NORMAL; + } + } + } + + break; + case MSG_SENTRY_POS: + sentry_pos = [readfloat(),readfloat(),readfloat()]; + break; + case MSG_DISPENSER_POS: + dispenser_pos = [readfloat(),readfloat(),readfloat()]; + break; + case MSG_SERVER_ADMIN_INFO: + is_admin = TRUE; + SERVER_ADMIN.ceasefire = readbyte(); + SERVER_ADMIN.quad_rounds = readfloat(); + SERVER_ADMIN.quad_round_time = readfloat(); + SERVER_ADMIN.fo_login_required = readfloat(); + SERVER_ADMIN.fo_matchrated = readfloat(); + SERVER_ADMIN.play_to_completion = readfloat(); + SERVER_ADMIN.timelimit = readfloat(); + SERVER_ADMIN.fraglimit = readfloat(); + SERVER_ADMIN.clanmode = readfloat(); + SERVER_ADMIN.quadmode = readfloat(); + SERVER_ADMIN.duelmode = readfloat(); + SERVER_ADMIN.new_balance = readfloat(); + SERVER_ADMIN.pubmode = (((SERVER_ADMIN.clanmode & 1) || (SERVER_ADMIN.quadmode & 1) || (SERVER_ADMIN.duelmode & 1))?1:0) + + (((SERVER_ADMIN.clanmode & 2) || (SERVER_ADMIN.quadmode & 2) || (SERVER_ADMIN.duelmode & 2))?2:0); + SERVER_ADMIN.pubmode = 3 - SERVER_ADMIN.pubmode; //Invert + SERVER_ADMIN.captainmode = readfloat(); + break; + case MSG_CAPTAINS: + captain1 = readfloat(); + captain2 = readfloat(); + break; + case MSG_MOTD: + SBAR.MOTD = strcat(readstring(), "\n", readstring()); + SBAR.GameMode = readfloat(); + SBAR.CountdownStarted = FALSE; + motd_expiry = time + MOTD_TIME; + quad_rounds_total = readfloat(); + number_of_teams = readfloat(); + break; + case MSG_PREMATCH: + prematch = readbyte(); + round_active = readbyte(); + round_over = readbyte(); + SBAR.CountdownStarted = readbyte(); + //SBAR.GameMode = SBAR.GameMode - (SBAR.GameMode & 8) + ((prematch & 1)?8:0); + //SBAR.GameMode = SBAR.GameMode - (SBAR.GameMode & 16) + ((prematch & 2)?16:0); + //SBAR.GameMode = SBAR.GameMode - (SBAR.GameMode & 32) + ((prematch & 4)?32:0); + //motd_expiry = time + MOTD_TIME; + quad_round = readfloat(); + //game_started = readbyte(); + local float rtr = readfloat(); + if(rtr) { + round_ending = time + rtr; + } + FO_Hud_ShowPanel(HUDP_MOTD); + break; + case MSG_QUAD_ROUND_BEGIN: + break; + case MSG_TEAM_SCORES: + TeamScore[0] = readfloat(); + TeamScore[1] = readfloat(); + TeamScore[2] = readfloat(); + TeamScore[3] = readfloat(); + break; + case MSG_VOTE_UPDATE: + te = AddVoteMap(readstring(), "","",0,0,0,FALSE); + te.votecount = readfloat(); + if(readbyte()) { + current_vote = te; + } else { + if(current_vote == te) { + current_vote = world; + } + } + break; + case MSG_VOTE_MAP_ADD: + te = AddVoteMap(readstring(), readstring(), readstring(), readfloat(), readfloat(), readfloat(), FALSE); + te.votecount = readfloat(); + if(readbyte()) { + current_vote = te; + } else { + if(current_vote == te) { + current_vote = world; + } + } + break; + case MSG_VOTE_MAP_DELETE: + RemoveVoteMap(readstring(), FALSE); + break; + case MSG_LOGIN: + if (!FoLogin("", FALSE)) + print("You are not logged in\n"); + break; + case MSG_PAUSE: + CsGrenTimer::SyncPause(); + break; + case MSG_UNPAUSE: + CsGrenTimer::SyncUnpause(); + break; + case MSG_RELOADSOUND: + local float weapon = readfloat(); + FO_ReloadSound(weapon); + break; + case MSG_FLAG_PICKUP: + flag_team = readfloat(); + pick_up_time = time; + break; + case MSG_BUILDING: + SentryPreviewStop(); + break; + case MSG_FLAG_DROP: + pick_up_time = FALSE; + break; + } +} + +string cached_timer; +string GetGrenTimerSound() { + string wav = CVARS(fo_grentimersound); + + if (cached_timer != wav) { + precache_sound(wav); + cached_timer = wav; + } + return wav; +} + +static float GrenTimerVolume() { + if (game_state.is_spectator && CVARF(fo_spec_grentimervolume) != -1) + return CVARF(fo_spec_grentimervolume); + return CVARF(fo_grentimervolume); +} + +void CsGrenTimer::StartSound() { + if !(this.flags_ & FL_GT_SOUND) + return; + + string wav = GetGrenTimerSound(); + + // Note there's a bug where soundupdate returns false for a new sample, even + // though it's started. + soundupdate(this, CHAN_VOICE, wav, GrenTimerVolume(), 0, 0, 0, sound_offset()); + this.playing_ = TRUE; +} + +void CsGrenTimer::Set(float expires_at, float _grentype, float timer_flags) { + grentype_ = _grentype; + raw_expires_at_ = expires_at_ = expires_at; + flags_ = timer_flags; + playing_ = FALSE; + + if (this.flags_ & FL_GT_ADJPING) { + float rtt = pstate_pred.client_ping * CVARF(fo_grentimer_ping_frac); + if (RewindFlagEnabled(REWIND_GRENADES)) + rtt = max(0, rtt - (100 + FO_RewindGrenMs(_grentype))); + expires_at_ -= rtt / 1000; + } + + if (!CVARF(fo_grentimer_nostack)) + StartSound(); + else + UpdateSoundStack(); +} + +void CsGrenTimer::Stop() { + raw_expires_at_ = -1; + expires_at_ = -1; + PauseSound(); // Pause is really stop. + flags_ = 0; +} + +void StopGrenTimers() { + // New style. + for (float i = 0; i < NUM_GREN_TIMERS; i++) + grentimers[i].Stop(); +} + +CsGrenTimer ParseGrenPrimed(float grentype, float explodes_at, + float timer_flags = 0) { + if (grentype == GREN_FLARE || grentype == GREN_CALTROP) + return world; + + float timer_mode = CVARF(fo_grentimer); + switch (timer_mode) { + case 0: break; + case 1: timer_flags = FL_GT_SOUND; break; + // 2 [and something sane for anything we don't recognize.] + default: timer_flags |= FL_GT_SOUND | FL_GT_ADJPING; break; + } + + if (game_state.is_spectator) + timer_flags &= ~FL_GT_ADJPING; + + float debug_print_state = CVARF(fo_grentimer_debug) & 1; + + CsGrenTimer timer = CsGrenTimer::GetNext(); + timer.Set(explodes_at, grentype, timer_flags); + + if (debug_print_state) { + float ping = getplayerkeyfloat(player_localnum, INFOKEY_P_PING) / 1000; + + float expires_at = timer.expiry(); + + print(sprintf("expires_at=%0.2f explodes_at=%0.2f\n", expires_at, explodes_at)); + } + + return timer; +} +#define HITFLAG(_fl_name) (hitflags & (HITFLAG_##_fl_name)) + +static void PlayHitSound(float hitflags) { + float wantsnoarmoursound = CVARF(fo_hitaudio_noarmour); + float wantshurtselfsound = CVARF(fo_hitaudio_hurtself); + float wantshurtenemysound = CVARF(fo_hitaudio_hurtenemy); + float wantshurtteamsound = CVARF(fo_hitaudio_hurtteam); + + float wantskillselfsound = CVARF(fo_hitaudio_killself); + float wantskillenemysound = CVARF(fo_hitaudio_killenemy); + float wantskillteamsound = CVARF(fo_hitaudio_killteam); + + string snd = "", snd2 = ""; + + if (HITFLAG(SELF)) { + if (wantshurtselfsound) + snd = SND_HURTSELF; + if (wantskillselfsound && HITFLAG(KILLINGBLOW)) + snd2 = SND_KILLSELF; + } else if (HITFLAG(FRIENDLY)) { + if (wantshurtteamsound) + snd = SND_HURTTEAM; + if (wantskillteamsound && HITFLAG(KILLINGBLOW)) + snd2 = SND_KILLTEAM; + } else { + if (wantshurtenemysound) + snd = HITFLAG(MEATSHOT) ? SND_HURTENEMY_MEATSHOT : SND_HURTENEMY; + if (wantskillenemysound && HITFLAG(KILLINGBLOW)) + snd2 = SND_KILLENEMY; + + if (HITFLAG(HEADSHOT)) + localsound(SND_HEADSHOT, CHAN_AUTO, 1); + if (HITFLAG(NOARMOUR) && wantsnoarmoursound) + localsound(SND_NOARMOUR, CHAN_AUTO, 1); + } + + if (snd != "") + localsound(snd, CHAN_AUTO, 1); + if (snd2 != "") + localsound(snd2, CHAN_AUTO, 1); +} + +void NewHittext(entity hittext); + +void ParseHitFlag(vector targpos, float mitdmg, float rawdmg, float hitflags) { + if (HITFLAG(IMFLASHED)) + return; + + float want_hittext = CVARF(fo_hittext_enabled) && !HITFLAG(NOTEXT); + float want_hitsound = CVARF(fo_hitaudio_enabled) && !HITFLAG(NOAUDIO); + + if (want_hitsound) + PlayHitSound(hitflags); + + if (!want_hittext) + return; + + if (HITFLAG(SELF) || + (HITFLAG(FRIENDLY) && !CVARF(fo_hittext_friendly))) + return; + + entity te = spawn(); + + te.hittext_str = ftos((CVARF(fo_hittext_rawdamage)) ? rawdmg : mitdmg); + te.classname = CN_HITTEXT; + te.hittext_expires = time + CVARF(fo_hittext_duration); + te.hittext_hitflags = hitflags; + + setorigin(te, + [targpos_x, targpos_y, targpos_z + CVARF(fo_hittext_offset)]); + + NewHittext(te); +}; + +#undef HITFLAG + +void ParseSBAR() +{ + SBAR.ClipSize = readstring(); + SBAR.FragStreak = readfloat(); + SBAR.Caps = readfloat(); + float class_info = readfloat(); + + switch (class_info) + { + case PC_SCOUT: + SBAR.ScannerOn = readfloat(); + if (SBAR.ScannerOn) + { + SBAR.ScannerRange = readfloat(); + + if (SBAR.ScannerRange) + { + SBAR.ScannerTeamNo = readfloat(); + SBAR.ScannerPlayerClass = readfloat(); + SBAR.ScannerTFItemsFlags = readfloat(); + } + } + break; + case PC_SNIPER: + SBAR.SniperDam = readfloat(); + break; + case PC_DEMOMAN: + SBAR.IsDetpacking = readfloat(); + SBAR.DetpackLeft = readfloat(); + break; + case PC_MEDIC: + float medicaura = readfloat(); + if (medicaura) + { + SBAR.AuraActive = readfloat(); + if (SBAR.AuraActive) + { + SBAR.HealCount = readfloat(); + SBAR.HealAmount = readfloat(); + SBAR.AuraStatus = readfloat(); + } + } + break; + case PC_HVYWEAP: + SBAR.LockedCannon = readfloat(); + break; + case PC_PYRO: + SBAR.AirBlast = readfloat(); + break; + case PC_SPY: + SBAR.IsUndercover = readfloat(); + + if (SBAR.IsUndercover == 1) + { + SBAR.InvisOnly = readfloat(); + SBAR.UndercoverTeam = readfloat(); + SBAR.UndercoverSkin = readfloat(); + } + else if (SBAR.IsUndercover == 2) + { + SBAR.InvisOnly = readfloat(); + SBAR.UndercoverTimer = readfloat(); + SBAR.UndercoverTeam = readfloat(); + SBAR.DisguiseTeam = readfloat(); + SBAR.QueueTeam = readfloat(); + SBAR.UndercoverSkin = readfloat(); + SBAR.DisguiseSkin = readfloat(); + SBAR.QueueSkin = readfloat(); + } + break; + case PC_ENGINEER: + SBAR.IsBuilding = readfloat(); + if (SBAR.IsBuilding) + { + SBAR.BuildingPercentage = readfloat(); + } + + SBAR.HasSentry = readfloat(); + if (SBAR.HasSentry) + { + SBAR.SentryLevel = readfloat(); + SBAR.SentryHealth = readfloat(); + SBAR.SentryAmmoShells = readfloat(); + SBAR.SentryAmmoRockets = readfloat(); + } + + SBAR.HasDispenser = readfloat(); + if (SBAR.HasDispenser) + { + SBAR.DispenserHealth = readfloat(); + } + break; + } +} + +float(float ent_num, float channel, string soundname, float vol, float attenuation, vector pos, float pitchmod) CSQC_Event_Sound = { + // Sound sent to our predicted weapon is expected to have been client-side. + if (ent_num == pengine.pweap_ent.entnum) + return 1; + + // Sounds from remote entities are not local and we should always play. + if (ent_num != player_localentnum || game_state.is_spectator) + return 0; + + // Sounds below this line are conditionally filtered. + switch(soundname) { + case "dash.wav": + if (PM_Enabled() & !CVARF(fopm_nonudge)) + return 1; + break; + case "player/plyrjmp8.wav": + case "player/land.wav": + case "player/land2.wav": + case "player/h2ojump.wav": + case "misc/water1.wav": + case "misc/water2.wav": + case "player/inlava.wav": + case "player/inh2o.wav": + case "player/slimbrn2.wav": + case "misc/outwater.wav": + if (CSQC_JumpSounds_Active()) + return 1; + break; + } + + return 0; +} diff --git a/csqc/hitfeedback.qc b/csqc/hitfeedback.qc new file mode 100644 index 000000000..290c72661 --- /dev/null +++ b/csqc/hitfeedback.qc @@ -0,0 +1,109 @@ +static entity render_hittexts[40]; +static float num_hittexts, num_render; +static float next_render_update; + +static vector clr_noarmour, clr_reg, clr_friendly; + +static void AddRenderableHittext(entity hittext) { + if (num_render < render_hittexts.length) + render_hittexts[num_render++] = hittext; +} + +void NewHittext(entity hittext) { + num_hittexts++; + AddRenderableHittext(hittext); +} + +static void RenderHitText(entity p) { + const float maxd = 1500, mind = 0; + vector po = p.origin; + vector o = PM_Org(); + + float rem = p.hittext_expires - time; + po.z += (CVARF(fo_hittext_duration) - rem) * CVARF(fo_hittext_speed); + vector direc = normalize(po - PM_Org()); + makevectors(getviewprop(VF_ANGLES)); + + float d = v_forward * direc; + + if(d <= 0 || rem <= 0) + return; + + float diff = vlen(po - o); + if (diff > maxd) + return; + + traceline(o, po, MOVE_NOMONSTERS, p); + if (trace_fraction < 1) + return; + + vector clr; + + if (p.hittext_hitflags & HITFLAG_FRIENDLY) { + clr = clr_friendly; + } else { + if (CVARF(fo_hittext_noarmour) && + (p.hittext_hitflags & HITFLAG_NOARMOUR)) + clr = clr_noarmour; + else + clr = clr_reg; + } + + vector c = project(po); + + vector size = '1 1 1' * CVARF(fo_hittext_size); + size *= (maxd - max(diff, mind)) / (maxd - mind); + + string str = p.hittext_str; + // stringwidth() is technically more correct here, but there's a bunch of + // these and we have to draw them every frame so exploit fixed size font to + // compute inline. + c.x -= strlen(str) * size_x / 2; + + // Fade out + float alpha = CVARF(fo_hittext_alpha); + + float fade_time = max(0.5, CVARF(fo_hittext_alpha) * 0.2); + if (rem < fade_time) + alpha = rem/fade_time * CVARF(fo_hittext_alpha); + + drawstring(c, str, size, clr, alpha, 0); +} + +static void UpdateHitTextList() { + if (time < next_render_update) + return; + + // Convert these periodically + clr_reg = stov(CVARS(fo_hittext_colour)); + clr_noarmour = stov(CVARS(fo_hittext_colour2)); + clr_friendly = stov(CVARS(fo_hittext_colour3)); + + next_render_update = time + 0.1; + num_render = 0; + + int count; + entity* hittexts = find_list(classname, CN_HITTEXT, EV_STRING, count); + + num_hittexts = 0; + for (int i = 0; i < count; i++) { + entity ht = hittexts[i]; + if (time > ht.hittext_expires) { + remove(ht); + continue; + } + + num_hittexts++; + AddRenderableHittext(ht); + } +} + +void RenderHitTexts() { + if (num_hittexts) + UpdateHitTextList(); + + if (CVARF(fo_hittext_enabled)) { + for (int i = 0; i < num_render; i++) + RenderHitText(render_hittexts[i]); + } +} diff --git a/csqc/hud.qc b/csqc/hud.qc new file mode 100644 index 000000000..01dd92bb3 --- /dev/null +++ b/csqc/hud.qc @@ -0,0 +1,1180 @@ +void Hud_WriteCfg(string path) +{ + // this overwrites + float filehandle; + filehandle = fopen(path, FILE_WRITE); + string line = ""; + + for(float i = HUDP_FIRST; i <= HUDP_LAST; i++) + line = GetPanelString(line, getHudPanel(i)); + + fputs(filehandle, line); + fclose(filehandle); +} + +void FO_Hud_Editor_LoadDefaultSettings() +{ + getHudPanel(HUDP_OPTIONS)->drawPanel = Hud_DrawHudOptionsPanel; + vector vsize = (vector)getproperty(VF_SCREENVSIZE); + float width = vsize_x; + float height = vsize_y; + + // check struct, put defaults in + float yoffset = height - 64; + + //Default menus, id, ready and MOTD to centre of the screen + getHudPanel(HUDP_GAME_MODE)->Position = [width - getHudPanel(HUDP_GAME_MODE)->FillSize.x, 30]; + getHudPanel(HUDP_GAME_MODE)->Orientation = FO_HUD_INSERT_AFTER; + getHudPanel(HUDP_MOTD)->Position = [(width / 2) - (getHudPanel(HUDP_MOTD)->FillSize.x / 2), 30]; + getHudPanel(HUDP_NOTIFICATION)->Position = [(width / 2) - (getHudPanel(HUDP_NOTIFICATION)->FillSize.x / 2), 30]; + getHudPanel(HUDP_MENU_HINT)->Position = [(width / 2) - (getHudPanel(HUDP_MENU)->FillSize.x / 2), 80]; + getHudPanel(HUDP_MENU_HINT)->Orientation = FO_HUD_INSERT_MIDDLE; + getHudPanel(HUDP_MENU)->Position = [(width / 2) - (getHudPanel(HUDP_MENU)->FillSize.x / 2), 120]; + getHudPanel(HUDP_IDENTIFY)->Position = [(width / 2) - (getHudPanel(HUDP_IDENTIFY)->FillSize.x / 2), height - 100]; + getHudPanel(HUDP_CLIPSIZE)->Position = [(width / 2) + 16, height - 50]; + getHudPanel(HUDP_CLIPSIZE)->Scale = 0.75; + getHudPanel(HUDP_GREN1)->Position = [(width / 2) - 16 - (getHudPanel(HUDP_GREN1)->FillSize.x * 3), height - 50]; + getHudPanel(HUDP_GREN1)->Scale = 0.75; + getHudPanel(HUDP_GREN2)->Position = [(width / 2) - 16 - getHudPanel(HUDP_GREN2)->FillSize.x * 0.75, height - 50]; + getHudPanel(HUDP_GREN2)->Scale = 0.75; + getHudPanel(HUDP_GRENTIMER)->Position = [(width / 2) + 16, (height / 2) + 32]; + getHudPanel(HUDP_GRENTIMER)->Scale = 0.75; + getHudPanel(HUDP_FLAGINFO)->Orientation = FO_HUD_INSERT_AFTER; + getHudPanel(HUDP_FLAGINFO)->Position = [width - 24, (height / 2) - 8]; + getHudPanel(HUDP_FRAGSTREAK)->Display = 0; + getHudPanel(HUDP_CAPS)->Display = 0; + getHudPanel(HUDP_SPECIAL)->Position = [0, 180]; + getHudPanel(HUDP_SHOWSCORES)->Position = [(width / 2) - (getHudPanel(HUDP_SHOWSCORES)->FillSize.x / 2), 30]; + getHudPanel(HUDP_SHOWSCORES)->Scale = 1.00; + getHudPanel(HUDP_TEAMSCORE)->Position = [(width - getHudPanel(HUDP_TEAMSCORE)->FillSize.x), 0]; + getHudPanel(HUDP_MAP_MENU)->Position = [(width / 2) - (getHudPanel(HUDP_MAP_MENU)->FillSize.x / 2), 30]; + getHudPanel(HUDP_MAP_MENU)->Scale = 1.00; + getHudPanel(HUDP_MAP_MENU)->Display = 0; + + getHudPanel(HUDP_SPEEDBAR)->Display = 0; + getHudPanel(HUDP_SPEEDBAR)->Scale = 1.00; + getHudPanel(HUDP_SPEEDBAR)->Snap = HUD_SNAP_CENTER; + getHudPanel(HUDP_SPEEDBAR)->Position = [0, 30]; + + getHudPanel(HUDP_RELOADPROGRESS)->Display = 1; + getHudPanel(HUDP_RELOADPROGRESS)->Snap = HUD_SNAP_CENTER; + getHudPanel(HUDP_RELOADPROGRESS)->Position = [0, yoffset - 30]; +} + +void FO_Hud_Editor_List_Panels() = { + print("^1Available HUD Elements:^7\n"); + for (float i = HUDP_FIRST; i <= HUDP_LAST; i++) + print(getHudPanel(i)->SaveName, ": ", getHudPanel(i)->Name, "\n"); +// for(float i = 0; i < Hud_ExtraPanels.length; i++) { +// print(Hud_ExtraPanels[i].id, ": ", Hud_ExtraPanels[i].Name, "\n"); +// } +} + +string FO_Hud_Editor_Get_Panel_Setting(string panel_name, string setting) = { +/* + float id = getHudPanel(panelid); + if(id < 0) { + print("^1HUD Element^7 '", panelid, "' does not exist!\n"); + return ""; + } +*/ + FO_Hud_Panel* panel = getHudPanelBySaveName(panel_name); + if(!panel || &panel == &NullPanel) { + print("^1HUD Element^7 '", panel_name, "' does not exist!\n"); + return ""; + } + + switch (setting) { + case "_id": + return ftos(panel.id); + case "saveName": + return panel.SaveName; + case "name": + return panel.Name; + case "position": + return vtos(panel.Position); + case "size": + return vtos(panel.FillSize); + case "scale": + return ftos(panel.Scale); + case "textscale": + return ftos(panel.TextScale); + case "show": + return ftos(panel.Display); + case "orientation": + return ftos(panel.Orientation); + case "snap": + return ftos(panel.Snap); + case "style": + return ftos(panel.Style); + default: + print("^1Setting^7 '", setting, "' does not exist!\n"); + break; + } + return ""; +} + +void FO_Hud_Editor_Show_Panel(string panel_name) = { + /* + float id = getHudPanel(panelid); + if(id < 0) { + print("^1HUD Element^7 '", panelid, "' does not exist!\n"); + return; + } + */ + FO_Hud_Panel* panel = getHudPanelBySaveName(panel_name); + if(!panel || &panel == &NullPanel) { + print("^1HUD Element^7 '", panel_name, "' does not exist!\n"); + return; + } + print("^1id^7: ", panel.id, "\n"); + print("\tname: ", panel.Name, "\n"); + print("\tposition: ", vtos(panel.Position), "\n"); + print("\tsize: ", vtos(panel.FillSize), "\n"); + print("\tscale: ", ftos(panel.Scale), "\n"); + print("\ttextscale: ", ftos(panel.TextScale), "\n"); + print("\tshow: ", ftos(panel.Display), "\n"); + print("\torientation: ", ftos(panel.Orientation), "\n"); + print("\tsnap: ", ftos(panel.Snap), "\n"); + print("\tstyle: ", ftos(panel.Style), "\n"); +} + +void FO_Hud_Editor_Print_Panel_Setting(string panel_name, string setting) = { + if(panel_name == "help" || panel_name == "?") { + print("^1orientation^7: Extends from base to the... 0 = Left, 1 = Right, 2 = Middle (where applicable)\n"); + print("^1snap^7: 0 = None, Horzontal: 1/2/4 = Left/Centre/Right, Vertical: 8/16/32 = Top/Middle/Bottom)\n"); + return; + } + if(setting == "") { + FO_Hud_Editor_Show_Panel(panel_name); + } else { + print(FO_Hud_Editor_Get_Panel_Setting(panel_name, setting), "\n"); + } +} + +void FO_Hud_Editor_Set_Panel_Setting(string panel_name, string setting, string value) = { +/* + float id = getHudPanel(panelid); + if(id < 0) { + print("^1HUD Element^7 '", panelid, "' does not exist!\n"); + return; + } +*/ + float val = 0; + + FO_Hud_Panel* panel = getHudPanelBySaveName(panel_name); + if(!panel || &panel == &NullPanel) { + print("^1HUD Element^7 '", panel_name, "' does not exist!\n"); + return; + } + switch (setting) { + case "id": + print("^5id ^1can't be changed^7!\n"); + break; + case "name": + print("^5name ^1can't be changed^7!\n"); + break; + case "position": + panel.Position = stov(value); + break; + case "size": + panel.FillSize = stov(value); + break; + case "scale": + panel.Scale = stof(value); + break; + case "textscale": + panel.TextScale = stof(value); + break; + case "show": + FO_Hud_SetDisplay(panel->id, stof(value)); + break; + case "orientation": + panel.Orientation = stof(value); + break; + case "snap": + val = stof(value); + if(val) { + if(val & 1 || val & 2 || val & 4) { + panel.Position.x = 0; + } + if(val & 8 || val & 16 || val & 32) { + panel.Position.y = 0; + } + } else { + panel.Position = getPanelPosition(panel); + } + panel.Snap = val; + break; + case "style": + panel.Style = stof(value); + break; + default: + print("^1Setting^7 '", setting, "' does not exist!\n"); + break; + } +} + +/* + * pos = x coord of the UI box + * width = x width of the UI box + * iconsize = x width of the icon (text will start before/after it); offset + * text = actual text to display + * textsize = fontsize + * align = alignment + */ +float GetTextAlignOffset(float pos, float width, float iconsize, string text, float textsize, float align) { + if(align == FO_HUD_INSERT_AFTER) { + return pos - strlen(text) * textsize + width - iconsize; + } else if(align == FO_HUD_INSERT_MIDDLE) { + return pos + (width / 2) - ((strlen(text) * textsize) / 2); + } + return pos + iconsize; +} +//MEHT this is a sui example thing +void(PanelID id, vector pos, vector size, string name, string command) bind_button = +{ + sui_push_frame(pos, size); + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + string key = "A"; //sui_binder(id, [0, 0], size, name, command); + if (sui_is_hovered(id)) sui_fill([0, 0], size, MENU_HIGHLIGHT, 0.1, 0); + sui_text([6, 0], MENU_TEXT_SMALL, name, MENU_TEXT_1, 1, 0); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([-6, 0], MENU_TEXT_SMALL, key, MENU_TEXT_1, 1, 0); + + sui_pop_frame(); +}; + +void (float dx, float dy) Hud_MapMenuPanel_Move = { + FO_Hud_Panel* panel = getHudPanel(HUDP_MAP_MENU); + if (Mouse.x > panel.Position.x && + Mouse.y > panel.Position.y && + Mouse.x < (panel.Position.x + panel.FillSize.x * panel.Scale) && + Mouse.y < (panel.Position.y + panel.FillSize.y * panel.Scale) + ) { + panel.Position.x += dx; + panel.Position.y += dy; + } +}; + +void() Hud_DrawMapMenuPanel = { + FO_Hud_Panel* panel = getHudPanel(HUDP_MAP_MENU); + local vector fillsize = panel.FillSize * panel.Scale; + local float alpha = fo_hud_editor?0.2:0.9; + local vector position = getPanelPosition(panel); + if(panel.Display) + setcursormode(TRUE); + if (hud_panel(panel->id, position, fillsize, alpha, panel.Display)) { + // click event + } + + if (fo_hud_editor) + return; + + local vector bgcolour = MENU_BG_DARK, textcolour = MENU_TEXT_1; + local float textscale = 1, padding = 4, cnt = 0; + local float titlesize = 12; + local float listitemhover = FALSE; + local vector listitemsize = [100, 10], listitempos = [0,0]; + if(panel.TextScale) { + textscale = panel.TextScale; + } else { + textscale = panel.Scale; + } + listitemsize.y = listitemsize.y * textscale; + titlesize = titlesize * textscale; + padding = padding * panel.Scale; + listitemsize.x = listitemsize.y * MAP_MAX_CHARS; //20 chars of mapname + + local vector listviewsize = [listitemsize.x, fillsize.y - padding * 3 - listitemsize.y]; + local float visiblelistitems = floor(listviewsize.y / listitemsize.y); + + //Window title + sui_push_frame(position + [padding,padding], [fillsize.x - padding * 2, titlesize]); + sui_set_align([SUI_ALIGN_CENTER, SUI_ALIGN_CENTER]); + sui_text([padding, 0], [titlesize,titlesize], "Map Selection", MENU_TEXT_4, 1, 0); + sui_pop_frame(); + + //Filter textbox + if(vote_list_filter != "") { + sui_push_frame(position + [padding,padding], listitemsize + [0,padding]); + //sui_border_box([0,0], listitemsize, 1, MENU_BG_DARK, 0.6, 0); + sui_fill([0,0], listitemsize + [0,padding], MENU_BG_DARK, 0.6, 0); + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + sui_text([0, 0], [listitemsize.y, listitemsize.y], vote_list_filter, MENU_TEXT_2, 1, 0); + sui_pop_frame(); + } + + vector listoffset; +/* + sui_list_view_begin(strcat("a", "scrl"), panel.Position + [200,4], [100, 150], [94, 10], num_mapvotes, listoffset, [0, 6]); + vector listitem_pos = '0 0 0'; + for (float index = sui_list_item(listitem_pos); index > -1; index = sui_list_item(listitem_pos)) + { + sui_push_frame(listitem_pos, [94, 10]); + //bind_button(strcat("b", ftos(index)), [0, 0], [94, 24], "index is ", ftos(index)); + hud_button(strcat("b", ftos(index)), listitem_pos, [94, 24], "index is ", ftos(index)); + //drawstring(listitem_pos, strcat("a", ftos(index)), [8,8], MENU_TEXT_2, 1, 0); + + sui_pop_frame(); + } + sui_list_view_end(); +*/ + //sui_scroll_view_begin("scrolltest", panel.Position + [200,4], [100,100], [100,200], listoffset, [2,6]); + //sui_scroll_view_end(); + sui_border_box(position + [padding, padding * 2 + titlesize], listviewsize, 1, MENU_TEXT_SPEC_FO, 0.6, 0); + if(vote_list_offset > num_mapvotes_filtered - visiblelistitems) { + vote_list_offset = num_mapvotes_filtered - visiblelistitems; + } + if(vote_list_offset < 0) { + vote_list_offset = 0; + } + entity mc = find(world, classname, "map_candidate_filtered"); + while(mc) { + if(cnt >= vote_list_offset) { + listitempos = position + [padding, padding * 2 + titlesize + listitemsize.y * (cnt - vote_list_offset)]; + if((listitempos.y + listitemsize.y + padding) > (position.y + fillsize.y)) { + //too many to fit + break; + } + listitemhover = (Mouse.x > listitempos.x && Mouse.y > listitempos.y && Mouse.x < (listitempos.x + listitemsize.x) && Mouse.y < (listitempos.y + listitemsize.y)); + if(listitemhover) { + bgcolour = MENU_BORDER; //MENU_UNSELECTED; + } else { + bgcolour = MENU_BG_DARK; + } + if(vote_selected_index < 0) { + vote_selected_index = 0; + } + if(vote_selected_index >= num_mapvotes_filtered) { + vote_selected_index = num_mapvotes_filtered - 1; + } + if(vote_selected_index == cnt) { + vote_selected_item = mc; + } + if(!vote_selected_item) { + vote_selected_item = mc; + vote_selected_index = cnt; + } + if(mc == vote_selected_item) { + alpha = 1; + bgcolour = MENU_BG_WARNING; + } else { + alpha = 0.4; + } + //sui_border_box(listitempos, listitemsize, 1, bgcolour, 0.4, 0); + //sui_fill(listitempos, listitemsize, bgcolour, alpha, 0); + //sui_text(listitempos, [listitemsize.y, listitemsize.y], mc.name, textcolour, alpha, 0); + sui_push_frame(listitempos, listitemsize); + if(current_vote && mc.owner == current_vote) { + textcolour = MENU_TEXT_GREEN; + } else if(mc.owner.votecount > 0) { + textcolour = MENU_TEXT_WARNING; + } else { + textcolour = MENU_TEXT_1; + } + if(hud_colour_button(HUDL_MAP_BASE + cnt,[0,0], listitemsize, mc.owner.name, bgcolour, [listitemsize.y, listitemsize.y], textcolour, SUI_ALIGN_START, 2, alpha, alpha, 1)) { + vote_selected_item = mc; + vote_selected_index = cnt; + } + if(mc.owner.votecount > 0) { + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([padding * -1, 0], [listitemsize.y, listitemsize.y], ftos(mc.owner.votecount), MENU_TEXT_4, alpha, 0); + } + sui_pop_frame(); + } + cnt++; + mc = find(mc, classname, "map_candidate_filtered"); + } + if(cnt < num_mapvotes_filtered) { + //draw scrollbar here + //sui_scrollbar("maplist_scrollbar", [listitemsize.x, fillsize.y - padding * 3 - listitemsize.y], [listitemsize.x,listitemsize.y * num_mapvotes], listoffset, [0,6]); + } + + sui_border_box(position, fillsize, 1, MENU_BORDER, 0.6, 0); + + //Map heading + vector mapheadingpos = [position.x + padding * 2 + listviewsize.x, position.y + padding * 2 + titlesize]; + float mapinfowidth = fillsize.x - (mapheadingpos.x - position.x); + sui_push_frame(mapheadingpos, [mapinfowidth, titlesize]); + sui_set_align([SUI_ALIGN_CENTER, SUI_ALIGN_CENTER]); + sui_text([padding, 0], [titlesize,titlesize], (vote_selected_item?vote_selected_item.owner.name:"No map selected"), MENU_TEXT_3, 1, 0); + sui_pop_frame(); + + if(!vote_selected_item) { + return; + } + + float current_vote_selected = FALSE; + if(current_vote && current_vote == vote_selected_item.owner) { + current_vote_selected = TRUE; + } + + ////Make it half the width of the map info area and aspect of 1.25:1 + //vector levelshotsize = [mapinfowidth / 2, mapinfowidth * 2 / 5]; + //No, make it 16:9 and 2/3 of the width + vector levelshotsize = [mapinfowidth * 2 / 3, mapinfowidth * 2 * 9 / (3 * 16)]; + + //Map image + float filehandle; + string levelshot = strcat("textures/levelshots/",vote_selected_item.owner.name,".jpg"); //need to fix extensions somehow + filehandle = fopen(levelshot, FILE_READ); + if (filehandle >= 0) { + fclose(filehandle); + sui_push_frame([mapheadingpos.x + mapinfowidth - levelshotsize.x - padding,mapheadingpos.y + titlesize + padding * 2], levelshotsize); + sui_pic([0,0], levelshotsize, levelshot, [1,1,1], 1, 0); + sui_border_box([0,0], levelshotsize, 1, MENU_TEXT_GREEN, 0.6, 0); + sui_pop_frame(); + } else { + //placeholder maybe? + } + + //Map details + float mapdetailgridindex = 0; + sui_push_frame(mapheadingpos + [0,titlesize + padding * 2], [mapinfowidth - levelshotsize.x, titlesize]); + if(vote_selected_item.owner.group) { + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + sui_text([padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], "Type:", MENU_TEXT_2, 1, 0); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([-2 * padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], vote_selected_item.owner.groupname, MENU_TEXT_2, 1, 0); + mapdetailgridindex++; + } + if(vote_selected_item.owner.team_num) { + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + sui_text([padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], "Teams:", MENU_TEXT_2, 1, 0); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([-2 * padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], ftos(vote_selected_item.owner.team_num), MENU_TEXT_2, 1, 0); + mapdetailgridindex++; + } + if(vote_selected_item.owner.min_val || vote_selected_item.owner.max_val) { + string playernumstring = ""; + if(vote_selected_item.owner.min_val && vote_selected_item.owner.max_val) { + if(vote_selected_item.owner.min_val == vote_selected_item.owner.max_val) { + playernumstring = strcat(ftos(vote_selected_item.owner.min_val)); + } else { + playernumstring = strcat(ftos(vote_selected_item.owner.min_val),"-",ftos(vote_selected_item.owner.max_val)); + } + } else if(vote_selected_item.owner.min_val) { + playernumstring = strcat(ftos(vote_selected_item.owner.min_val),"+"); + } else { + playernumstring = strcat(ftos(vote_selected_item.owner.max_val),"-"); + } + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + sui_text([padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], "Players:", MENU_TEXT_2, 1, 0); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([-2 * padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], playernumstring, MENU_TEXT_2, 1, 0); + mapdetailgridindex++; + } + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + sui_text([padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], "Client-side:", MENU_TEXT_2, 1, 0); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([-2 * padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], vote_selected_item.owner.localmap?"\x10\x8f\x11":"\x10 \x11", MENU_TEXT_2, 1, 0); + mapdetailgridindex++; + if(vote_selected_item.owner.votecount) { + if(current_vote_selected) { + textcolour = MENU_TEXT_GREEN; + } else { + textcolour = MENU_TEXT_WARNING; + } + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_CENTER]); + sui_text([padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], "Votes:", textcolour, 1, 0); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_CENTER]); + sui_text([-2 * padding, (titlesize + padding) * mapdetailgridindex], [titlesize,titlesize], strcat(ftos(vote_selected_item.owner.votecount),(current_vote_selected?" (yours)":"")), textcolour, 1, 0); + mapdetailgridindex++; + } + sui_pop_frame(); + + + //Map description + vector descriptionpos = [mapheadingpos.x,position.y + fillsize.y - padding * 4 - titlesize - listitemsize.y * 3]; + sui_push_frame(descriptionpos, [mapinfowidth - padding * 2, listitemsize.y * 3]); + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_START]); + sui_text([padding / 2, padding / 2], [listitemsize.y,listitemsize.y], vote_selected_item.owner.description, MENU_TEXT_1, 1, 0); + //sui_border_box([0,0], [fillsize.x - (mapheadingpos.x - panel.Position.x), listitemsize.y * 3], 1, MENU_BORDER, 0.6, 0); + sui_border_box([0,0], [mapinfowidth - padding, listitemsize.y * 3], 1, MENU_BORDER, 0.6, 0); + sui_pop_frame(); + + sui_push_frame(descriptionpos + [0, listitemsize.y * 3 + padding], [mapinfowidth - padding * 2, listitemsize.y * 3]); + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_START]); + if(current_vote_selected) { + if(hud_colour_button(HUDB_MAP_VOTE, [0,0], listitemsize + [0,padding], "Unvote", MENU_BUTTON, [listitemsize.y,listitemsize.y], MENU_TEXT_1, SUI_ALIGN_CENTER, 0, 1, 1, 0)) { + localcmd("cmd break\n"); + //current_vote = world; + } + } else { + if(hud_colour_button(HUDB_MAP_VOTE, [0,0], listitemsize + [0,padding], "Vote", MENU_BUTTON, [listitemsize.y,listitemsize.y], MENU_TEXT_1, SUI_ALIGN_CENTER, 0, 1, 1, 0)) { + localcmd("cmd votemap ", vote_selected_item.owner.name, "\n"); + } + } + if(hud_colour_button(HUDB_MAP_CHANGE, [0 - listitemsize.x - padding,0], listitemsize + [0,padding], "Change Now", MENU_BUTTON, [listitemsize.y,listitemsize.y], MENU_TEXT_WARNING, SUI_ALIGN_CENTER, 0, 1, 1, 0)) { + if(is_admin) { + localcmd("cmd map ", vote_selected_item.owner.name, "\n"); + } else { + localcmd("rcon map ", vote_selected_item.owner.name, "\n"); + } + } + + sui_pop_frame(); + +} + +void() Hud_DrawTeamScorePanel = { + float alpha = 0; + FO_Hud_Panel* panel = getHudPanel(HUDP_TEAMSCORE); + + //vector size = getFillSize(id); + float textScale = panel.Scale; //panel.TextScale; //panel.TextScale?panel.TextScale:panel.Scale; + //float sizex, sizey; + //sizex = size_x; + //sizey = size_y; + //vector mediumtext = MENU_TEXT_SMALL * textScale; + local float padding = 0.5; // * textScale; + + local vector fillsize = [12,12]; // * textScale; //mediumtext; + fillsize_x = fillsize_x * 3; + fillsize_y = fillsize_y + padding * 2; + panel.FillSize = fillsize; + fillsize = fillsize * textScale; + vector position = getPanelPosition(panel); + + if (hud_panel(panel->id, position, fillsize, alpha, getHudPanel(HUDP_TEAMSCORE)->Display)) + { + // click event + if (fo_hud_editor) + { + } + } + + local float offset; + local vector score_position = position; + local string message = ""; + if(panel.Orientation == FO_HUD_INSERT_AFTER) { + position_x = position_x - ((number_of_teams - 1) * fillsize_x); + } else if(panel.Orientation == FO_HUD_INSERT_MIDDLE) { + position_x = position_x - (((number_of_teams - 1) * fillsize_x) / 2); + } + float len; + float textOffset; + string val; + + for(float i = 0; i < number_of_teams; i++) { + score_position = position; + offset = (fillsize_x) * i; + score_position_x = score_position_x + offset; + //message = strcat(message, strpad(-3, ftos(TS.team1score))); + HRC_drawfill(score_position, fillsize, TEXT_TEAM_COLOUR[i], 0.5, 0); + val = ftos(TeamScore[i]); + len = strlen(val); + textOffset = (12 * textScale * (3 - len)); + Hud_DrawStringLMP(score_position + [textOffset,padding], val, 12 * textScale); + } +}; + +void Hud_DrawFlagStatusBar() +{ + vector pos; + float sizey, sizex; + sizey = FO_Hud_Icon_Size_y * getHudPanel(HUDP_FLAGINFO)->Scale; + sizex = FO_Hud_Icon_Size_x * getHudPanel(HUDP_FLAGINFO)->Scale; + + float flagInfoCount = 0; + for (float i = FlagInfoLines.length - 1; i >= 0; i--) { + if (FlagInfoLines[i].id) + flagInfoCount++; + } + //Show one for reference even on maps without + if(fo_hud_editor && !flagInfoCount) { + flagInfoCount = 1; + } + vector fillsize = [sizex * 4, sizey * flagInfoCount]; + FO_Hud_Panel* panel = getHudPanel(HUDP_FLAGINFO); + panel->FillSize = fillsize; + //pos = getPosition(HUDP_FLAGINFO); //getHudPanel(HUDP_FLAGINFO)->Position; + pos = getScaledPanelPosition(panel, 1); + float alpha = 0; + if (hud_panel(panel->id, pos, fillsize, alpha, getHudPanel(HUDP_FLAGINFO)->Display)) { + // click event + if (fo_hud_editor) + { + // TODO: Delete? Anything? + } + } + + for (float i = 0; i < flagInfoCount; i++) { + if (FlagInfoLines[i].id) { + alpha = FlagInfoLines[i].state == FLAGINFO_HOME ? CVARF(fo_hud_idle_alpha) : 1; + string icon = FlagInfoLines[i].icon.filename; + vector iconcolour = FlagInfoLines[i].icon.colour; + float bigfont = 8 * (getHudPanel(HUDP_FLAGINFO)->TextScale ? getHudPanel(HUDP_FLAGINFO)->TextScale : getHudPanel(HUDP_FLAGINFO)->Scale); + float bigfontvoffset = sizey / 2 - bigfont / 2; //Center text against the icon + if (FlagInfoLines[i].state == FLAGINFO_CARRIED) + { + HRC_drawstring([GetTextAlignOffset(pos_x,sizex,sizex,FlagInfoLines[i].carrier,bigfont,getHudPanel(HUDP_FLAGINFO)->Orientation), pos_y + bigfontvoffset + sizey * i, 0], FlagInfoLines[i].carrier, [bigfont,bigfont], '1 0 0', 1, 0); + } + else if (FlagInfoLines[i].state == FLAGINFO_DROPPED && FlagInfoLines[i].locname) + { + HRC_drawstring([GetTextAlignOffset(pos_x,sizex,sizex,FlagInfoLines[i].locname,bigfont,getHudPanel(HUDP_FLAGINFO)->Orientation), pos_y + bigfontvoffset + sizey * i, 0], FlagInfoLines[i].locname, [bigfont,bigfont], '1 1 1', 1, 0); + } + + HRC_drawpic([pos_x, pos_y + sizey * i, 0], icon, [sizex, sizey, 0], iconcolour, alpha, 0); + + if (FlagInfoLines[i].timeleft >= 0) + { + string stime = ftos(FlagInfoLines[i].timeleft); + float smallfont = 6 * getHudPanel(HUDP_FLAGINFO)->Scale; + HRC_drawstring([pos_x + sizex - stringwidth(stime, 1, [smallfont, smallfont]), pos_y + sizey * (i + 1) - smallfont, 0], stime, [smallfont, smallfont], '1 1 1', 1, 0); + } + } + } +} + +void Hud_ScrollPanelSelector(float num, float numlines) { // XXX + float newpos = getHudPanel(HUDP_OPTIONS)->Status + num; + if(newpos < 1) { + getHudPanel(HUDP_OPTIONS)->Status = 1; + } else if(newpos > (Hud_Panels.length - numlines)) { + getHudPanel(HUDP_OPTIONS)->Status = Hud_Panels.length - (numlines - 1); + } else { + getHudPanel(HUDP_OPTIONS)->Status = newpos; + } +} + +//Draw the list of all panels +void Hud_DrawHudOptionsPanelSelector() { + if(!fo_hud_editor) + return; + vector pos = getPosition(HUDP_OPTIONS); //getHudPanel(HUDP_OPTIONS)->Position; //Start with option panel's pos + vector size = getHudPanel(HUDP_OPTIONS)->FillSize * getHudPanel(HUDP_OPTIONS)->Scale; //for simplicity, use the same size as options panel + float textsize = 8 * (getHudPanel(HUDP_OPTIONS)->TextScale ? getHudPanel(HUDP_OPTIONS)->TextScale : getHudPanel(HUDP_OPTIONS)->Scale); + if((pos.x + (size.x * 2)) < ScreenSize.x) { + //If there's room on the screen, draw to the right of options panel + pos.x = pos.x + size.x; + } else { + pos.x = pos.x - size.x; + } + + if (hud_panel(HUDP_OPTION_LIST, pos, size, 0, 1)) + { + // click event + } + + float numlines = rint(size.y / (textsize + 2)); + // Need to draw scrollbar? + if((Hud_Panels.length * (textsize + 2)) > size.y) { + //Scrollbar buttons + if(hud_button(HUDB_OPTION_SCROLLUP, pos + [size.x - textsize - 2,2], [textsize, textsize], "^")) { + Hud_ScrollPanelSelector(-1, numlines); + } + if(hud_button(HUDB_OPTION_SCROLLDOWN, pos + [size.x - textsize - 2, size.y - textsize - 2], [textsize, textsize], "v")) { + Hud_ScrollPanelSelector(1, numlines); + } + } + + for(float i = 0,j = 0; i < min(numlines, Hud_Panels.length); i++) { + j = i + getHudPanel(HUDP_OPTIONS)->Status - 1; + if(hud_button(HUDL_OPTION_BASE + i, pos + [2, (textsize + 2) * i], [size.x - textsize - 4, textsize + 2], getHudPanel(j + HUDP_FIRST)->Name)) { + Editor_SelectedPanel_Index = j + HUDP_FIRST; + } + } +// for(float i = 0,j = 0; i < min(numlines, Hud_ExtraPanels.length); i++) { +// j = i + getHudPanel(HUDP_OPTIONS)->Status - 1; +// if(hud_button(strcat("hud_option_panel_list_item", ftos(i)), pos + [2, (textsize + 2) * i], [size.x - textsize - 4, textsize + 2], Hud_ExtraPanels[j].Name)) { +// Editor_SelectedPanel_Index = j; +// } +// } +} + +void Hud_DrawHudOptionsPanel(float display, string text) { + if(!fo_hud_editor) + return; + + FO_Hud_Panel* panel = getHudPanel(HUDP_OPTIONS); + vector pos = getPosition(HUDP_OPTIONS); //getHudPanel(HUDP_OPTIONS)->Position; + vector size = panel->FillSize * panel->Scale; + if (hud_panel(panel->id, pos, size, 0, panel->Display)) + panel->Status = 0; // click event0 + + if (Editor_SelectedPanel_Index < HUDP_FIRST || Editor_SelectedPanel_Index > HUDP_LAST) + Editor_SelectedPanel_Index = HUDP_FIRST; + + FO_Hud_Panel* selectedPanel = getHudPanel(Editor_SelectedPanel_Index); + + //drawstring(pos + [4,4], selectedPanel.Name, [8,8], MENU_SELECTED, 1, 0); + if(hud_button(HUDB_OPTION_LIST, pos + [4,2], [140, 10], strcat(selectedPanel.Name, " >"))) { + getHudPanel(HUDP_OPTIONS)->Status = !getHudPanel(HUDP_OPTIONS)->Status; + } + + if (selectedPanel != panel /* Don't allow editing the options panel itself */) { + float fscale = selectedPanel->Scale; + HRC_drawstring(pos + [4,12], strcat("Scale: ",ftos(rint(fscale * 100)), "%"), [8,8], MENU_SELECTED, 1, 0); + hud_slider(HUDE_OPTION_SCALE_SLIDER, pos + [8,24], [136,8], [0.2,5.0,24], fscale); + if(fscale != selectedPanel->Scale) + selectedPanel->Scale = fscale; + + float ftscale = selectedPanel->TextScale; + string scales = ftscale ? strcat(ftos(rint(ftscale * 100)), "%") : "Auto"; + HRC_drawstring(pos + [4,34], strcat("Text Scale: ",scales), [8,8], MENU_SELECTED, 1, 0); + hud_slider(HUDE_OPTION_TEXTSCALE_SLIDER, pos + [8,44], [136,8], [0,5.0,25], ftscale); + if(ftscale != selectedPanel->TextScale) + getHudPanel(Editor_SelectedPanel_Index)->TextScale = ftscale; + float ftextalign = selectedPanel->Orientation; + //drawstring(pos + [4,54], strcat("Text Pos: ", selectedPanel->Orientation ? "Left" : "Right"), [8,8], MENU_SELECTED, 1, 0); + HRC_drawstring(pos + [4,60], "Text Pos: ", [8,8], MENU_SELECTED, 1, 0); + if(hud_button(HUDE_OPTION_TEXTALIGN_TOGGLE, pos + [size.x - 6 - 56,56], [56, 16], HUD_ALIGN[selectedPanel->Orientation])) { + selectedPanel->Orientation = (selectedPanel->Orientation + 1) % 3; + } + //hud_slider("hud_option_textalign_scroll", pos + [8,64], [32,8], [0,1,1], ftextalign); + //if(ftextalign != selectedPanel->Orientation) { + // getHudPanel(Editor_SelectedPanel_Index)->Orientation = ftextalign; + //} + if(!selectedPanel->System && hud_button(HUDE_OPTION_SHOWHIDE_TOGGLE, pos + [4,74], [140, 16], selectedPanel->Display ? "Hide Panel" : "Show Panel")) { + FO_Hud_SetDisplay(selectedPanel->id, !selectedPanel->Display); + } + } + HRC_drawstring(pos + [4,96],"Position: ", [8,8], MENU_SELECTED, 1, 0); + HRC_drawstring(pos + [10,106], strcat("x: ",ftos(selectedPanel.Position.x)), [8,8], MENU_SELECTED, 1, 0); + HRC_drawstring(pos + [10,116], strcat("y: ",ftos(selectedPanel.Position.y)), [8,8], MENU_SELECTED, 1, 0); + + local float snap = 0; + local string ssnap; + if(selectedPanel->Snap & 2) { + snap = 1; + } else if(selectedPanel->Snap & 4) { + snap = 2; + } else { + snap = 0; + } + HRC_drawstring(pos + [4,130], "Hor. Snap: ", [8,8], MENU_SELECTED, 1, 0); + if(hud_button(HUDE_OPTION_HSNAP_TOGGLE, pos + [size.x - 6 - 56,126], [56, 16], HUD_HORIZONTAL_ALIGN[snap])) { + snap = (snap + 1) % 3; + getHudPanel(Editor_SelectedPanel_Index)->Position.x = 0; + getHudPanel(Editor_SelectedPanel_Index)->Snap -= (getHudPanel(Editor_SelectedPanel_Index)->Snap & 7); + getHudPanel(Editor_SelectedPanel_Index)->Snap += pow(2,snap); + } + if(getHudPanel(Editor_SelectedPanel_Index)->Snap & 16) { + snap = 1; + } else if(getHudPanel(Editor_SelectedPanel_Index)->Snap & 32) { + snap = 2; + } else { + snap = 0; + } + HRC_drawstring(pos + [4,148], "Ver. Snap: ", [8,8], MENU_SELECTED, 1, 0); + if(hud_button(HUDE_OPTION_VSNAP_TOGGLE, pos + [size.x - 6 - 56,144], [56, 16], HUD_VERTICAL_ALIGN[snap])) { + snap = (snap + 1) % 3; + getHudPanel(Editor_SelectedPanel_Index)->Position.y = 0; + getHudPanel(Editor_SelectedPanel_Index)->Snap -= (getHudPanel(Editor_SelectedPanel_Index)->Snap & 56); + getHudPanel(Editor_SelectedPanel_Index)->Snap += pow(2,(snap + 3)); + } + + if(hud_button(HUDE_OPTION_SHOWALL_TOGGLE, pos + [4,162], [140, 16], getHudPanel(HUDP_OPTIONS)->Style ? "Show All" : "Show HUD Only")) { + getHudPanel(HUDP_OPTIONS)->Style = !getHudPanel(HUDP_OPTIONS)->Style; + } + FO_Hud_ShowPanel(HUDP_OPTIONS); + + if(getHudPanel(HUDP_OPTIONS)->Status) { + Hud_DrawHudOptionsPanelSelector(); + } +} + +void Hud_DrawClassInfoPanel(float playerclass) +{ + const float id = HUDP_SPECIAL; + FO_Hud_Panel* panel = getHudPanel(id); + vector pos = getPanelPosition(panel); + + if(fo_hud_editor) { + hud_panel(id, pos, panel->FillSize * panel->Scale, 0, panel->Display); + HRC_drawpic(pos, "textures/wad/face1", FO_Hud_Icon_Size * panel->Scale, '1 1 1', 1, 0); + return; + } + + if (!fo_hud_editor && !panel->Display) + return; + + switch (WP_PlayerClass()) { + case PC_SOLDIER: + return; + } + + if (!WP_PlayerClass() || WP_PlayerClass() == PC_SOLDIER) + return; + + if (hud_panel(id, pos, panel->FillSize * panel->Scale, 0, panel->Display)) { + // click event + } + + float val; + vector size = FO_Hud_Icon_Size * panel->Scale; + vector fontSize = FO_Hud_Icon_Font_Size * (panel->TextScale ? panel->TextScale : panel->Scale); + pos = [pos_x + 2, pos_y + 2, 0]; + vector basepos = pos; + vector colour = '1 1 1'; + string icon = ""; + string msg = ""; + + //icon = HudIcons[playerclass-1].icon; + /* icon = HudIcons[playerclass+6].icon; */ + /* HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); */ + + float len = 0, offset = 0; + + switch (playerclass) + { + case PC_SCOUT: + icon = ICON_SCOUT; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = SBAR.ScannerOn ? "Scanning" : "Offline"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + if (SBAR.ScannerOn) + { + msg = SBAR.ScannerRange ? strcat("Dist: ", ftos(SBAR.ScannerRange)) : "No targets"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + if (SBAR.ScannerRange) + { + msg = (SBAR.ScannerTeamNo == team_no) ? "Friendly" : "Enemy"; + msg = strcat(msg, " ", ClassToString(SBAR.ScannerPlayerClass)); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + } + break; + case PC_SNIPER: + float sniper_dam = WP_SniperCharge(); + if (sniper_dam) + { + icon = ICON_SNIPER; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = strcat("Dam: ", ftos(sniper_dam)); + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + + if (sniper_dam == PC_SNIPER_MAXDAM) { + msg = "(100%)"; + colour = '1 0 0'; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + } + break; + case PC_DEMOMAN: + if (SBAR.IsDetpacking) + { + icon = ICON_DEMOMAN; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = "Setting"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + + msg = strcat(ftos(SBAR.DetpackLeft), " (", ftos(SBAR.IsDetpacking), ") secs left"); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + else if (SBAR.DetpackLeft) + { + icon = ICON_DEMOMAN; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = strcat(ftos(SBAR.DetpackLeft), " secs left"); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + break; + case PC_MEDIC: + icon = ICON_MEDIC; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = SBAR.AuraActive ? "On" : "Off"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + if (SBAR.AuraActive) + { + if (SBAR.HealCount) + { + msg = strcat(ftos(SBAR.HealCount), " players healed"); + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + + msg = strcat("for ", ftos(SBAR.HealAmount), " hp"); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + else + { + if (SBAR.AuraStatus == PC_MEDIC_AURA_OUTOFPOWER) + { + msg = "Out of power"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + else if (SBAR.AuraStatus == PC_MEDIC_AURA_RECHARGING) + { + msg = "Recharging"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + } + } + break; + case PC_HVYWEAP: + if (WP_LockedCannon()) + { + icon = ICON_HWGUY; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = "Assault Cannon Locked"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + break; + case PC_PYRO: + if (SBAR.AirBlast) + { + icon = ICON_PYRO; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = "Airblast Cooling Down"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + else if (!SBAR.AirBlast) + { + icon = ICON_PYRO; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = "Airblast Ready"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + break; + case PC_SPY: + if (SBAR.IsUndercover == 1) + { + icon = ICON_SPY; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + if (SBAR.InvisOnly) + { + msg = "Invisible"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + else + { + msg = "Undercover"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + msg = strcat(TeamToString(SBAR.UndercoverTeam), " ", ClassToString(SBAR.UndercoverSkin)); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + } + else if (SBAR.IsUndercover == 2) + { + icon = ICON_SPY; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + if (SBAR.InvisOnly) + { + msg = "Invisible"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + msg = strcat("In ", ftos(SBAR.UndercoverTimer), " secs"); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + else + { + msg = "Disguising"; + pos = HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + if (SBAR.DisguiseTeam) + { + msg = strcat("(", TeamToString(SBAR.DisguiseTeam), SBAR.QueueSkin ? "" : ") "); + } + else if (SBAR.QueueTeam) + { + msg = strcat("(", TeamToString(SBAR.QueueTeam), " "); + } + else if (SBAR.UndercoverTeam) + { + msg = strcat(TeamToString(SBAR.UndercoverTeam), " "); + } + string msg2 = ""; + if (SBAR.DisguiseSkin) + { + msg2 = strcat(SBAR.QueueTeam ? "" : "(", ClassToString(SBAR.DisguiseSkin), ")"); + } + else if (SBAR.QueueSkin) + { + msg2 = strcat(" ", ClassToString(SBAR.QueueSkin), ")"); + } + else if (SBAR.UndercoverSkin) + { + msg2 = strcat(ClassToString(SBAR.UndercoverSkin)); + } + msg = strcat(msg, msg2); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, TRUE, colour); + } + } + break; + case PC_ENGINEER: + if (NewBalanceActive()) { + basepos = pos; + msg = pstate_pred.server_time >= pstate_pred.special_next ? + "Impeller charged" : "Impeller recharging"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, + pos, basepos, FALSE, colour); + + pos = [pos_x, pos_y + size_y + 4]; + } + if (SBAR.HasSentry) + { + icon = ICON_ENGINEER_SG; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = strcat("L: ", ftos(SBAR.SentryLevel), " H: ", ftos(rint(SBAR.SentryHealth)), " S: ", ftos(rint(SBAR.SentryAmmoShells)), " R: ", ftos(rint(SBAR.SentryAmmoRockets))); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + else if (SBAR.IsBuilding) + { + icon = ICON_ENGINEER_SG; //HudIcons[playerclass+6].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = strcat(ftos(SBAR.BuildingPercentage), "%"); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + + // disp + pos = [pos_x, pos_y + size_y + 2]; + basepos = pos; + if (SBAR.HasDispenser) + { + icon = ICON_ENGINEER_DISP; //HudIcons[playerclass + 7].icon; + HRC_drawpic(pos, icon, size, '1 1 1', 1, 0); + + msg = strcat("H: ", ftos(rint(SBAR.DispenserHealth))); + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, pos, basepos, FALSE, colour); + } + break; + } +} + +void Hud_DrawIdentifyPanel(string identify) { + const float id = HUDP_IDENTIFY; + FO_Hud_Panel* panel = getHudPanel(id); + if (!panel->Display && !fo_hud_editor) + return; + + if(fo_hud_editor && !identify) + identify = "\n"; + + vector pos; + pos = getPanelPosition(panel); + + if (hud_panel(id, pos, panel->FillSize * panel->Scale, 0, panel->Display)) { + // click event + } + + if (time > last_id_time + 3) + return; + + vector fontSize = FO_Hud_Icon_Font_Size * panel->Scale; + + float count = tokenizebyseparator(identify, "\n"); + float msgcount = 0; + string msg = ""; + for (float f = 0; f <= count; f++) { + msg = argv(f); + // tokenize doesn't handle newlines very well + msg = strreplace("\n", "", msg); + msg = strtrim(msg); + if (strlen(msg) > 0) { + pos = pos + [0, (fontSize_y * msgcount), 0]; + HRC_drawstring(pos, msg, fontSize, '1 1 1', 1, 0); + msgcount++; + } + } +} + +void Hud_DrawPanel(FO_Hud_Panel* panel) { + float offset = 0; + if(sui_is_last_clicked(panel.id)) { + Editor_SelectedPanel_Index = panel->id; + } + if(!getHudPanel(HUDP_OPTIONS)->Style || panel.Display || (fo_hud_editor && panel->id == Editor_SelectedPanel_Index)) { + if (panel.Display || fo_hud_editor) + panel.drawPanel(panel.id, panel.getValue()); + //Draw panel names when editing + if(fo_hud_editor && panel->id != HUDP_OPTIONS) { + switch (panel.Orientation) { + case FO_HUD_INSERT_BEFORE: + offset = 2; + break; + case FO_HUD_INSERT_AFTER: + offset = panel.FillSize.x - (strlen(panel.Name) * 6) - 2; + break; + case FO_HUD_INSERT_MIDDLE: + offset = (panel.FillSize.x / 2) - (strlen(panel.Name) * 3); //because 3 = 6/2 + break; + } + HRC_drawstring(getScaledPanelPosition(panel,1) + [offset,2], panel.Name, '6 6', '0 1 0', 1, 0); + } + } +} + +void Hud_Draw(float width, float height) +{ + for(float i = HUDP_FIRST; i <= HUDP_LAST; i++) { + FO_Hud_Panel* panel = getHudPanel(i); + if (!panel->Display && !fo_hud_editor) + continue; // Fast-path skip. + + Hud_DrawPanel(panel); + } + HudSettings.MousePos = [Mouse.x, Mouse.y]; +} + +const static float HUDP_COUNT = HUDP_LAST - HUDP_FIRST + 1; + +struct { + float last_draw[HUDP_COUNT]; + float idx; +} incremental_hud; + +void Hud_UpdateView(float width, float height, float menushown, float perf_sample) { + ScreenSize = [width, height, menushown]; + + if (HRC_NewFrame()) { + float hts = perf_start_sample(&hud_timing, perf_sample); + sui_begin(width, height); + Menu_Draw(width, height, menushown); + Hud_Draw(width, height); + sui_end(); + perf_finish_sample(&hud_timing, hts); + return; + } + + if (!hud_render_cache.enabled) + return; + + // Handle appearance/removal of scoreboard. Invalidate if it's disappearing + // and render it immediately for responsiveness if it's been brought up. + static float last_showingscores; + if (last_showingscores != showingscores) { + if (last_showingscores) { + HRC_Invalidate(HUDP_SHOWSCORES); + } else { + incremental_hud.idx = HUDP_SHOWSCORES - HUDP_FIRST; + incremental_hud.last_draw[incremental_hud.idx] = 0; + } + last_showingscores = showingscores; + } + + // Skip any not-displayed elements. + float i = 0, idx = incremental_hud.idx; + while (!getHudPanel(HUDP_FIRST + idx)->Display && + i < HUDP_COUNT) { i++; idx = (idx + 1) % HUDP_COUNT; + } + incremental_hud.idx = idx; + + PanelID id = idx + HUDP_FIRST; + // Time ordered and oldest. + if (time >= incremental_hud.last_draw[idx] + HRC_fps_time()) { + float hts = perf_start_sample(&hud_partial_timing, perf_sample); + sui_begin(width, height); + HRC_Invalidate(id); + HRC_SetActive(id); + Hud_DrawPanel(getHudPanel(id)); + sui_end(); + perf_finish_sample(&hud_partial_timing, hts); + + incremental_hud.last_draw[idx] = time; + // We'll start searching from here for next incremental update. + incremental_hud.idx = (incremental_hud.idx + 1) % HUDP_COUNT; + } + + HRC_Render(); +} diff --git a/csqc/hud_helpers.qc b/csqc/hud_helpers.qc new file mode 100644 index 000000000..fab0a72ec --- /dev/null +++ b/csqc/hud_helpers.qc @@ -0,0 +1,492 @@ +void Hud_WriteCfg(string path); +void FO_Hud_InitSystemPanels(); + +void FO_Hud_Editor_Cancel() { + fo_hud_editor = FALSE; + setcursormode(FALSE); +} + +void FO_Hud_Editor() { + if (fo_hud_editor) { + FO_Hud_Editor_Cancel(); + + FO_Hud_InitSystemPanels(); + Hud_WriteCfg(FO_HUD_CONFIG_PATH); + } + else + { + fo_hud_editor = TRUE; + FO_Hud_InitSystemPanels(); + FO_Hud_ShowPanel(HUDP_OPTIONS); + setcursormode(TRUE); + } +} + +float(float fo_align) fo_to_sui_aligntment = { + switch(fo_align) { + case HUD_ALIGN_RIGHT: + return SUI_ALIGN_END; + case HUD_ALIGN_LEFT: + return SUI_ALIGN_START; + case HUD_ALIGN_CENTER: + return SUI_ALIGN_CENTER; + } + return HUD_ALIGN_CENTER; +}; + +void(PanelID id, vector pos, vector size, vector minmaxsteps, __inout float value) hud_slider = +{ + sui_push_frame(pos, size); + + value = sui_slidercontrol(id, [0, 0], size, minmaxsteps, value, sui_slider_noop); + float maxval = minmaxsteps[1]; + float sliderx = (value / maxval) * size_x; + sui_fill([0, size_y * 0.25], [size_x, size_y * 0.5], MENU_BG_DARK, 1.0, 0); + + float is_active = sui_is_held(id) || (sui_is_hovered(id) && !sui_click_held()); + vector slider_ctrl_color = is_active ? MENU_BUTTON + MENU_HIGHLIGHT * 0.1 : MENU_BUTTON; + sui_fill([sliderx - 2, 0], [4, size_y], slider_ctrl_color, 1.0, 0); + + sui_pop_frame(); +}; + + +vector UpdatePos(PanelID id, vector mousepos) +{ + if (id < HUDP_FIRST || id > HUDP_LAST) + return '0 0 0'; + + vector pos; + FO_Hud_Panel* panel = getHudPanel(id); + pos = panel->Position + mousepos - HudSettings.MousePos; + panel->Position = pos; + + return pos; +} + +float(PanelID id, vector pos, vector size, float alpha, float enabled) hud_panel = +{ + vector basecolor = MENU_BG; + vector outlinecolour = enabled ? MENU_BORDER : MENU_UNSELECTED; + + if (fo_hud_editor) + { + sui_border_box(pos, size, 1, outlinecolour, 0.4, 0); + if (sui_is_last_clicked(id)) + { + sui_border_box(pos, size, 1, MENU_SELECTED, 0.4, 0); + } + + if (sui_is_held(id)) + { + //pos = [Mouse.x, Mouse.y]; + //pos = UpdatePos(id, pos); + UpdatePos(id, [Mouse.x, Mouse.y]); + sui_border_box(pos, size, 1, MENU_SELECTED, 0.4, 0); + } + + if (sui_is_hovered(id)) + { + alpha = 0.6; + basecolor = MENU_BG + MENU_HIGHLIGHT * 0.1; + } + + if (sui_is_held(id)) + { + alpha = 0.6; + basecolor = MENU_BG - MENU_DARKEN * 0.1; + } + // Make options panel switch back to last selected element after dragging + if(sui_is_released(id) && id == HUDP_OPTIONS) { + _last_clicked_actions[0] = getHudPanel(Editor_SelectedPanel_Index)->id; + } + } + + sui_push_frame(pos, size); + + sui_fill([0, 0], size, basecolor, alpha, 0); + + sui_set_align([SUI_ALIGN_CENTER, SUI_ALIGN_CENTER]); + sui_action_element([0, 0], size, id, sui_noop); + sui_pop_frame(); + + return sui_is_clicked(id); +}; + +static string lmp_lookup(float c, float use_red) { + string lmp = ""; + switch (c) { + case ' ': return ""; + case '*': return "use_red"; // Caller must handle + case '-': lmp = "num_minus"; break; + case '/': lmp = "num_slash"; break; + case ':': lmp = "num_colon"; break; + default: { + if (c >= '0' && c <= '9') + lmp = sprintf("num_%g", c-'0'); + else + return "sb_quad"; // Unrecognized. + } + } + if (use_red) + lmp = strcat("a", lmp); + return lmp; +} + +// this draws backwards, haven't bothered to change as we don't use it +void Hud_DrawLargeValue(vector pos, float value, float threshhold, float size) +{ + float c; + float len, i; + string s; + if (!size) size = 24; + vector vsize = [size, size, 0]; + if (value < 0) + value = 0; //hrm + s = ftos(floor(value)); + len = strlen(s); + i = 0; + if (value <= threshhold) { //use alternate (red) numbers + while(iSaveName; + line = FormatCfgVector(line, strcat(sn, ".position"), panel->Position); + line = FormatCfgString(line, strcat(sn, ".scale"), ftos(panel->Scale)); + line = FormatCfgString(line, strcat(sn, ".textscale"), ftos(panel->TextScale)); + line = FormatCfgString(line, strcat(sn, ".display"), ftos(panel->Display)); + line = FormatCfgString(line, strcat(sn, ".nodeinsertloc"), ftos(panel->Orientation)); + line = FormatCfgString(line, strcat(sn, ".snap"), ftos(panel->Snap)); + line = FormatCfgString(line, strcat(sn, ".style"), ftos(panel->Style)); + + return line; +} + +void FO_Hud_HidePanel(PanelID id) { + getHudPanel(id)->Display = FALSE; + HRC_Invalidate(id); +} + +void FO_Hud_ShowPanel(PanelID id) { + getHudPanel(id)->Display = TRUE; +} + +void FO_Hud_SetDisplay(PanelID id, float display) { + if (display) + FO_Hud_ShowPanel(id); + else + FO_Hud_HidePanel(id); +} + +// Hide panels that are used by other systems, e.g. scores or map voting menu, +// that we don't want bein inadvertently rendered. +void FO_Hud_InitSystemPanels() { + for (float i = HUDP_FIRST; i <= HUDP_LAST; i++) { + FO_Hud_Panel* panel = getHudPanel(i); + if (panel->System) { + switch (panel->id) { + case HUDP_MOTD: + case HUDP_NOTIFICATION: + FO_Hud_ShowPanel(i); + break; + + case HUDP_MAP_MENU: // Enumerated for readability, not necessity + case HUDP_MENU: + default: + FO_Hud_HidePanel(i); + break; + } + } + } +} + +float firstrun; +void FO_Hud_Editor_LoadSettings(string filename) +{ + if(!filename || filename == "") return; + + vector vsize = (vector)getproperty(VF_SCREENVSIZE); + float width = vsize_x; + float height = vsize_y; + fo_hud_editor = FALSE; + FO_Hud_Panel* panel = &NullPanel; + + FO_Hud_Editor_LoadDefaultSettings(); + + HudSettings.MousePos = [0, 0]; + firstrun = TRUE; + + // fte does weird stuff and writes/reads this to/from a "gamedir/data/file" + float filehandle; + filehandle = fopen(filename, FILE_READ); + if (filehandle >= 0) { + // get number of lines + string ln; + ln = fgets(filehandle); + while (ln) { + if (strlen(ln) > 0) { + ln = strreplace("\n", "", ln); + string val, field; + + float x = 0, y = 0; + float count = tokenizebyseparator(ln, ":"); + field = argv(0); + field = strtrim(field); + val = argv(1); + val = strtrim(val); + + count = tokenizebyseparator(field, "."); + string pnl; + pnl = argv(0); + panel = getHudPanelBySaveName(pnl); + if(!panel || panel == &NullPanel) { + if(strlen(pnl) < 6) { + ln = fgets(filehandle); + continue; + } + pnl = substring(pnl, 0, strlen(pnl) - 5); + panel = getHudPanelBySaveName(pnl); + if(!panel || panel == &NullPanel) { + ln = fgets(filehandle); + continue; + } + } + + if (panel->id == HUDP_INVALID) + continue; + + switch (argv(1)) + { + case "position": + count = tokenizebyseparator(val, ","); + x = stof(argv(0)); + y = stof(argv(1)); + panel.Position = [x, y]; + break; + case "scale": + panel.Scale = stof(val); + break; + case "textscale": + panel.TextScale = stof(val); + break; + case "display": + FO_Hud_SetDisplay(panel->id, stof(val)); + break; + case "nodeinsertloc": + panel.Orientation = stof(val); + break; + case "snap": + panel.Snap = stof(val); + break; + case "style": + panel.Style = stof(val); + break; + } + } + ln = fgets(filehandle); + } + fclose(filehandle); + } else { + // write a new file + Hud_WriteCfg(filename); + } + if (getHudPanel(HUDP_MENU)->Position.x < 0) getHudPanel(HUDP_MENU)->Position.x = 10; + if (getHudPanel(HUDP_MENU)->Position.y < 0) getHudPanel(HUDP_MENU)->Position.y = 10; + if (getHudPanel(HUDP_MENU)->Position.x + getHudPanel(HUDP_MENU)->FillSize.x > vsize_x) getHudPanel(HUDP_MENU)->Position.x = vsize_x / 2 - getHudPanel(HUDP_MENU)->FillSize.x / 2; + if (getHudPanel(HUDP_MENU)->Position.y + getHudPanel(HUDP_MENU)->FillSize.y > vsize_y) getHudPanel(HUDP_MENU)->Position.y = 64; + + FO_Hud_InitSystemPanels(); +} + +static FO_Hud_Panel zero_panel; + +// Inherits active render cache id +void GetNewPanel(vector pos, vector fillSize, float scale, float display, float orientation, string name) +{ + FO_Hud_Panel pnl = zero_panel; + + pnl.Position = pos; + pnl.FillSize = fillSize; + pnl.Scale = scale; + pnl.Display = display; + pnl.Orientation = orientation; + pnl.Name = name; + + NewPanel = pnl; +} + +// draws value string using lmps +void Hud_DrawPanelLMP(FO_Hud_Panel* panel, string val, string icon, float icon_alpha) +{ + if (!panel.Display && !fo_hud_editor) + return; + + vector pos; + pos = getPanelPosition(panel); //getPosition(fid); + vector scaledFillSize = panel.FillSize * panel.Scale; + //if(scaledFill + + if (hud_panel(panel.id, pos, scaledFillSize, 0, panel.Display)) + { + // click event + if (fo_hud_editor) + { + + } + } + + pos = [pos_x + 2, pos_y + 2, 0]; + + HRC_drawpic(pos, icon, scaledFillSize, '1 1 1', icon_alpha, 0); + + float len = strlen(val); + + float textScale = panel.TextScale ? panel.TextScale : panel.Scale; + float offset = 0; + + if(!panel.Style) { + offset = (panel.Orientation == FO_HUD_INSERT_BEFORE) ? 2 + scaledFillSize.x : (2 + (scaledFillSize.z * len)) * -1; + + pos = [pos_x + offset, pos_y, 0]; + Hud_DrawStringLMP(pos, val, 24 * textScale); + } else { + float smallfont = 6 * textScale; + + if(panel.Orientation == FO_HUD_INSERT_BEFORE) { + offset = scaledFillSize.x - len * smallfont - 2; + } else if(panel.Orientation == FO_HUD_INSERT_MIDDLE) { + offset = (scaledFillSize.x) / 2 - ((len * smallfont)/2); + } + HRC_drawstring([pos_x + offset, pos_y + scaledFillSize.y - smallfont - 2, 0], val, [smallfont, smallfont], '1 1 1', 1, 0); + } +} + +void Hud_DrawLMPThreshold(FO_Hud_Panel* panel, string val, float threshold) +{ + if (!panel || (!panel.Display && !fo_hud_editor)) + return; + + vector pos; + pos = getPanelPosition(panel); + + float textScale = panel.TextScale ? panel.TextScale : panel.Scale; + vector fillScale = panel.FillSize * textScale; + if (hud_panel(panel.id, pos, fillScale, 0, panel.Display)) + { + // click event + if (fo_hud_editor) + { + + } + } + pos = [pos_x + 2, pos_y + 2, 0]; +/* + vector size = FO_Hud_Icon_Size * panel.Scale; + + + pos = [pos_x + offset, pos_y, 0]; + */ + + float len = strlen(val); + float textSize = FO_Hud_Icon_Size.x * textScale; + + float offset = 0; + if(panel.Orientation == FO_HUD_INSERT_BEFORE) { + offset = fillScale_x - len * textSize; + } else if(panel.Orientation == FO_HUD_INSERT_MIDDLE) { + offset = (fillScale_x) / 2 - ((len * textSize)/2); + } + + Hud_DrawStringLMPThreshold(pos + [offset,0], val, textSize, threshold); +} + +vector GetStringOffsetPos(float orientation, string msg, vector size, vector fontSize, + vector pos, vector basepos, float newline) +{ + float len, offset; + len = strlen(msg); + offset = (orientation == FO_HUD_INSERT_BEFORE) ? 2 + size_x : (2 + size_x + (fontSize_x * len)) * -1; + pos = [basepos_x + offset, newline ? pos_y + 2 + ((size_y - fontSize_y) * .5) : pos_y + ((size_y - fontSize_y) * .5), 0]; + + return pos; +} + +vector HRC_drawOffsetString(float orientation, string msg, vector size, + vector fontSize, vector pos, vector basepos, float newline, vector colour) +{ + pos = GetStringOffsetPos(orientation, msg, size, fontSize, pos, basepos, newline); + HRC_drawstring(pos, msg, fontSize, colour, 1, 0); + + return pos; +} diff --git a/csqc/input.qc b/csqc/input.qc new file mode 100644 index 000000000..19282cbeb --- /dev/null +++ b/csqc/input.qc @@ -0,0 +1,124 @@ +void Menu_Cancel(); +void FO_Menu_Game(float); + +// TRUE --> capture input +// FALSE --> pass input on +float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent = { + if (fo_hud_editor || fo_hud_menu_active) { + sui_input_event(evtype, scanx, chary, devid); + float menu_mouse = (fo_hud_menu_active && (CurrentMenu.flags & FO_MENU_FLAG_USE_MOUSE)); + + switch (evtype) { + case IE_KEYDOWN: + if (scanx == K_ESCAPE) { + Menu_Cancel(); + FO_Hud_Editor_Cancel(); + return TRUE; // Always capture escape + } else if (fo_hud_menu_active) + return fo_menu_process_input(CurrentMenu, scanx); + break; + case IE_MOUSEABS: + Mouse.x = scanx; + Mouse.y = chary; + break; + } + + return fo_hud_editor; // capture iff hud-editor + } else if(getHudPanel(HUDP_MAP_MENU)->Display) { + sui_input_event(evtype, scanx, chary, devid); + + switch (evtype) { + case IE_MOUSEDELTA: + return TRUE; + case IE_MOUSEABS: + PrevMouse.x = Mouse.x; + PrevMouse.y = Mouse.y; + Mouse.x = scanx; + Mouse.y = chary; + if (sui_is_held(HUDP_MAP_MENU)) + Hud_MapMenuPanel_Move(Mouse.x - PrevMouse.x, Mouse.y - PrevMouse.y); + return TRUE; + case IE_KEYDOWN: + switch (scanx) { + case K_ESCAPE: + return TRUE; + case K_MOUSE1: + return TRUE; + case K_UPARROW: + vote_selected_index--; + vote_list_offset--; + return TRUE; + case K_DOWNARROW: + vote_selected_index++; + vote_list_offset++; + return TRUE; + case K_PGUP: + vote_selected_index -= 10; + vote_list_offset -= 10; + return TRUE; + case K_PGDN: + vote_selected_index +=10; + vote_list_offset +=10; + return TRUE; + case K_MWHEELUP: + vote_list_offset--; + return TRUE; + case K_MWHEELDOWN: + vote_list_offset++; + return TRUE; + case K_ENTER: + if(vote_selected_item && current_vote == vote_selected_item.owner) { + localcmd("cmd break\n"); + } else { + localcmd("cmd votemap ", vote_selected_item.owner.name, "\n"); + } + return TRUE; + case K_BACKSPACE: + if(strlen(vote_list_filter) > 0) { + vote_list_filter = substring(vote_list_filter, 0, strlen(vote_list_filter) - 1); + ApplyMapFilter(); + } + return TRUE; + case K_DEL: //blank it out + if(strlen(vote_list_filter) > 0) { + vote_list_filter = ""; + ApplyMapFilter(); + } + return TRUE; + default: + //48 = '0' .. 57 = '9' + //97 = 'a' .. 122 = 'z' + //45 = '-' + if((scanx >= 48 && scanx <= 57) || (scanx >= 97 && scanx <= 122) || scanx == 45) { + if(strlen(vote_list_filter) < MAP_MAX_CHARS) { + vote_list_filter = strcat(vote_list_filter, chr2str(chary)); + ApplyMapFilter(); + } + return TRUE; + } + } + case IE_KEYUP: + switch (scanx) { + case K_ESCAPE: + showVoteMenu(FALSE); + return TRUE; + case K_MOUSE1: + return TRUE; + } + } + } else { + switch (evtype) + { + case IE_KEYDOWN: + switch (scanx) + { + case K_ESCAPE: + FO_Menu_Game(TRUE); + return TRUE; + } + break; + } + } + + return FALSE; +} diff --git a/csqc/main.qc b/csqc/main.qc new file mode 100644 index 000000000..e3312d8b5 --- /dev/null +++ b/csqc/main.qc @@ -0,0 +1,1032 @@ +void FO_Hud_Editor(); +void Hud_Draw(float width, float height); +void FO_Hud_Editor_LoadSettings(string); +void FO_Hud_Editor_LoadDefaultSettings(); +void FO_Hud_Editor_List_Panels(); +void FO_Hud_Editor_Print_Panel_Setting(string, string setting); +void FO_Hud_Editor_Set_Panel_Setting(string, string setting, string value); +void Hud_WriteCfg(string path); +void AddGrenTimer(float grentype, float offset); +void StopGrenTimers(); +float IsValidToUseGrenades(); +void Sync_GameState(); +float FoLogin(string token, float print_error); +void Perf_Status(); +void FO_Hud_Init(); +float InFluid(vector point); +float CalculateWaterLevel(); +void RenderHitTexts(); +entity sentry_preview; +entity sentry_preview_range_sphere; +float sentry_preview_offset; +float previewing_sentry; +float prevent_firing; +float sentry_fits; + +void GetSelf() = { + self = findfloat(world, entnum, player_localentnum); +} + +void PushToSlotHistory(float value) { + if (slot_history_top >= MAX_SLOT_HISTORY_SIZE) { + // Stack is full + return; + } + slot_history[slot_history_top] = value; + if (slot_history_top == 0) { + Slot slot = !IsSlotNull(pstate_pred.queue_slot) ? + pstate_pred.queue_slot : pstate_pred.current_slot; + slot_under_stack = SlotIndex(slot) + 1; + } + slot_history_top += 1; +} + +float RemoveFromSlotHistory(float slot) { + for (float i = slot_history_top - 1; i >= 0; i--) { + if (slot_history[i] == slot) { + for (float j = i+1; j < MAX_SLOT_HISTORY_SIZE; j++) { + slot_history[j-1] = slot_history[j]; + } + + slot_history[MAX_SLOT_HISTORY_SIZE-1] = 0; + slot_history_top--; + break; + } + } + + if (slot_history_top > 0) { + return slot_history[slot_history_top - 1]; + } else { + if (CVARF(fo_default_weapon)) + return CVARF(fo_default_weapon); + else + return slot_under_stack; + } +} + +static void BindAlias(TFAlias* tfa) { + if (tfa->impulse == 0 && tfa->cmd == "") // Some aliases are !csqc-only + return; + + if (tfa->impulse) + localcmd(sprintf("alias %s impulse %d\n", tfa->alias, tfa->impulse)); + else + localcmd(strcat("alias ", tfa->alias, " \"", tfa->cmd, "\"\n")); +} + +static void SetupAliases() { + float i; + + for (i = 0; i < client_aliases.length; i++) + BindAlias(&client_aliases[i]); + + for (i = 0; i < csqc_aliases.length; i++) + BindAlias(&csqc_aliases[i]); + + print("Aliases set\n"); +} + +DECLARE_PERF_SAMPLER(frame_timing, 60, 0.1); +DECLARE_PERF_SAMPLER(hud_timing, 60, 0.1); +DECLARE_PERF_SAMPLER(hud_partial_timing, 60, 0.1); + +void ClientSettings_Check(); + +noref void(float apiver, string enginename, float enginever) CSQC_Init = { + print("CSQC Started\n"); + + + // for (float i = 0; i < HudIcons.length; i++) { + // precache_pic(HudIcons[i].icon); + // } + + INIT_PERF_SAMPLER(frame_timing); + INIT_PERF_SAMPLER(hud_timing); + INIT_PERF_SAMPLER(hud_partial_timing); + + FO_Hud_Init(); + FO_Weapons_Init(); + Predict_InitDefaultConfig(); + FO_Predict_Init(); + CsGrenTimer::Init(); + + registercommand("specialup"); + registercommand("specialdown"); + + registercommand("zoomin"); + registercommand("zoomout"); + + registercommand("+slot"); + registercommand("-slot"); + + registercommand("fo_beta_pmove"); + registercommand("fopm_cmd"); + + registercommand("login"); + registercommand("fo_hud_editor"); + registercommand("fo_hud_reload"); + registercommand("fo_hud_reset"); + registercommand("fo_hud"); + registercommand("fo_hud_save"); + registercommand("fo_hud_load"); + + registercommand("fo_menu_game"); + registercommand("fo_main_menu"); + registercommand("fo_menu_team"); + registercommand("fo_menu_class"); + registercommand("fo_menu_admin"); + registercommand("fo_menu_vote"); + registercommand("fo_menu_special"); + registercommand("fo_menu_disguise"); + registercommand("fo_menu_build"); + registercommand("fo_menu_dropammo"); + registercommand("fo_menu_cancel"); + + registercommand("fo_min_ping"); + + registercommand("+aux_jump"); + registercommand("-aux_jump"); + registercommand("+rj"); + registercommand("-rj"); + + registercommand("slot_a"); + + registercommand("+fo_showscores"); + registercommand("-fo_showscores"); + registercommand("fo_settings_check"); + + registercommand("vote_addmap"); + registercommand("vote_removemap"); + + registercommand("wpp_status"); + registercommand("perf_status"); + for(float i = 0; i < MENU_OPTION.length - 1; i++) { + registercvar(strcat("fo_menu_option_",MENU_OPTION[i]), MENU_OPTION[i]); + } + registercvar("fo_menu_option_+", "="); + + FO_Hud_Editor_LoadSettings(FO_HUD_CONFIG_PATH); + + MenuPanel = getHudPanel(HUDP_MENU); + + is_admin = FALSE; + jump_counter = 0; + + num_mapvotes = 0; + vote_selected_item = world; + vote_selected_index = -1; + vote_list_offset = 0; + current_vote = world; + vote_list_filter = ""; + + PM_Init(); + TF_Init(); + + ClientSettings_Check(); + SetupAliases(); + + CurrentMenu = &FO_MENU_TEAM; + player_menu_type = 0; + FO_Menu_Game(TRUE); + + pengine.view_mask = MASK_VIEWMODEL; // Start with engine models. + + precache_model("progs/turrpreview.mdl"); + sentry_preview = spawn(); + setmodel(sentry_preview, "progs/turrpreview.mdl"); + setsize(sentry_preview, '-16 -16 0', '16 16 48'); + sentry_preview.alpha = 0.25; + + precache_model("progs/sphere.mdl"); + sentry_preview_range_sphere = spawn(); + setmodel(sentry_preview_range_sphere, "progs/sphere.mdl"); + sentry_preview_range_sphere.scale = 1000; + sentry_preview_range_sphere.alpha = 0.02; + + zoom_scale = 1; + + print("CSQC initialization finished\n"); +}; + +noref void() CSQC_WorldLoaded = { + localcmd("menu_restart\n"); + // Resolve race condition where models packed into map package sometimes do + // not resolve correctly. + localcmd("flush\n"); + localcmd("cl_smartjump 0\n"); // replaced by fo_smartjump +} + +void FO_CussView(); +void FO_CussCrosshair(float width, float height); +void Hud_UpdateView(float width, float height, float menushown, float perf_sample); +void UpdateTeamColorCrosshair(); +void PMD_DrawGraphs(float width); + +DEFCVAR_FLOAT(fov, 90); + +void SentryPreviewStart() { + if (CVARF(fo_forward_facing_sentry)) { + sentry_preview_offset = 0; + } else { + sentry_preview_offset = 180; + } + + sentry_preview.angles_y = input_angles_y; + sentry_preview.drawmask = MASK_ENGINE; + + local vector sphere_colormod = '1 1 1'; + switch (team_no) { + case 1: + sphere_colormod = '0 0.4 1'; + break; + case 2: + sphere_colormod = '1 0 0'; + break; + case 3: + sphere_colormod = '1 1 0'; + break; + case 4: + sphere_colormod = '0 1 0'; + break; + } + + sentry_preview_range_sphere.colormod = sphere_colormod * 4; + sentry_preview_range_sphere.drawmask = MASK_ENGINE; + + previewing_sentry = TRUE; + prevent_firing = TRUE; +} + +void SentryPreviewStop() { + previewing_sentry = FALSE; + sentry_preview.drawmask = 0; + sentry_preview_range_sphere.drawmask = 0; +} + +noref void(float width, float height, float menushown) CSQC_UpdateView = { + float fts = perf_start_sample(&frame_timing); + clearscene(); + setproperty(VF_DRAWWORLD, 1); // we want to draw our world! + + FO_CussView(); + + setviewprop(VF_AFOV, CVARF(fov)/zoom_scale); + setsensitivityscaler(1/zoom_scale); + + // Draw original sbar, viewsize honoured automatically. + if (!CVARF(fo_fte_hud) || CVARF(fo_legacy_sbar)) + setproperty(VF_DRAWENGINESBAR, 1); + + float mask = MASK_ENGINE; + if (!intermission) + mask |= WPP_ViewModelMask(); + addentities(mask); + + if (PM_Enabled()) + PM_UpdateView(); + + if (previewing_sentry) { + if (game_state.is_alive) { + } else { + } + } + + if (previewing_sentry) { + if (!game_state.is_alive) { + SentryPreviewStop(); + } else { + } + makevectors(view_angles); + local vector v_forward_sentry; + v_forward_sentry.z = (normalize(v_forward) * 64).z; + v_forward_z = 0; + local vector xy_pos = normalize(v_forward) * 64; + v_forward_sentry.x = xy_pos.x; + v_forward_sentry.y = xy_pos.y; + + sentry_preview.origin = PM_Org() + v_forward_sentry; + sentry_fits = PlaceSentry(sentry_preview, PM_Org()); + sentry_preview.colormod = sentry_fits ? '1 1 1' : '0.5 0.2 0.2'; + sentry_preview_range_sphere.origin = sentry_preview.origin; + sentry_preview.angles_y = anglemod(view_angles_y + sentry_preview_offset); + } else { + setproperty(VF_DRAWCROSSHAIR, 1); // we want to draw our crosshair! + } + + renderscene(); + + FO_CussCrosshair(width, height); + + TFxRenderGrenadeTimers(); + RenderHitTexts(); + Hud_UpdateView(width, height, menushown, fts); + PMD_DrawGraphs(width); + + perf_finish_sample(&frame_timing, fts); + + // Work around bug in some versions of FTE. See pmove.qc + recent_pmove_vel_z = pmove_vel_z; +} + +void Slot_Keydown(float slot) { + PushToSlotHistory(slot); + + localcmd(sprintf("impulse %d\n+attack\n", TF_SLOT1 + slot - 1)); +} + +void Slot_Keyup(float slot) { + float prev = RemoveFromSlotHistory(slot); + float still_attack = slot_history_top > 0; // Empty stack check + + if (prev) + localcmd(sprintf("impulse %d\n", TF_SLOT1 + prev - 1)); + if (!still_attack) + localcmd("-attack\n"); +} + +void ZoomIn() { + zoom_scale = min(zoom_scale + 1, 5); +} + +void ZoomOut() { + zoom_scale = max(zoom_scale - 1, 1); +} + +void W_ChangeToSlotAlternate(string opt1, string opt2, string opt3, string opt4); + +noref float(string cmd) CSQC_ConsoleCommand = { + tokenize_console(cmd); + float val; + string key1, key2; + local float grentype; + + switch(argv(0)) { + case "specialup": + switch (WP_PlayerClass()) { + case PC_SNIPER: + ZoomIn(); + break; + case PC_SPY: + localcmd("cmd disguise prev\n"); + break; + case PC_ENGINEER: + if (previewing_sentry) { + sentry_preview_offset = anglemod(sentry_preview_offset - 15); + } else if (vlen(PM_Org() - sentry_pos) < ENG_BUILDING_DISMANTLE_DISTANCE) { + update_sentry_angles = time + 0.55; + sentry_angles_y = anglemod(sentry_angles_y - 15); + localcmd(sprintf("cmd sentry angle %f\n", sentry_angles_y)); + } + break; + } + break; + case "specialdown": + switch (WP_PlayerClass()) { + case PC_SNIPER: + ZoomOut(); + break; + case PC_SPY: + localcmd("cmd disguise next\n"); + break; + case PC_ENGINEER: + if (previewing_sentry) { + sentry_preview_offset = anglemod(sentry_preview_offset + 15); + } else if (vlen(PM_Org() - sentry_pos) < ENG_BUILDING_DISMANTLE_DISTANCE) { + update_sentry_angles = time + 0.55; + sentry_angles_y = anglemod(sentry_angles_y + 15); + localcmd(sprintf("cmd sentry angle %f\n", sentry_angles_y)); + } + break; + } + break; + case "zoomin": + ZoomIn(); + break; + case "zoomout": + ZoomOut(); + break; + case "+slot": + Slot_Keydown(stof(argv(1))); + break; + case "-slot": + Slot_Keyup(stof(argv(1))); + break; + + case "fo_beta_pmove": + printf("Please set `fo_pmove 1` instead.\n"); + break; + + case "login": + FoLogin(argv(1), TRUE); + break; + case "fo_hud_editor": + FO_Hud_Editor(); + break; + case "fo_hud": + if(argv(1) == "") { + FO_Hud_Editor_List_Panels(); + } else if(argv(3) == "") { + FO_Hud_Editor_Print_Panel_Setting(argv(1), argv(2)); + } else { + FO_Hud_Editor_Set_Panel_Setting(argv(1), argv(2), argv(3)); + } + break; + case "fo_hud_save": + if(argv(1) != "") { + Hud_WriteCfg(argv(1)); + } else { + Hud_WriteCfg(FO_HUD_CONFIG_PATH); + } + break; + case "fo_hud_reload": + FO_Hud_Editor_LoadSettings(FO_HUD_CONFIG_PATH); + break; + case "fo_hud_reset": + FO_Hud_Editor_LoadDefaultSettings(); + break; + case "fo_hud_load": + if(argv(1) != "") { + FO_Hud_Editor_LoadSettings(argv(1)); + } + break; + case "fo_menu_game": + FO_Menu_Game(TRUE); + break; + case "fo_menu_team": + FO_Menu_Team(TRUE); + break; + case "fo_menu_class": + FO_Menu_Class(TRUE); + break; + case "fo_main_menu": + if(checkcommand("m_main")) { + //Use menuqc main menu + localcmd("m_main\n"); + } else { + //If menu.dat is missing, fall back to engine menus + localcmd("menu_main\n"); + } + break; + case "fo_menu_admin": + FO_Menu_Admin_Main(TRUE); + break; + case "fo_menu_vote": + //FO_Menu_Vote(TRUE); + showVoteMenu(!getHudPanel(HUDP_MAP_MENU)->Display); + break; + case "fo_menu_special": + FO_Menu_Special(TRUE); + break; + case "fo_menu_disguise": + if (WP_PlayerClass() == PC_SPY) + FO_Menu_Spy_Skin(TRUE); + break; + case "fo_menu_build": + if (WP_PlayerClass() == PC_ENGINEER) + FO_Menu_Build(TRUE); + break; + case "fo_menu_dropammo": + FO_Menu_DropAmmo(TRUE); + break; + case "fo_menu_cancel": + Menu_Cancel(); + break; + case "fo_min_ping": + UpdateFoMinPing(argv(1)); + break; + case "fo_settings_check": + ClientSettings_Check(); + break; + case "+fo_showscores": + if (CVARF(fo_oldscoreboard) == 1) + { + tokenize(findkeysforcommand(argv(0))); + + key1 = argv(0); + key2 = argv(1); + if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); + if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); + + if (key1 != "") + { + localcmd(sprintf("unbind %s\n", key1)); + localcmd(sprintf("bind %s +showscores\n", key1)); + } + + if (key2 != "") + { + localcmd(sprintf("unbind %s\n", key2)); + localcmd(sprintf("bind %s +showscores\n", key2)); + } + } + FO_Show_Scores(TRUE); + break; + case "-fo_showscores": + FO_Show_Scores(FALSE); + break; + case "+showscores": + case "+showteamscores": + showingscores = TRUE; + if (CVARF(fo_oldscoreboard) != 1) + { + tokenize(findkeysforcommand(argv(0))); + + key1 = argv(0); + key2 = argv(1); + if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); + if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); + + if (key1 != "") + { + localcmd(sprintf("unbind %s\n", key1)); + localcmd(sprintf("bind %s +fo_showscores\n", key1)); + } + + if (key2 != "") + { + localcmd(sprintf("unbind %s\n", key2)); + localcmd(sprintf("bind %s +fo_showscores\n", key2)); + } + } + if(CVARF(fo_fte_hud)) { + FO_Show_Scores(TRUE); + } + break; + case "-showscores": + case "-showteamscores": + showingscores = FALSE; + if(CVARF(fo_fte_hud)) { + FO_Show_Scores(FALSE); + } + break; + case "+aux_jump": + jump_counter++; + localcmd("+jump\n"); + break; + case "-aux_jump": + jump_counter--; + + if (jump_counter < 0) { + jump_counter = 0; + } + + if (!jump_counter) { + localcmd("-jump\n"); + } + + break; + case "+rj": + if (WP_PlayerClass() == PC_SOLDIER || WP_PlayerClass() == PC_PYRO) + localcmd("+button4\n"); + break; + case "-rj": + if (WP_PlayerClass() == PC_SOLDIER || WP_PlayerClass() == PC_PYRO) + localcmd("-button4\n"); + break; + case "wpp_status": + WPP_Status(); + break; + case "perf_status": + Perf_Status(); + break; + case "vote_addmap": + AddVoteMap(argv(1),argv(2),argv(3),stof(argv(4)),stof(argv(5)),stof(argv(6)),TRUE); + break; + case "vote_removemap": + RemoveVoteMap(argv(1), TRUE); + break; + case "slot_a": // Alternate between passed options + W_ChangeToSlotAlternate(argv(1), argv(2), argv(3), argv(4)); + break; + } + + return FALSE; +}; + +void(float isnew) CSQC_Ent_Update = { + float etype = readbyte(); + switch (etype) { + case ENT_CONFIG: + EntUpdate_Config(); + break; + case ENT_WEAPONPRED: + EntUpdate_WeaponPred(isnew); + break; + case ENT_PROJECTILE: + EntUpdate_Projectile(isnew); + break; + default: + error("Unhandled CSQC entity\n"); + return; + } +}; + +void() CSQC_Ent_Remove = { //the entity in question left the player's pvs, and will no longer be tracked... + if (self.removefunc) + self.removefunc(); + remove(self); +}; + +static float IsFoConced() { + if (IsClownMode(CLOWN_CONC)) + return TRUE; + + if (!fo_config.fo_concuss) + return FALSE; + + return pstate_pred.tfstate & TFSTATE_CONC; +} + +struct ConcCurve { + float duration; + float cycles; + float amp_end; + float offset; +}; + +ConcCurve conc_curve[] = { + {1, 0.5, 0.0, 0.5}, + {6, 4, 0.2, 0.5}, + {2, 1.5, 0.75}, + {1, 1, 1}, + {0, 0, 1}, // Terminator +}; + +ConcCurve clown_curve[] = { + {10, 4, 0.75}, + {0, 0, 0.75}, // Terminator +}; + +float ClownConcPeriod() { return clown_curve[0].duration; } + +static float Blend(float start, float end, float rem, float D) { + return rem/D * start + (D-rem)/D * end; +} + +static vector FO_Conc_Offset() { + static float last_rem; + float rem = cuss_state.end_time - time; + if (rem < 0) + return '0 0 0'; + + ConcCurve* table; + float len; + ConcCurve* cur = __NULL__; + float i, rot = 0; + + if (!IsClownMode(CLOWN_CONC)) { + table = conc_curve; + len = conc_curve.length; + } else { + table = clown_curve; + len = clown_curve.length; + } + + for (i = 0; i < len - 1; i++) { + cur = &table[i]; + if (rem <= cur->duration) + break; + rem -= cur->duration; + } + + float amp = Blend(table[i+1].amp_end, cur->amp_end, rem, cur->duration); + float a = (cur->duration - rem) / cur->duration * cur->cycles * 2 * M_PI; + a += cur->offset * 2 * M_PI; + + return [sin(a), sin(a) * cos(a), amp * pstate_pred.conc_amp]; +} + +static void FO_UpdateConcAim() { + makevectors(input_angles); + vector o = FO_Conc_Offset(); + + cuss_state.c_forward = normalize(v_forward * 200 + o.x * o.z * v_right + o.y * o.z * v_up); + + vector vv = o * min(o.z / 4, 20); + cuss_state.c_view = vectoangles(v_forward * 200 + vv.x * v_right + vv.y * v_up); + cuss_state.c_view[0] *= -1; +} + +DEFCVAR_FLOAT(fo_crossy, 0); // For people who want to use crossy +DEFCVAR_FLOAT(cl_crossx, 0); +DEFCVAR_FLOAT(cl_crossy, 0); + +static void FO_CussView() { + if (cuss_state.cussed) { + FO_UpdateConcAim(); + setproperty(VF_ANGLES, cuss_state.c_view); + } +}; + +static void FO_CussCrosshair(float width, float height) { + const float crosshair_hz = 200; + static float next_update; + if (time < next_update) + return; + next_update = time + 1.0/crosshair_hz; + + if (!IsFoConced()) { + if (cuss_state.cussed || CVARF(cl_crossx) != 0 || + CVARF(cl_crossy) != CVARF(fo_crossy)) { + // Make sure we restore crosshairs + localcmd(sprintf("cl_crossx %d; cl_crossy %d;\n", 0, CVARF(fo_crossy))); + CVARF(cl_crossx) = 0; + CVARF(cl_crossy) = CVARF(fo_crossy); + cuss_state.cussed = FALSE; + } + return; + } + + cuss_state.cussed = TRUE; + makevectors(input_angles); // updated by input_frame + vector p = project(PM_Org() + 8000 * cuss_state.c_forward); + + localcmd(sprintf("cl_crossx %d; cl_crossy %d;\n", + p_x - width / 2, CVARF(fo_crossy) + p_y - height / 2)); +} + +static void UpdateTeamColorCrosshair() { + if (!CVARF(fo_team_color_crosshair)) + return; + + if (crosshair_team_no == team_no) + return; + + string crosshair_color = "0xffffff"; + switch (team_no) { + case 1: + crosshair_color = "0x0066ff"; + break; + case 2: + crosshair_color = "0xff0000"; + break; + case 3: + crosshair_color = "0xffff00"; + break; + case 4: + crosshair_color = "0x00ff00"; + break; + } + localcmd("crosshaircolor ", crosshair_color, "\n"); + crosshair_team_no = team_no; +} + +void FO_ApplyCussInput() { + if (!IsFoConced()) + return; + + float modify_forward = TRUE; + + if ((!pmove_onground && (fo_config.fo_concuss & FOC_EASY_AIR)) || + (pmove_onground && (fo_config.fo_concuss & FOC_EASY_GROUND))) + modify_forward = input_buttons & BUTTON0; + + if (modify_forward) { + input_angles = vectoangles(cuss_state.c_forward); + makevectors(input_angles); + input_angles_x *= -1; + } +} + +void PM_InputFrame(); + +noref void CSQC_Input_Frame() { + local float changed_buttons = input_buttons ^ oldbuttons; + oldbuttons = input_buttons; + + local float keydowns = changed_buttons & input_buttons; + local float keyups = changed_buttons & ~input_buttons; + + Sync_GameState(); + + switch (WP_PlayerClass()) { + case PC_SNIPER: + if (keydowns & BUTTON3) { + if (zoom_scale == 1) { + zoom_scale = 3; + } else { + zoom_scale = 1; + } + } + break; + case PC_SOLDIER: + case PC_PYRO: + // Intercept rocket jump + if (input_buttons & BUTTON4) { + input_buttons |= BUTTON0 | BUTTON2; + } + break; + case PC_ENGINEER: + // Intercept sentry build + if (!getstatf(STAT_HAS_SENTRY) && getstatf(STAT_CELLS) >= 130 && game_state.is_alive && !prematch) { + if (input_buttons & BUTTON4) { + if (keydowns & BUTTON4) { + if (!previewing_sentry) { + SentryPreviewStart(); + } else { + SentryPreviewStop(); + } + } + + input_buttons = input_buttons - BUTTON4; + } + + if (previewing_sentry) { + if (keydowns & BUTTON0) { + if (sentry_fits) { + localcmd(sprintf("cmd build sentry %f\n", anglemod(180 + sentry_preview_offset))); + SentryPreviewStop(); + } else { + print("Can't build here\n"); + } + } + + if (input_buttons & BUTTON0) { + input_buttons = input_buttons - BUTTON0; + } + } + } + break; + } + + if (prevent_firing && !previewing_sentry && (keyups & BUTTON0)) { + prevent_firing = FALSE; + } + + if (prevent_firing) { + input_buttons &= ~BUTTON0; + } + + PM_InputFrame(); + FO_ApplyCussInput(); +} + +float(float save, float take, vector inflictororg) CSQC_Parse_Damage = { + if (take > 0) + painfinished = time + 0.2; + + return 0; +} + +void CSQC_Shutdown() { +} + +// We can query, but not set via an autocvar. +DEFCVAR_FLOAT(cl_delay_packets, 0); +DEFCVAR_FLOAT(fov, 90); + +void WP_UpdatePings(); + +float last_servercommandframe; +void _Sync_ServerCommandFrame() { + // Server command frames are monotonically unique, we can skip processing + // unless there is new state. + if (last_servercommandframe == servercommandframe) + return; + last_servercommandframe = servercommandframe; + + prev_game_state = game_state; + + team_no = getstatf(STAT_TEAMNO); + all_time = getstatf(STAT_ALL_TIME); + SBAR.ReadyStatus = getstatf(STAT_FLAGS); + + game_state.localentnum = player_localentnum; + + if (team_no == 0 ||stof(getplayerkeyvalue(player_localnum, "*spectator"))) { + game_state.is_spectator = 1; + game_state.is_player = 0; + } else { + game_state.is_spectator = 0; + game_state.is_player = 1; + } + + // Note: When spectating someone, refers to them. + game_state.is_alive = getstatf(STAT_HEALTH) > 0; + game_state.spawn_gen = getstatf(STAT_SPAWN_GEN); + + game_state.is_ceasefire = serverkeyfloat("pausestate"); + WP_ServerFrame(); + + // Use an undocumented ezquake compat feature to figure out whether we + // have focus or not, skip updates in this case due to lowered network fps. + float is_unfocused = getplayerkeyfloat(player_localnum, "chat") & 2; + if (!is_unfocused) + WP_UpdatePings(); + + CsGrenTimer::UpdateSoundStack(); + UpdateTeamColorCrosshair(); +} + + +// Called for each {client, server} command frame, ensures globals are +// synchronized with server and predicted state. +void Sync_GameState() { + _Sync_ServerCommandFrame(); + PM_PredictJump_Engine(); +} + +static string FoLogin_GetToken() { + float filehandle = fopen(FO_TOKEN_PATH, FILE_READ); + if (filehandle == -1) + return ""; + + string token = fgets(filehandle); + fclose(filehandle); + return token; +} + +// Doesn't actually return success, just whether it sent a token. +float FoLogin(string token, float print_error) { + if (token != "") { + float filehandle = fopen(FO_TOKEN_PATH, FILE_WRITE); + fputs(filehandle, token); + fclose(filehandle); + } else { + token = FoLogin_GetToken(); + } + + if (token == "") { + if (print_error) + print("Token required: Please sign-up at fortressone.org and follow the instructions to generate your login token.\n"); + return FALSE; + } + + localcmd("setinfo _fo_token \"\"\n"); + localcmd(sprintf("cmd fo-login-silent %s\n", token)); + return TRUE; +} + +static string to_precision(float f, float p) { + string fmt = strcat("%0.", ftos(p), "f"); + return sprintf(fmt, f); +} + +static float get_precision() { + float t = gettime(1); + + // For (debug) clients which have modified gettime to report with more + // precision. + return (t*1000 == ceil(t*1000)) ? 3 : 5; +} + +static float gettime_lat() { + const float trials = 1000; + float s = gettime(1), f, i; + for (i = 0; i < trials; i++) + gettime(1); + return (gettime(1) - s)/trials; +} + +// Note while this works with ms +void Perf_Status() { + printf("Performance Stats:\n"); + float avg, variance, minv, maxv; + + float prec = get_precision(); + if (prec > 3) + printf("gettime() timing = %0.5f\n", gettime_lat()); + + + printf("HUD cache used=%d freelist=%d\n", + (float)hud_render_cache.draw_count, + (float)hud_render_cache.free_count); + + compute_avg(&frame_timing.samples, &avg, &variance); + compute_maxmin(&frame_timing.samples, &minv, &maxv); + printf(" Frame render (%d) avg=%s var=%0.3f min=%s max=%s\n", + (float)min(frame_timing.samples.count, frame_timing.samples.max_count), + to_precision(avg, prec), variance, + to_precision(minv, prec), to_precision(maxv, prec)); + + compute_avg(&hud_timing.samples, &avg, &variance); + compute_maxmin(&hud_timing.samples, &minv, &maxv); + printf(" HUD render (%d) avg=%s var=%0.3f min=%s max=%s\n", + (float)min(hud_timing.samples.count, hud_timing.samples.max_count), + to_precision(avg, prec), variance, + to_precision(minv, prec), to_precision(maxv, prec)); + + compute_avg(&hud_partial_timing.samples, &avg, &variance); + compute_maxmin(&hud_partial_timing.samples, &minv, &maxv); + printf(" HUD partial (%d) avg=%s var=%0.3f min=%s max=%s\n", + (float)min(hud_partial_timing.samples.count, hud_partial_timing.samples.max_count), + to_precision(avg, prec), variance, + to_precision(minv, prec), to_precision(maxv, prec)); +} + +void ClientSettings_Check() { + localcmd("cl_movespeedkey 1\n"); + if (cvar("worker_count") == 0) + printf("ERROR: Please set `worker_count 4` to reduce stuttering!\n"); + if (cvar("r_temporalscenecache") == 0) + printf("RECOMMENDED: Please set `r_temporalscenecache 1` to reduce stuttering!\n"); + if (cvar("fo_grentimer") != 2) + printf("RECOMMEND: Setting `fo_grentimer 2` for ping correction.\n"); + if (cvar("fo_hud_cache") != 1) + printf("ERROR: Please set `fo_hud_cache 1` to improve fps.\n"); + if (cvar("fo_predict_weapons") != 1) + printf("WARNING: client-side weapon prediction disabled [fo_predict_weapons 0]\n"); + if (cvar("fo_predict_projectiles") != 1) + printf("WARNING: client-side projectile prediction disabled [fo_predict_projectiles 0]\n"); + printf("Finished checking settings...\n"); +} diff --git a/csqc/menu.qc b/csqc/menu.qc new file mode 100644 index 000000000..d901afae1 --- /dev/null +++ b/csqc/menu.qc @@ -0,0 +1,1436 @@ +void (float force) FO_Menu_Build; +void (float force) FO_Menu_Admin_Main; +void (float force) FO_Menu_Admin_Modes; +void (float force) FO_Menu_Admin_Settings; +void (float force) FO_Menu_Admin_Rounds; +void (float force) FO_Menu_Admin_Timelimit; +void (float force) FO_Menu_Admin_Fraglimit; +void (float force) FO_Menu_Admin_QuadTimelimit; +void (float force) FO_Menu_Admin_FoMatchRated; +void (float force) FO_Menu_Admin_PlayToCompletion; +void (float force) FO_Menu_Admin_NewBalance; +void (float force) FO_Menu_Spy; +void (float force) FO_Menu_Spy_Skin; +void (float force) FO_Menu_Spy_Team; +void (float force) FO_Menu_Class; +void (float force) showVoteMenu; +void FO_Menu_Admin_Players(float force, float type, float page); +void FO_Hud_SetDisplay(PanelID id, float display); + +typedef struct { + string shortcut; //key to press. if omitted - mouse only + string name; //what to display + string value; //optional - displays current value/state + string description; //optional + float state; //active/disabled + void() action; + vector colour; + + PanelID id; // Must be last member, automatically initialized. +} fo_menu_option; + +typedef struct { + vector position; + vector size; + string title; + float flags; + fo_menu_option options[FO_MENU_MAX_OPTIONS]; + float num_opts; + float active; + void() update; + float page; + string description; + + PanelID id; // Must be after statically initialized members. +} fo_menu; + +class fo_menu_option_2 { + string shortcut; //key to press. if omitted - mouse only + string name; //what to display + string value; //optional - displays current value/state + string description; //optional + float state; //active/disabled + virtual void() action = {}; + vector colour; + fo_menu_option_2 next; +}; + +class fo_menu_2 { + vector position; + vector size; + string title; + float flags; + fo_menu_option_2 options; + float num_opts; + float active; + virtual void() update = {}; +}; + +void FO_Menu_Team(float); +void Menu_Cancel() ; + +fo_menu* InProgressMenu; +fo_menu* CurrentMenu; + +fo_menu_option MenuSpacer = {"","","","",FO_MENU_STATE_SPACER}; + +var fo_menu FO_MENU_GAME = { + [0,0], [300,200], "Fortress One", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","Select Team", "","Join a team",FO_MENU_STATE_NORMAL,{FO_Menu_Team(TRUE); },MENU_BUTTON}, + {"2","Select Class","","Each class has unique strenghts and weaknesses.",FO_MENU_STATE_NORMAL,{localcmd("changeclass\n"); Menu_Cancel(); },MENU_BUTTON}, + {"3","Ready","","Only applies to organised games",FO_MENU_STATE_NORMAL,{localcmd("ready\n"); Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"4","Spectate", "","Do not participate in the game, but observe as a ghost",FO_MENU_STATE_NORMAL,{localcmd("observe\n"); Menu_Cancel();},MENU_BUTTON}, + {"5","Server Admin", "","Admin options for the server",FO_MENU_STATE_DISABLED,{FO_Menu_Admin_Main(TRUE);},MENU_BUTTON}, + {"6","Captain's Menu", "","",FO_MENU_STATE_DISABLED,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_CAPTAIN_PICK, 0);},MENU_BUTTON}, + {"7","Map Menu", "","",FO_MENU_STATE_NORMAL,{showVoteMenu(TRUE);},MENU_BUTTON}, + MenuSpacer, + {"0","Main Menu","","",FO_MENU_STATE_NORMAL,{localcmd("fo_main_menu\n"); Menu_Cancel();},MENU_BUTTON}, + }, 10, TRUE +}; +var fo_menu FO_MENU_GAME_SPECTATOR = { + [0,0], [300,200], "Fortress One", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","Join Game", "","",FO_MENU_STATE_NORMAL,{localcmd("join\n"); Menu_Cancel(); },MENU_BUTTON}, + MenuSpacer, + {"3","Server Admin", "","",FO_MENU_STATE_DISABLED,{FO_Menu_Admin_Main(TRUE);},MENU_BUTTON}, + {"4","Map Menu", "","",FO_MENU_STATE_NORMAL,{showVoteMenu(TRUE);},MENU_BUTTON}, + MenuSpacer, + {"0","Main Menu","","",FO_MENU_STATE_NORMAL,{localcmd("fo_main_menu\n"); Menu_Cancel();},MENU_BUTTON}, + }, 6, TRUE +}; +var fo_menu FO_MENU_SPECTATOR_TRACK = { + [0,0], [300,200], "Track", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Join Game", "","",FO_MENU_STATE_NORMAL,{localcmd("track 1\n"); },MENU_BUTTON}, + }, 0, TRUE +}; + +void teamChosen(string team) = { + localcmd("cmd changeteam ", team, "\n"); + + if(WP_PlayerClass()) { + Menu_Cancel(); + } else { + FO_Menu_Class(TRUE); + } +}; + +var fo_menu FO_MENU_TEAM = { + [0,0], [300,200], "Select Team", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Blue team","","Known for cunning and strategy, Blues like to attack first",FO_MENU_STATE_NORMAL,{teamChosen("1");},MENU_TEXT_BLUE_FO}, + {"2","Red team","","Excellent at standing their ground, Reds won't stand for being attacked",FO_MENU_STATE_NORMAL,{teamChosen("2");},MENU_TEXT_RED_FO}, + {"3","Yellow team","","The best team",FO_MENU_STATE_NORMAL,{teamChosen("3");},MENU_TEXT_YELLOW_FO}, + {"4","Green team","","Also okay",FO_MENU_STATE_NORMAL,{teamChosen("4");},MENU_TEXT_GREEN_FO}, + MenuSpacer, + {"5","Auto-assign team","","",FO_MENU_STATE_NORMAL,{teamChosen("auto");},MENU_BUTTON}, + {"6","All time attack","","Always on the attacking team",FO_MENU_STATE_NORMAL,{teamChosen("attack");},MENU_BUTTON}, + {"7","All time defence","","Always on the defending team",FO_MENU_STATE_NORMAL,{teamChosen("defence");},MENU_BUTTON}, + MenuSpacer, + {"0","Spectate","","",FO_MENU_STATE_NORMAL,{localcmd("observe\n");Menu_Cancel();},MENU_BUTTON}, + }, 10, TRUE, { + if(intermission) { + Menu_Cancel(); + } + } +}; +var fo_menu FO_MENU_CLASS = { + [0,0], [300,200], "Select Class", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES, { + {"1","Scout","","Fastest but weakest. Has a scanner (menu) and a `dash` (special)",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 1\n");Menu_Cancel();},MENU_BUTTON}, + {"2","Sniper","","Long charge times but powerful hits.",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 2\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Soldier","","All-rounder. Has a rocket launcher.",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 3\n");Menu_Cancel();},MENU_BUTTON}, + {"4","Demoman","","Area-denial expert. 'special' to detonate pipebombs. 'menu' for detpack.",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 4\n");Menu_Cancel();},MENU_BUTTON}, + {"5","Medic","","Fast and immune to concussions and disease. \nBioweapon can heal/supercharge teammates and infect enemies\n'special' allows to automatically heal nearby teammates",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 5\n");Menu_Cancel();},MENU_BUTTON}, + {"6","Heavy Weapons","","Toughest of the classes. 'special' allows spinning the cannon without firing.",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 6\n");Menu_Cancel();},MENU_BUTTON}, + {"7","Pyro","","Can still pull off smaller rjs. ",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 7\n");Menu_Cancel();},MENU_BUTTON}, + {"8","Spy","","Can disguise as the enemy and feign death",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 8\n");Menu_Cancel();},MENU_BUTTON}, + {"9","Engineer","","Can build sentry guns and ammo dispensers. Also has powerful EMP grenades as secondaries",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 9\n");Menu_Cancel();},MENU_BUTTON}, + {"0","Random Playerclass","","Class will be randomly chosen upon each spawn.",FO_MENU_STATE_NORMAL,{localcmd("cmd changeclass 10\n");Menu_Cancel();},MENU_BUTTON}, + }, 10, TRUE, { + if(intermission) { + Menu_Cancel(); + } + } +}; +var fo_menu FO_MENU_DROPAMMO = { + [0,0], [300,150], "Drop Ammo", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES, { + {"1","Shells","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dropammo 1\n");Menu_Cancel();},MENU_BUTTON}, + {"2","Nails","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dropammo 2\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Rockets","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dropammo 3\n");Menu_Cancel();},MENU_BUTTON}, + {"4","Cells","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dropammo 4\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 6, TRUE +}; +var fo_menu FO_MENU_SCOUT = { + [0,0], [300,150], "Scanner", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES, { + {"1","Scanner","","",FO_MENU_STATE_NORMAL,{localcmd("cmd autoscan\n");Menu_Cancel();},MENU_BUTTON}, + {"2","Scan for enemies","","",FO_MENU_STATE_NORMAL,{localcmd("cmd scane\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Scan for friendlies","","",FO_MENU_STATE_NORMAL,{localcmd("cmd scanf\n");Menu_Cancel();},MENU_BUTTON}, + {"4","Scan sound","","",FO_MENU_STATE_NORMAL,{localcmd("cmd scansound\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 6, TRUE +}; +//var fo_menu_option FO_MENU_SPY; +var fo_menu FO_MENU_SPY = { + [0,0], [300,150], "Spy", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES, { + {"1","Disguise","","",FO_MENU_STATE_NORMAL,{ FO_Menu_Spy_Skin(TRUE); localcmd("cmd disguise\n");},MENU_BUTTON}, + {"2","Last Disguise","","",FO_MENU_STATE_NORMAL,{localcmd("cmd disguise last\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Feign","","",FO_MENU_STATE_NORMAL,{localcmd("feign\n");Menu_Cancel();},MENU_BUTTON}, + {"4","Reset Disguise","","",FO_MENU_STATE_NORMAL,{localcmd("cmd disguise none\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"0","Manual Colour Changes","","",FO_MENU_STATE_NORMAL,{ + local float smt = !stof(getplayerkeyvalue(player_localnum, "smt")); + localcmd("setinfo smt ", ftos(smt), "\n"); + },MENU_BUTTON}, + }, 8, TRUE, { + if(SBAR.InvisOnly) { + FO_MENU_SPY.options[0].name = "Invisibility"; + FO_MENU_SPY.options[0].value = (SBAR.IsUndercover?"on":"off"); + FO_MENU_SPY.options[1].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_SPY.options[0].name = "Disguise"; + FO_MENU_SPY.options[0].value = (SBAR.IsUndercover?"on":"off"); + if(last_selected_skin || last_team) { + FO_MENU_SPY.options[1].state = FO_MENU_STATE_NORMAL; + FO_MENU_SPY.options[1].value = strcat(TeamToString(last_team)," ",ClassToString(last_selected_skin)); + } else { + FO_MENU_SPY.options[1].state = FO_MENU_STATE_DISABLED; + } + FO_MENU_SPY.options[3].state = (SBAR.IsUndercover?FO_MENU_STATE_NORMAL:FO_MENU_STATE_DISABLED); + } + FO_MENU_SPY.options[7].value = (stof(getplayerkeyvalue(player_localnum, "smt"))?"on":"off"); + } +}; + +void sendDisguiseCommand(float skinno) = { + localcmd("cmd disguise skin ",ftos(skinno),"\n"); + local float smt = stof(getplayerkeyvalue(player_localnum, "smt")); + if (smt) { + FO_Menu_Spy_Team(TRUE); + } else { + Menu_Cancel(); + } +}; + +var fo_menu FO_MENU_SPY_SKIN = { + [0,0], [300,200], "Disguise as enemy", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES, { + {"1","Scout","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(1);},MENU_BUTTON}, + {"2","Sniper","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(2);},MENU_BUTTON}, + {"3","Soldier","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(3);},MENU_BUTTON}, + {"4","Demoman","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(4);},MENU_BUTTON}, + {"5","Medic","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(5);},MENU_BUTTON}, + {"6","Heavy Weapons","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(6);},MENU_BUTTON}, + {"7","Pyro","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(7);},MENU_BUTTON}, + {"8","Spy","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(8);},MENU_BUTTON}, + {"9","Engineer","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(9);},MENU_BUTTON}, + {"0","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + {"-","Civilian","","",FO_MENU_STATE_NORMAL,{sendDisguiseCommand(11);},MENU_BUTTON}, + }, 11, TRUE +}; +var fo_menu FO_MENU_SPY_TEAM = { + [0,0], [300,200], "Disguise as", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Blue team","","",FO_MENU_STATE_NORMAL,{localcmd("cmd disguise team 1\n");Menu_Cancel();},'0.3 0.4 0.7'}, + {"2","Red team","","",FO_MENU_STATE_NORMAL,{localcmd("cmd disguise team 2\n");Menu_Cancel();},'0.7 0.4 0.3'}, + {"3","Yellow team","","",FO_MENU_STATE_NORMAL,{localcmd("cmd disguise team 3\n");Menu_Cancel();},'0.7 0.7 0.3'}, + {"4","Green team","","",FO_MENU_STATE_NORMAL,{localcmd("cmd disguise team 4\n");Menu_Cancel();},'0.4 0.7 0.3'}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 6, TRUE +}; +var fo_menu FO_MENU_DETPACK = { + [0,0], [300,200], "Set detpack for", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","5 Seconds","","",FO_MENU_STATE_NORMAL,{localcmd("cmd detpack 5\n");Menu_Cancel();},MENU_BUTTON}, + {"2","20 Seconds","","",FO_MENU_STATE_NORMAL,{localcmd("cmd detpack 20\n");Menu_Cancel();},MENU_BUTTON}, + {"3","50 Seconds","","",FO_MENU_STATE_NORMAL,{localcmd("cmd detpack 50\n");Menu_Cancel();},MENU_BUTTON}, + {"4","255 Seconds","","",FO_MENU_STATE_NORMAL,{localcmd("cmd detpack 255\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 6, TRUE +}; +var fo_menu FO_MENU_DETPACK_CANCEL = { + [0,0], [300,200], "Setting detpack...", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Cancel","","",FO_MENU_STATE_NORMAL,{localcmd("cmd detpack cancel\n");Menu_Cancel();},MENU_BUTTON}, + }, 1, TRUE, { + if(!SBAR.IsDetpacking) { + Menu_Cancel(); + } + } +}; +var fo_menu FO_MENU_BUILD_CANCEL = { + [0,0], [300,200], "Building...", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Cancel","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build cancel\n");Menu_Cancel();},MENU_BUTTON}, + }, 1, TRUE, { + if(!SBAR.IsBuilding) { + Menu_Cancel(); + } + } +}; +var fo_menu FO_MENU_BUILD = { + [0,0], [300,200], "Engineering", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Build Sentry Gun","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build sentry\n");Menu_Cancel();},MENU_BUTTON}, + {"2","Build Dispenser","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build dispenser\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Destroy Sentry Gun","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build destroy sentry\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Dismantle Sentry Gun","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build destroy sentry\n");Menu_Cancel();},MENU_BUTTON}, + {"4","Destroy Dispenser","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build destroy dispenser\n");Menu_Cancel();},MENU_BUTTON}, + {"4","Dismantle Dispenser","","",FO_MENU_STATE_NORMAL,{localcmd("cmd build destroy dispenser\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 8, TRUE, { + if(SBAR.IsBuilding) { + Menu_Cancel(); + //CurrentMenu = &FO_MENU_BUILD_CANCEL; + FO_Menu_Build(FALSE); + return; + } + if(SBAR.HasSentry) { + FO_MENU_BUILD.options[0].state = FO_MENU_STATE_HIDDEN; //Build + if(vlen(PM_Org() - sentry_pos) <= ENG_BUILDING_DISMANTLE_DISTANCE) { + FO_MENU_BUILD.options[2].state = FO_MENU_STATE_HIDDEN; //Destroy + FO_MENU_BUILD.options[3].state = FO_MENU_STATE_NORMAL; //Dismantle + } else { + FO_MENU_BUILD.options[2].state = FO_MENU_STATE_NORMAL; //Destroy + FO_MENU_BUILD.options[3].state = FO_MENU_STATE_HIDDEN; //Dismantle + } + } else { + if(getstatf(STAT_CELLS) >= ENG_SENTRY_COST) { + FO_MENU_BUILD.options[0].state = FO_MENU_STATE_NORMAL; + } else { + FO_MENU_BUILD.options[0].state = FO_MENU_STATE_DISABLED; + } + FO_MENU_BUILD.options[2].state = FO_MENU_STATE_HIDDEN; + FO_MENU_BUILD.options[3].state = FO_MENU_STATE_HIDDEN; + } + if(SBAR.HasDispenser) { + FO_MENU_BUILD.options[1].state = FO_MENU_STATE_HIDDEN; + if(vlen(PM_Org() - dispenser_pos) <= ENG_BUILDING_DISMANTLE_DISTANCE) { + FO_MENU_BUILD.options[4].state = FO_MENU_STATE_HIDDEN; + FO_MENU_BUILD.options[5].state = FO_MENU_STATE_NORMAL; + } else { + FO_MENU_BUILD.options[4].state = FO_MENU_STATE_NORMAL; + FO_MENU_BUILD.options[5].state = FO_MENU_STATE_HIDDEN; + } + } else { + if(getstatf(STAT_CELLS) >= ENG_DISPENSER_COST) { + FO_MENU_BUILD.options[1].state = FO_MENU_STATE_NORMAL; + } else { + FO_MENU_BUILD.options[1].state = FO_MENU_STATE_DISABLED; + } + FO_MENU_BUILD.options[4].state = FO_MENU_STATE_HIDDEN; + FO_MENU_BUILD.options[5].state = FO_MENU_STATE_HIDDEN; + } + } +}; +var fo_menu FO_MENU_SENTRY_MAINTAIN = { + [0,0], [300,200], "Sentry Gun", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Anticlockwise","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate -45\n");Menu_Cancel();},MENU_BUTTON}, + {"2","180 Degrees","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate 180\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Clockwise","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate 45\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + /* {"4","With Mouse","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate mouse\n");Menu_Cancel();},MENU_BUTTON}, */ + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 5, TRUE, { + if(vlen(PM_Org() - sentry_pos) > ENG_BUILDING_MAINT_DISTANCE) { + Menu_Cancel(); + return; + } + } +}; + +var fo_menu FO_MENU_SENTRY_ROTATE = { + [0,0], [300,200], "Rotate Sentry Gun", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Anticlockwise","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate -45\n");Menu_Cancel();},MENU_BUTTON}, + {"2","180 Degrees","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate 180\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Clockwise","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate 45\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + /* {"4","With Mouse","","",FO_MENU_STATE_NORMAL,{localcmd("cmd sentry rotate mouse\n");Menu_Cancel();},MENU_BUTTON}, */ + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 5, TRUE, { + if(vlen(PM_Org() - sentry_pos) > ENG_BUILDING_MAINT_DISTANCE) { + Menu_Cancel(); + return; + } + } +}; +var fo_menu FO_MENU_DISPENSER_MAINTAIN = { + [0,0], [300,200], "Dispenser", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Insert Ammo","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dispenser ammo\n");Menu_Cancel();},MENU_BUTTON}, + {"2","Insert Armor","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dispenser armor\n");Menu_Cancel();},MENU_BUTTON}, + {"3","Repair","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dispenser repair\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 5, TRUE, { + if(vlen(PM_Org() - dispenser_pos) > ENG_BUILDING_MAINT_DISTANCE) { + Menu_Cancel(); + return; + } + } +}; +var fo_menu FO_MENU_DISPENSER_USE = { + [0,0], [300,200], "Use Dispenser", FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS, { + {"1","Withdraw Ammo","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dispenser withdraw ammo\n");Menu_Cancel();},MENU_BUTTON}, + {"2","Withdraw Armor","","",FO_MENU_STATE_NORMAL,{localcmd("cmd dispenser withdraw armor\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + {"5","Nothing","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 4, TRUE, { + if(vlen(PM_Org() - touched_dispenser_pos) > ENG_BUILDING_MAINT_DISTANCE) { + Menu_Cancel(); + return; + } + } +}; + +void updateAdminMenuInfo() = { + if(admin_menu_next_update < time) { + localcmd("cmd adminrefresh\n"); + admin_menu_next_update = time + CVARF(fo_adminrefresh); + } +} + +var fo_menu FO_MENU_ADMIN_MAIN = { + [0,0], [300,300], "Server Admin [1/3]", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","Ceasefire","","Pause the game",FO_MENU_STATE_NORMAL,{localcmd("cmd ceasefire\n");},MENU_BORDER_WARNING}, + {"2","Kick...","","",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_ADMIN_KICK, 0);},MENU_BORDER_WARNING}, + {"3","Ban...","","",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_ADMIN_BAN, 0);},MENU_BORDER_WARNING}, + {"4","Force Spectate...","","Force someone to be a spectator",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_ADMIN_FORCE_SPEC, 0);},MENU_BORDER_WARNING}, + {"5","Randomize Teams","","",FO_MENU_STATE_NORMAL,{localcmd("cmd randomise\n");},MENU_BORDER_WARNING}, + {"6","Restart Current Map","","",FO_MENU_STATE_NORMAL,{localcmd("cmd restart\n"); Menu_Cancel();},MENU_BORDER_WARNING}, + {"7","End Current Map","","",FO_MENU_STATE_NORMAL,{localcmd("cmd forcebreak\n"); Menu_Cancel();},MENU_BORDER_WARNING}, + {"8","Update Server","","",FO_MENU_STATE_NORMAL,{localcmd("cmd updateserver\n"); Menu_Cancel();},MENU_BORDER_WARNING}, + MenuSpacer, + {"0","Close Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + {"+","Next - Modes","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Modes(TRUE);},MENU_BUTTON}, + {"-","Prev - Settings","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Settings(TRUE);},MENU_BUTTON}, + }, 12, TRUE, { + updateAdminMenuInfo(); + FO_MENU_ADMIN_MAIN.options[0].value = SERVER_ADMIN.ceasefire?"on":"off"; + FO_MENU_ADMIN_MAIN.options[5].value = mapname; + } +}; + +//Basically bitfields - bit 1 is what the server is currently at, bit 2 what is requested +string (float m) modeStatus = { + switch(m) { + case 0: + return "off"; + case 1: + return "pending off"; + case 2: + return "pending on"; + case 3: + return "on"; + } + return ""; +} + +string (float m) ratedStatus = { + switch(m) { + case 0: + return "unrated"; + case 1: + return "rated"; + case 2: + return "default"; + } + return ""; +} + +var fo_menu FO_MENU_ADMIN_MODES = { + [0,0], [300,300], "Server Modes [2/3]", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","Pub Mode","","Public play, anything goes",FO_MENU_STATE_NORMAL,{localcmd("cmd pubmode\n");},MENU_BORDER_WARNING}, + {"2","Clan Mode","","Game has a prematch",FO_MENU_STATE_NORMAL,{localcmd("cmd clanmode\n");},MENU_BORDER_WARNING}, + {"3","Quad Mode","","Play for a set number of rounds, designed for attack vs defence",FO_MENU_STATE_NORMAL,{localcmd("cmd quadmode\n");},MENU_BORDER_WARNING}, + {"4","Duel Mode","","Simplifies 1 on 1 action",FO_MENU_STATE_NORMAL,{localcmd("cmd duelmode\n");},MENU_BORDER_WARNING}, + {"5","Captains Mode","","Select captains who can then pick their teams",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_CAPTAIN_1, 0);},MENU_BORDER_WARNING}, + {"7","Rated/Unrated","","Will player ratings be affected?",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_FoMatchRated(TRUE);},MENU_BORDER_WARNING}, + {"8","New Balance","","New Balance Test",FO_MENU_STATE_NORMAL,{localcmd("cmd new_balance");},MENU_BORDER_WARNING}, + {"9","Force Start","","Skip prematch and start the game\nPlease use sparingly",FO_MENU_STATE_NORMAL,{localcmd("cmd forcestart\n");},MENU_BORDER_WARNING}, + MenuSpacer, + {"0","Close Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + {"+","Next - Settings","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Settings(TRUE);},MENU_BUTTON}, + {"-","Prev - Main","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Main(TRUE);},MENU_BUTTON}, + }, 12, TRUE, { + updateAdminMenuInfo(); + FO_MENU_ADMIN_MODES.options[0].value = modeStatus(SERVER_ADMIN.pubmode); + FO_MENU_ADMIN_MODES.options[1].value = modeStatus(SERVER_ADMIN.clanmode); + FO_MENU_ADMIN_MODES.options[2].value = modeStatus(SERVER_ADMIN.quadmode); + FO_MENU_ADMIN_MODES.options[3].value = modeStatus(SERVER_ADMIN.duelmode); + FO_MENU_ADMIN_MODES.options[4].value = SERVER_ADMIN.captainmode?"on":"off"; + FO_MENU_ADMIN_MODES.options[6].value = ratedStatus(SERVER_ADMIN.fo_matchrated); + FO_MENU_ADMIN_MODES.options[6].value = modeStatus(SERVER_ADMIN.new_balance); + } +}; +var fo_menu FO_MENU_ADMIN_SETTINGS = { + [0,0], [300,300], "Settings [3/3]", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","Timelimit","","",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Timelimit(TRUE);},MENU_BORDER_WARNING}, + {"2","Fraglimit","","",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Fraglimit(TRUE);},MENU_BORDER_WARNING}, + {"3","Quad Rounds...","","Number of rounds in Quad mode. Usually 2",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Rounds(TRUE);},MENU_BORDER_WARNING}, + {"4","Quad Round Time...","","Round time for each quad round",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_QuadTimelimit(TRUE);},MENU_BORDER_WARNING}, + {"5","Play To Completion","","Play match to end of round even if result decided",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_PlayToCompletion(TRUE);},MENU_BORDER_WARNING}, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + {"0","Close Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + {"+","Next - Main","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Main(TRUE);},MENU_BUTTON}, + {"-","Prev - Modes","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Modes(TRUE);},MENU_BUTTON}, + }, 12, TRUE, { + updateAdminMenuInfo(); + FO_MENU_ADMIN_SETTINGS.options[0].value = ftos(SERVER_ADMIN.timelimit); + FO_MENU_ADMIN_SETTINGS.options[1].value = ftos(SERVER_ADMIN.fraglimit); + FO_MENU_ADMIN_SETTINGS.options[2].value = ftos(SERVER_ADMIN.quad_rounds); + FO_MENU_ADMIN_SETTINGS.options[3].value = ftos(SERVER_ADMIN.quad_round_time); + FO_MENU_ADMIN_SETTINGS.options[4].value = modeStatus(SERVER_ADMIN.play_to_completion); + } +}; +var void execute_admin_players(float choice, float page) { + print("not implemented ", ftos(choice + (page * 9)),"\n"); +} +void execute_admin_players_unknown(float choice, float page) { + print("not implemented ", ftos(choice + (page * 9)),"\n"); +} +void execute_admin_players_kick(float choice, float page) { + string userid = getplayerkeyvalue(choice + (page * 9), "userid"); + localcmd("cmd kick ", userid, "\n"); + Menu_Cancel(); + FO_Menu_Admin_Main(TRUE); +} +void execute_admin_players_ban(float choice, float page) { + string userid = getplayerkeyvalue(choice + (page * 9), "userid"); + localcmd("cmd ban ", userid, "\n"); + Menu_Cancel(); + FO_Menu_Admin_Main(TRUE); +} +void execute_admin_players_force_spec(float choice, float page) { + string userid = getplayerkeyvalue(choice + (page * 9), "userid"); + localcmd("cmd forcespec ", userid, "\n"); + Menu_Cancel(); + FO_Menu_Admin_Main(TRUE); +} +void execute_admin_players_captain1(float choice, float page) { + string userid = getplayerkeyvalue(choice + (page * 9), "userid"); + captain1_temp = stof(userid); + FO_Menu_Admin_Players(TRUE, CLIENT_MENU_CAPTAIN_2, 0); +} +void execute_admin_players_captain2(float choice, float page) { + string userid = getplayerkeyvalue(choice + (page * 9), "userid"); + captain2_temp = stof(userid); + //Send server cmd setting captains; reset everyone to observer; spam out captains set message + localcmd("cmd captainmode ", ftos(captain1_temp), " ", userid, "\n"); + FO_Menu_Admin_Main(TRUE); +} +void execute_admin_players_captain_pick(float choice, float page) { + string userid = getplayerkeyvalue(choice + (page * 9), "userid"); + localcmd("cmd captainpick ", userid, "\n"); +} + +var fo_menu FO_MENU_ADMIN_PLAYERS = { + [0,0], [300,300], "Players", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(0, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"2","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(1, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"3","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(2, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"4","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(3, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"5","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(4, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"6","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(5, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"7","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(6, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"8","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(7, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"9","","","",FO_MENU_STATE_NORMAL,{execute_admin_players(8, FO_MENU_ADMIN_PLAYERS.page);},MENU_BG_DARK}, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Main(TRUE);},MENU_BUTTON}, + {"+","Next","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Players(TRUE, player_menu_type, FO_MENU_ADMIN_PLAYERS.page + 1);},MENU_BUTTON}, + {"-","Prev","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Players(TRUE, player_menu_type, FO_MENU_ADMIN_PLAYERS.page - 1);},MENU_BUTTON}, + }, 12, TRUE +}; +void action_admin_players(float type) = { + string n, team; + float c = 0, p = 0, spec = 0, userid = 0, enable; + float page = FO_MENU_ADMIN_PLAYERS.page; + for(float i = 0; i < 9; i++) { + n = getplayerkeyvalue(i + (page * 9),"name"); + spec = stof(getplayerkeyvalue(i + (page * 9), "*spectator")); + team = getplayerkeyvalue(i + (page * 9), "team"); + userid = stof(getplayerkeyvalue(i + (page * 9), "userid")); + enable = TRUE; + if(!n){ + enable = FALSE; + } + if(type == CLIENT_MENU_ADMIN_FORCE_SPEC && spec){ + enable = FALSE; + } + if(type == CLIENT_MENU_CAPTAIN_PICK) { + if(spec) { + enable = FALSE; + } + if(team) { + enable = FALSE; + } + if(i + (page * 9) == player_localnum) { + enable = FALSE; + } + } + if(type == CLIENT_MENU_CAPTAIN_1 || type == CLIENT_MENU_CAPTAIN_2) { + if(spec) { + enable = FALSE; + } + if(userid == captain1_temp) { + enable = FALSE; + } + } + if(enable) { + FO_MENU_ADMIN_PLAYERS.options[i].state = FO_MENU_STATE_NORMAL; + FO_MENU_ADMIN_PLAYERS.options[i].name = n; + FO_MENU_ADMIN_PLAYERS.options[i].value = spec?"Spectator":""; + FO_MENU_ADMIN_PLAYERS.options[i].colour = MENU_BG_WARNING; + c++; + } else { + FO_MENU_ADMIN_PLAYERS.options[i].state = FO_MENU_STATE_DISABLED; + FO_MENU_ADMIN_PLAYERS.options[i].name = n; + FO_MENU_ADMIN_PLAYERS.options[i].value = ""; + FO_MENU_ADMIN_PLAYERS.options[i].colour = MENU_BG_DARK; + } + } +} +void action_admin_players_captain_pick() = { + action_admin_players(CLIENT_MENU_CAPTAIN_PICK); +} +var fo_menu FO_MENU_ADMIN_ROUNDS = { + [0,0], [300,300], "Quad Rounds", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","1","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 1\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"2","2","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 2\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"3","3","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 3\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"4","4","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 4\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"5","5","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 5\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"6","6","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 6\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"7","7","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 7\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"8","8","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 8\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"9","9","","",FO_MENU_STATE_NORMAL,{localcmd("cmd rounds 9\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + MenuSpacer, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Modes(TRUE);},MENU_BUTTON}, + }, 11, TRUE +}; +var fo_menu FO_MENU_ADMIN_QUAD_TIMELIMIT = { + [0,0], [300,200], "Quad Timelimit", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","1","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 1\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"2","5","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 5\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"3","10","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 10\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"4","15","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 15\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"5","20","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 20\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"6","25","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 25\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"7","30","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 30\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"8","45","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 45\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"9","60","","",FO_MENU_STATE_NORMAL,{localcmd("cmd roundtime 60\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + MenuSpacer, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Modes(TRUE);},MENU_BUTTON}, + }, 11, TRUE +}; +var fo_menu FO_MENU_ADMIN_PLAY_TO_COMPLETION = { + [0,0], [300,200], "Play to completion?", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","No","","Match will end as soon as the winner is known",FO_MENU_STATE_NORMAL,{localcmd("cmd play_to_completion 0\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"2","Yes","","Match will end at the end of the final round",FO_MENU_STATE_NORMAL,{localcmd("cmd play_to_completion 1\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Modes(TRUE);},MENU_BUTTON}, + }, 10, TRUE +}; +var fo_menu FO_MENU_ADMIN_MATCH_RATED = { + [0,0], [300,200], "Match Rated?", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","Rated","","Match result will affect player ratings",FO_MENU_STATE_NORMAL,{localcmd("cmd fo_matchrated 1\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"2","Unrated","","Match result will not affect player ratings",FO_MENU_STATE_NORMAL,{localcmd("cmd fo_matchrated 0\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + {"3","Default","","Only matches with a teams of three or more players will be rated",FO_MENU_STATE_NORMAL,{localcmd("cmd fo_matchrated 2\n");FO_Menu_Admin_Modes(TRUE);},MENU_BORDER_WARNING}, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Modes(TRUE);},MENU_BUTTON}, + }, 10, TRUE +}; +var fo_menu FO_MENU_ADMIN_TIMELIMIT = { + [0,0], [300,200], "Set Timelimit", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","0 (infinite)","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 0\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"2","5","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 5\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"3","10","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 10\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"4","15","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 15\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"5","20","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 20\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"6","30","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 30\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"7","45","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 45\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"8","60","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 60\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"9","120","","",FO_MENU_STATE_NORMAL,{localcmd("cmd timelimit 120\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Settings(TRUE);},MENU_BUTTON}, + }, 10, TRUE +}; +var fo_menu FO_MENU_ADMIN_FRAGLIMIT = { + [0,0], [300,200], "Set Fraglimit", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { + {"1","0 (infinite)","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 0\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"2","5","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 5\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"3","10","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 10\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"4","15","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 15\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"5","20","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 20\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"6","30","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 30\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"7","50","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 50\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"8","100","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 100\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"9","200","","",FO_MENU_STATE_NORMAL,{localcmd("cmd fraglimit 200\n");FO_Menu_Admin_Settings(TRUE);},MENU_BORDER_WARNING}, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();FO_Menu_Admin_Settings(TRUE);},MENU_BUTTON}, + }, 10, TRUE +}; +var fo_menu FO_MENU_VOTE = { + [0,0], [300,200], "Map Vote", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES, { + {"1","","","",FO_MENU_STATE_NORMAL,{localcmd("cmd votemap ",FO_MENU_VOTE.options[0].name,"\n");Menu_Cancel();},MENU_BUTTON}, + {"2","","","",FO_MENU_STATE_NORMAL,{localcmd("cmd votemap ",FO_MENU_VOTE.options[1].name,"\n");Menu_Cancel();},MENU_BUTTON}, + {"3","","","",FO_MENU_STATE_NORMAL,{localcmd("cmd votemap ",FO_MENU_VOTE.options[2].name,"\n");Menu_Cancel();},MENU_BUTTON}, + {"4","","","",FO_MENU_STATE_NORMAL,{localcmd("cmd votemap ",FO_MENU_VOTE.options[3].name,"\n");Menu_Cancel();},MENU_BUTTON}, + {"5","","","",FO_MENU_STATE_NORMAL,{localcmd("cmd votemap ",FO_MENU_VOTE.options[5].name,"\n");Menu_Cancel();},MENU_BUTTON}, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + MenuSpacer, + {"0","Back to Main Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, + }, 10, TRUE, { + FO_MENU_VOTE.title = strcat("Map Vote (", ftos(ceil(mapvote_expiry - time)), "s left)"); + if((mapvote_expiry - time) < 0) { + Menu_Cancel(); + } + } +}; + +vector fo_menu_draw(fo_menu * menu) = { + vector position; + if(menu.flags & FO_MENU_FLAG_CENTER) { + position = getPanelPosition(MenuPanel); + } else { + position = menu.position; + } + + if(fo_hud_editor) return position; + if(!menu.active || showingscores || (intermission && !(menu.flags & FO_MENU_FLAG_ALLOW_INTERMISSION))) { + setcursormode(FALSE); + return position; + } + + FO_Hud_ShowPanel(HUDP_MENU_HINT); + SBAR.Hint = ""; + + setcursormode(menu.flags & FO_MENU_FLAG_USE_MOUSE); + + if(menu.update) { + menu.update(); + } + + local float scale = MenuPanel.Scale, textscale = MenuPanel.TextScale; + if(!textscale) { + textscale = scale; + } + + local float padding = 4 * scale, titleoffset = 36 * scale; + local vector menusize = menu.size * scale; + local vector buttonsize = [menusize.x - padding * 2, 24]; + local vector buttonpos = position; + local float buttonhover = FALSE; + local vector smalltext = MENU_TEXT_SMALL * textscale, mediumtext = MENU_TEXT_MEDIUM * textscale; + local vector tempcolour; + local float alignment = SUI_ALIGN_START; + local vector bgcolour = (menu.flags & FO_MENU_FLAG_WARNING)?MENU_BG_WARNING:MENU_BG; + local vector bordercolour = (menu.flags & FO_MENU_FLAG_WARNING)?MENU_BORDER_WARNING:MENU_BORDER; + + menusize.y = titleoffset + menu.num_opts * (buttonsize.y + padding); + + if (sui_is_held(menu->id)) { + position = position + [Mouse.x, Mouse.y] - HudSettings.MousePos; + MenuPanel.Position = MenuPanel.Position + [Mouse.x, Mouse.y] - HudSettings.MousePos; + } + + sui_border_box(position, menusize, 1, bordercolour, FO_MENU_TRANSPARENCY, 0); + sui_push_frame(position, menusize); + + sui_fill([0, 0], menusize, bgcolour, FO_MENU_TRANSPARENCY, 0); + + sui_set_align([SUI_ALIGN_CENTER, SUI_ALIGN_CENTER]); + sui_action_element([0, 0], menusize, menu.id, sui_noop); + sui_pop_frame(); + + HRC_drawstring( + position + [menusize.x / 2 - stringwidth(menu.title,1,mediumtext)/2,padding*2], + menu.title, + mediumtext, + (menu.flags & FO_MENU_FLAG_WARNING)?MENU_TEXT_WARNING:MENU_TEXT_2, + 1, + 0 + ); + float row = 0; + SBAR.Hint = menu.description; + for(float i = 0; i < FO_MENU_MAX_OPTIONS; i++) { + vector shortcutoffset = [0,0], valueoffset = [0,0]; + + if(menu.options[i].state == FO_MENU_STATE_HIDDEN) { + //continue; + } else if(menu.options[i].state == FO_MENU_STATE_SPACER) { + row++; + //continue; + } else { + if(menu.flags & FO_MENU_FLAG_SHOW_SHORTCUTS) { + shortcutoffset = [padding * 4 + smalltext.x, 0]; + } + if(menu.flags & FO_MENU_FLAG_SHOW_VALUES) { + valueoffset = [padding * 4 + smalltext.x * 1, 0]; + } + + if(menu.options[i].name) { + buttonpos = position + [padding, titleoffset + row * (buttonsize.y + padding)]; + buttonhover = (Mouse.x > buttonpos.x && Mouse.y > buttonpos.y && Mouse.x < (buttonpos.x + buttonsize.x) && Mouse.y < (buttonpos.y + buttonsize.y)); + if(buttonhover) { + SBAR.Hint = menu.options[i].description; + } + + if(menu.options[i].state == FO_MENU_STATE_NORMAL) { + //if(hud_colour_button(strcat("fo_menu_",menu.title,"_",menu.options[i].name), position + [padding, titleoffset + row * (buttonsize.y + padding)] + shortcutoffset, buttonsize - shortcutoffset - valueoffset, menu.options[i].name, menu.options[i].colour, smalltext, SUI_ALIGN_START, padding * 3 + smalltext.x)) { + if(hud_colour_button(menu.options[i].id, buttonpos, buttonsize, menu.options[i].name, menu.options[i].colour, smalltext, MENU_TEXT_1, alignment, shortcutoffset.x, 0.6, 1, 0)) { + menu.options[i].action(); + } + } else if(menu.options[i].state == FO_MENU_STATE_DISABLED) { + if(menu.options[i].name) { + //sui_border_box(position + [padding, titleoffset + row * (buttonsize.y + padding)] + shortcutoffset, buttonsize - shortcutoffset - valueoffset, 1, bgcolour, 0.4, 0); + sui_border_box(buttonpos, buttonsize, 1, bgcolour, 0.4, 0); + float disabledtextoffset = 0; + if(alignment == SUI_ALIGN_START) { + disabledtextoffset = padding + shortcutoffset.x; + } else { + padding + (buttonsize.x / 2) - stringwidth(menu.options[i].name,1,smalltext) / 2; //Centered + } + HRC_drawstring( + position + [disabledtextoffset, titleoffset + row * (buttonsize.y + padding) + (buttonsize.y / 2) - (MENU_TEXT_SMALL.y / 2)], + menu.options[i].name, + smalltext, + MENU_TEXT_2, + 1, + 0 + ); + } + + } + if(menu.options[i].shortcut) { + HRC_drawstring( + position + [padding*3, titleoffset + row * (buttonsize.y + padding) + (buttonsize.y / 2) - (smalltext.y / 2)], + menu.options[i].shortcut, + smalltext, + MENU_TEXT_3, + 1, + 0 + ); + } + if(menu.options[i].value) { + HRC_drawstring( + position + [menusize.x - padding*4 - stringwidth(menu.options[i].value,1,smalltext) , titleoffset + row * (buttonsize.y + padding) + (buttonsize.y / 2) - (smalltext.y / 2)], + menu.options[i].value, + //strcat(ftos(row),"/",ftos(i)), + smalltext, + MENU_TEXT_4, + 1, + 0 + ); + } + } + row++; + } + } + return position; +} + +float fo_menu_process_input(fo_menu * menu, float scan) = { + local string button = ""; + local float found = FALSE; + /* + if(scan > 47 && scan < 58) { + button = ftos(scan - 48); + } else { + switch(scan) { + case 45: + button = "-"; + break; + case 61: + button = "+"; + break; + } + } + */ + string prefix = "fo_menu_option_"; + for(float i = 0; i < MENU_OPTION.length; i++) { + if(str2chr(cvar_string(strcat("fo_menu_option_",MENU_OPTION[i])),0) == scan) { + button = MENU_OPTION[i]; + break; + } + } + if(button != "") { + for(float i = 0; i < menu.num_opts; i++) { + if(menu.options[i].shortcut == button) { + if(menu.options[i].state == FO_MENU_STATE_NORMAL) { + menu.options[i].action(); + } + //If a shortcut matches, always absorb it, even if disabled + //To avoid weird things like accidentally switching weapons + //While trying to pick a disabled option + found = TRUE; + } + } + } + //Trigger all of them + return found; +} + +void Menu_Cancel() = { + if (fo_hud_menu_active) { + setcursormode(FALSE); + fo_hud_menu_active = FALSE; + } + FO_Hud_HidePanel(HUDP_MENU_HINT); +} + +void Menu_Draw(float width, float height, float menushown) = { + if(fo_hud_menu_active) { + CurrentMenu.position = fo_menu_draw(CurrentMenu); + //MenuPanel.Position = CurrentMenu.position; + } else { + Menu_Cancel(); + } +} + +void FO_Menu_Game(float force) = { + if(fo_hud_menu_active && (CurrentMenu == &FO_MENU_GAME || CurrentMenu == &FO_MENU_GAME_SPECTATOR)) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && !force) + return; + + // Here we care about distinguishing connecting as spectator vs observer, so + // query it rather than using game_state which mixes. + float true_spec = stof(getplayerkeyvalue(player_localnum, "*spectator")); + if (true_spec) { + //print(getplayerkeyvalue(player_localnum, "name"), " is a spectator!\n"); + //FO_MENU_GAME_SPECTATOR.num_opts = 3; + FO_MENU_GAME_SPECTATOR.options[2].state = is_admin?FO_MENU_STATE_NORMAL:FO_MENU_STATE_DISABLED; + //if(is_admin) FO_MENU_GAME_SPECTATOR.num_opts++; + CurrentMenu = &FO_MENU_GAME_SPECTATOR; + } else { + float userid = stof(getplayerkeyvalue(player_localnum, "userid")); + + //FO_MENU_GAME.num_opts = 7; + FO_MENU_GAME.options[5].state = is_admin?FO_MENU_STATE_NORMAL:FO_MENU_STATE_DISABLED; + //if(is_admin) FO_MENU_GAME.num_opts++; + FO_MENU_GAME.options[6].state = (userid == captain1 || userid == captain2)?FO_MENU_STATE_NORMAL:FO_MENU_STATE_DISABLED; + //if(is_admin) FO_MENU_GAME.num_opts++; + CurrentMenu = &FO_MENU_GAME; + } + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Track() = { + if (!game_state.is_spectator) + return; + + local float i = 0; + local entity e = find(world, classname, "player"); + while(e) { + if(i > 19) break; + FO_MENU_SPECTATOR_TRACK.options[i].name = e.netname; + FO_MENU_SPECTATOR_TRACK.options[i].shortcut = ftos(i+1); + //FO_MENU_SPECTATOR_TRACK.options[i].action = void(){}; + i++; + e = find(e, classname, "player"); + } + FO_MENU_SPECTATOR_TRACK.num_opts = i; + //FO_MENU_SPECTATOR_TRACK.position = fo_menu_draw(FO_MENU_SPECTATOR_TRACK); +} + +void FO_Menu_Team(float force) = { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_TEAM && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) { + return; + } + if(team_no) { + if(number_of_teams < 2) { + return; + } + FO_MENU_TEAM.options[5].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_TEAM.options[5].state = FO_MENU_STATE_NORMAL; + } + for(float i = 0; i < 4; i++) { + if(i < number_of_teams) { + //FO_MENU_TEAM.options[i].value = ftos(readbyte()); + if((i + 1) != team_no) { + FO_MENU_TEAM.options[i].state = FO_MENU_STATE_NORMAL; + } else { + FO_MENU_TEAM.options[i].state = FO_MENU_STATE_DISABLED; + } + } else { + FO_MENU_TEAM.options[i].state = FO_MENU_STATE_DISABLED; + } + } + localcmd("cmd changeteam\n"); + CurrentMenu = &FO_MENU_TEAM; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Class(float force) = { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_CLASS && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) + return; + CurrentMenu = &FO_MENU_CLASS; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_DropAmmo(float force) { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_DROPAMMO) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && !force) + return; + + float available = 0; + FO_MENU_DROPAMMO.options[0].value = ""; + if(getstatf(STAT_SHELLS) < DROP_SHELLS) { + FO_MENU_DROPAMMO.options[0].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_DROPAMMO.options[0].state = FO_MENU_STATE_NORMAL; + available++; + } + FO_MENU_DROPAMMO.options[1].value = ""; + if(getstatf(STAT_NAILS) < DROP_NAILS) { + FO_MENU_DROPAMMO.options[1].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_DROPAMMO.options[1].state = FO_MENU_STATE_NORMAL; + available++; + } + FO_MENU_DROPAMMO.options[2].value = ""; + if(getstatf(STAT_ROCKETS) < DROP_ROCKETS) { + FO_MENU_DROPAMMO.options[2].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_DROPAMMO.options[2].state = FO_MENU_STATE_NORMAL; + available++; + } + FO_MENU_DROPAMMO.options[3].value = ""; + if(getstatf(STAT_CELLS) < DROP_CELLS) { + FO_MENU_DROPAMMO.options[3].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_DROPAMMO.options[3].state = FO_MENU_STATE_NORMAL; + available++; + } + if(!available) { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_DROPAMMO) { + Menu_Cancel(); + } else { + print("Not enough ammo\n"); + } + return; + } + if (WP_PlayerClass() == PC_ENGINEER) { + FO_MENU_DROPAMMO.title = "Drop or Make Ammo"; + if ((getstatf(STAT_SHELLS) < DROP_SHELLS) && ((getstatf(STAT_CELLS) / AMMO_COST_SHELLS) > (DROP_SHELLS - getstatf(STAT_SHELLS)))) { + FO_MENU_DROPAMMO.options[0].value = "(make)"; + FO_MENU_DROPAMMO.options[0].state = FO_MENU_STATE_NORMAL; + } + if ((getstatf(STAT_NAILS) < DROP_NAILS) && ((getstatf(STAT_CELLS) / AMMO_COST_NAILS) > (DROP_NAILS - getstatf(STAT_NAILS)))) { + FO_MENU_DROPAMMO.options[1].value = "(make)"; + FO_MENU_DROPAMMO.options[1].state = FO_MENU_STATE_NORMAL; + } + if ((getstatf(STAT_ROCKETS) < DROP_ROCKETS) && ((getstatf(STAT_CELLS) / AMMO_COST_ROCKETS) > (DROP_ROCKETS - getstatf(STAT_ROCKETS)))) { + FO_MENU_DROPAMMO.options[2].value = "(make)"; + FO_MENU_DROPAMMO.options[2].state = FO_MENU_STATE_NORMAL; + } + } else { + FO_MENU_DROPAMMO.title = "Drop Ammo"; + } + + CurrentMenu = &FO_MENU_DROPAMMO; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Scout(float force, float scanner_on, float scanner_flags) = { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_SCOUT && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) + return; + FO_MENU_SCOUT.options[0].value = (scanner_on?"on":"off"); + FO_MENU_SCOUT.options[1].value = ((scanner_flags & 1)?"on":"off"); + FO_MENU_SCOUT.options[2].value = ((scanner_flags & 2)?"on":"off"); + FO_MENU_SCOUT.options[3].value = ((scanner_flags & 4)?"on":"off"); + CurrentMenu = &FO_MENU_SCOUT; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Spy(float force) = { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_SPY && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) + return; + if(SBAR.InvisOnly) { + //FO_MENU_SPY.options[0].state = FO_MENU_STATE_HIDDEN; + //FO_MENU_SPY.options[1].state = FO_MENU_STATE_NORMAL; + FO_MENU_SPY.options[0].name = "Invisibility"; + FO_MENU_SPY.options[0].value = (SBAR.IsUndercover?"on":"off"); + FO_MENU_SPY.options[1].state = FO_MENU_STATE_DISABLED; + } else { + //FO_MENU_SPY.options[0].state = FO_MENU_STATE_NORMAL; + //FO_MENU_SPY.options[1].state = FO_MENU_STATE_HIDDEN; + FO_MENU_SPY.options[0].name = "Disguise"; + FO_MENU_SPY.options[0].value = (SBAR.IsUndercover?"on":"off"); + if(last_selected_skin || last_team) { + FO_MENU_SPY.options[1].state = FO_MENU_STATE_NORMAL; + FO_MENU_SPY.options[1].value = strcat(TeamToString(last_team)," ",ClassToString(last_selected_skin)); + } else { + FO_MENU_SPY.options[1].state = FO_MENU_STATE_DISABLED; + } + FO_MENU_SPY.options[3].state = (SBAR.IsUndercover?FO_MENU_STATE_NORMAL:FO_MENU_STATE_DISABLED); + } + local float smt = stof(getplayerkeyvalue(player_localnum, "smt")); + FO_MENU_SPY.options[7].value = (smt?"on":"off"); + CurrentMenu = &FO_MENU_SPY; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Spy_Team(float force) = { + if(fo_hud_menu_active && !force) + return; + if(team_no) { + if(number_of_teams < 2 || team_no > number_of_teams) { + return; + } + for(float i = 0; i < 4; i++) { + //if((i + 1) == SBAR.DisguiseTeam || i >= number_of_teams) { + if(i >= number_of_teams) { + FO_MENU_SPY_TEAM.options[i].state = FO_MENU_STATE_DISABLED; + } else { + FO_MENU_SPY_TEAM.options[i].state = FO_MENU_STATE_NORMAL; + } + + } + } + CurrentMenu = &FO_MENU_SPY_TEAM; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Spy_Skin(float force) = { + if(fo_hud_menu_active && CurrentMenu == &FO_MENU_SPY_SKIN && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) { + return; + } + if(!fo_hud_menu_active && CurrentMenu == &FO_MENU_SPY_SKIN && force == 2) { + return; + } + CurrentMenu = &FO_MENU_SPY_SKIN; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Detpack(float force, float cancel_detpack) = { + if(fo_hud_menu_active && (CurrentMenu == &FO_MENU_DETPACK_CANCEL || CurrentMenu == &FO_MENU_DETPACK) && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) + return; + if(cancel_detpack) { + CurrentMenu = &FO_MENU_DETPACK_CANCEL; + } else { + CurrentMenu = &FO_MENU_DETPACK; + } + fo_hud_menu_active = TRUE; +} +void FO_Menu_Build(float force) = { + if(fo_hud_menu_active && (CurrentMenu == &FO_MENU_BUILD_CANCEL || CurrentMenu == &FO_MENU_BUILD) && force != 2) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && force != 1) + return; + if(SBAR.IsBuilding) { + CurrentMenu = &FO_MENU_BUILD_CANCEL; + } else { + CurrentMenu = &FO_MENU_BUILD; + } + fo_hud_menu_active = TRUE; +} +void FO_Menu_Rotate_Sentry(float force) = { + if(fo_hud_menu_active && !force) + return; + if(SBAR.IsBuilding) { + return; + } else { + CurrentMenu = &FO_MENU_SENTRY_ROTATE; + } + fo_hud_menu_active = TRUE; +} +void FO_Menu_Dispenser_Fix(float force, float old_spanner) = { + if(fo_hud_menu_active && !force) + return; + FO_MENU_DISPENSER_MAINTAIN.options[2].state = (old_spanner?FO_MENU_STATE_NORMAL:FO_MENU_STATE_HIDDEN); + CurrentMenu = &FO_MENU_DISPENSER_MAINTAIN; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Dispenser_Use(float force) = { + if(fo_hud_menu_active && !force) + return; + if(SBAR.IsBuilding) { + return; + } else { + CurrentMenu = &FO_MENU_DISPENSER_USE; + } + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_Main(float force) = { + if(fo_hud_menu_active && (CurrentMenu == &FO_MENU_ADMIN_MAIN || CurrentMenu == &FO_MENU_ADMIN_MODES || CurrentMenu == &FO_MENU_ADMIN_SETTINGS)) { + Menu_Cancel(); + return; + } + if(fo_hud_menu_active && !force) + return; + localcmd("cmd adminrefresh\n"); + CurrentMenu = &FO_MENU_ADMIN_MAIN; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_Modes(float force) = { + if(fo_hud_menu_active && !force) + return; + localcmd("cmd adminrefresh\n"); + CurrentMenu = &FO_MENU_ADMIN_MODES; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_Settings(float force) = { + if(fo_hud_menu_active && !force) + return; + localcmd("cmd adminrefresh\n"); + CurrentMenu = &FO_MENU_ADMIN_SETTINGS; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_Rounds(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_ADMIN_ROUNDS; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_QuadTimelimit(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_ADMIN_QUAD_TIMELIMIT; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_PlayToCompletion(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_ADMIN_PLAY_TO_COMPLETION; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_FoMatchRated(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_ADMIN_MATCH_RATED; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_Timelimit(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_ADMIN_TIMELIMIT; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Admin_Fraglimit(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_ADMIN_FRAGLIMIT; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Admin_Players(float force, float type, float page) = { + if(fo_hud_menu_active && !force) + return; + if(page < 0) { + //or(float i = 0; i < 100; i++) { + // if(!getplayerkeyvalue(i,"name")) { + // page = floor(i / 9); + // break; + // } + //} + page = 0; + } + //if(!(getplayerkeyvalue(page * 9,"name"))) page = 0; + if(page < 0) page = 4; + if(page > 4) page = 0; + FO_MENU_ADMIN_PLAYERS.page = page; + player_menu_type = type; + FO_MENU_ADMIN_PLAYERS.update = SUB_Null; + if(type == CLIENT_MENU_ADMIN_KICK) { + FO_MENU_ADMIN_PLAYERS.title = strcat("Kick ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_kick; + } else if(type == CLIENT_MENU_ADMIN_BAN) { + FO_MENU_ADMIN_PLAYERS.title = strcat("Ban ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_ban; + } else if(type == CLIENT_MENU_ADMIN_FORCE_SPEC) { + FO_MENU_ADMIN_PLAYERS.title = strcat("Force Spec ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_force_spec; + } else if(type == CLIENT_MENU_CAPTAIN_1) { + captain1_temp = 0; + captain2_temp = 0; + FO_MENU_ADMIN_PLAYERS.title = strcat("Captain 1 ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_captain1; + } else if(type == CLIENT_MENU_CAPTAIN_2) { + FO_MENU_ADMIN_PLAYERS.title = strcat("Captain 2 ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_captain2; + } else if(type == CLIENT_MENU_CAPTAIN_PICK) { + FO_MENU_ADMIN_PLAYERS.title = strcat("Pick teammate ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_captain_pick; + FO_MENU_ADMIN_PLAYERS.update = action_admin_players_captain_pick; + } else { + FO_MENU_ADMIN_PLAYERS.title = strcat("Page ", ftos(page + 1), "/5"); + execute_admin_players = execute_admin_players_unknown; + } + action_admin_players(type); + CurrentMenu = &FO_MENU_ADMIN_PLAYERS; + fo_hud_menu_active = TRUE; +} +void FO_Menu_Vote(float force) = { + if(fo_hud_menu_active && !force) + return; + CurrentMenu = &FO_MENU_VOTE; + fo_hud_menu_active = TRUE; +} + +void FO_Menu_Special(float force) = { + if(fo_hud_menu_active && force != 1) + return; + + fo_menu* NewMenu = __NULL__; + switch (WP_PlayerClass()) { + case PC_SCOUT: + NewMenu = &FO_MENU_SCOUT; + //nm = FO_Menu_Scout; + break; + case PC_DEMOMAN: + NewMenu = &FO_MENU_DETPACK; + //nm = FO_Menu_Detpack; + break; + case PC_SPY: + NewMenu = &FO_MENU_SPY; + //nm = FO_Menu_Spy; + break; + case PC_ENGINEER: + NewMenu = &FO_MENU_BUILD; + //nm = FO_Menu_Build; + break; + default: + //no class menu + return; + } + + if(fo_hud_menu_active && CurrentMenu == NewMenu && force != 2) { + Menu_Cancel(); + return; + } + switch (WP_PlayerClass()) { + case PC_SCOUT: + FO_Menu_Scout(force, FALSE, FALSE); //TODO: fix scanner settings + break; + case PC_DEMOMAN: + FO_Menu_Detpack(force, FALSE); //TODO: fix cancel + break; + case PC_SPY: + FO_Menu_Spy(force); + break; + case PC_ENGINEER: + FO_Menu_Build(force); + break; + default: + //no class menu + return; + } + localcmd("cmd menu\n"); + //fo_hud_menu_active = TRUE; +}; + +void (float show) showVoteMenu = { + if(fo_hud_menu_active) { + Menu_Cancel(); + } + setcursormode(show); + FO_Hud_SetDisplay(HUDP_MAP_MENU, show); + if(show) { + CurrentMenu = &FO_MENU_VOTE; + } +}; + +#define INIT_MENU_IDS(_target) \ + do { \ + _target.id = ++next_id; \ + for (i = 0; i < _target.options.length; i++) \ + _target.options[i].id = ++next_id; \ + } while (0) + + +// Returns path if it exists, empty string otherwise. +static string InitIfFileExists(string path) { + float filehandle = fopen(path, FILE_READ); + if (filehandle >= 0) { + fclose(filehandle); + return path; + } + return ""; +} + +void FO_Hud_Init() { + float i; + ASSERTD_EQ(Hud_Panels.length, HUDP_LAST - HUDP_FIRST + 1); + + for (i = 0; i < Hud_Panels.length; i++) + ASSERTD_EQ(i + HUDP_FIRST, Hud_Panels[i].id); + float next_id = HUD_LAST + 1; + + /* DrawPanel = *getHudPanel(HUDP_OPTIONS); // XXX */ + INIT_MENU_IDS(FO_MENU_GAME); + INIT_MENU_IDS(FO_MENU_GAME_SPECTATOR); + INIT_MENU_IDS(FO_MENU_SPECTATOR_TRACK); + INIT_MENU_IDS(FO_MENU_TEAM); + INIT_MENU_IDS(FO_MENU_CLASS); + INIT_MENU_IDS(FO_MENU_DROPAMMO); + INIT_MENU_IDS(FO_MENU_SCOUT); + INIT_MENU_IDS(FO_MENU_SPY); + INIT_MENU_IDS(FO_MENU_SPY_TEAM); + INIT_MENU_IDS(FO_MENU_SPY_SKIN); + INIT_MENU_IDS(FO_MENU_DETPACK); + INIT_MENU_IDS(FO_MENU_DETPACK_CANCEL); + INIT_MENU_IDS(FO_MENU_BUILD); + INIT_MENU_IDS(FO_MENU_BUILD_CANCEL); + INIT_MENU_IDS(FO_MENU_SENTRY_MAINTAIN); + INIT_MENU_IDS(FO_MENU_SENTRY_ROTATE); + INIT_MENU_IDS(FO_MENU_ADMIN_MAIN); + INIT_MENU_IDS(FO_MENU_ADMIN_MODES); + INIT_MENU_IDS(FO_MENU_ADMIN_SETTINGS); + INIT_MENU_IDS(FO_MENU_ADMIN_PLAYERS); + INIT_MENU_IDS(FO_MENU_ADMIN_ROUNDS); + INIT_MENU_IDS(FO_MENU_ADMIN_QUAD_TIMELIMIT); + INIT_MENU_IDS(FO_MENU_ADMIN_MATCH_RATED); + INIT_MENU_IDS(FO_MENU_ADMIN_PLAY_TO_COMPLETION); + INIT_MENU_IDS(FO_MENU_ADMIN_TIMELIMIT); + INIT_MENU_IDS(FO_MENU_ADMIN_FRAGLIMIT); + INIT_MENU_IDS(FO_MENU_VOTE); + + FO_ScoreBoardAssets.icon_ready = InitIfFileExists("textures/wad/icon_ready.png"); + FO_ScoreBoardAssets.icon_chat = InitIfFileExists("textures/wad/icon_chat.png"); + FO_ScoreBoardAssets.icon_afk = InitIfFileExists("textures/wad/icon_afk.png"); +} +#undef INIT_MENU_IDS diff --git a/csqc/pmove.qc b/csqc/pmove.qc new file mode 100644 index 000000000..8db34ceca --- /dev/null +++ b/csqc/pmove.qc @@ -0,0 +1,1079 @@ +DEFCVAR_FLOAT(fo_jumpvolume, 1); + +#define STEPTIME 0.125 + +enumflags { + PMF_JUMP_HELD, +}; + +// `seq` is the start of the first frame after which all input_*s are handled. +// Running to `seq` will result in being on frame `seq + 1` in the resultant +// state. In the case that `seq == commandclientframe` this could only be +// partially true since the input_*s (in particular, timelength) are not +// constant for this frame. +static struct PMS_Data { + vector org, vel; + float seq, server_seq; + float interp_t; +} pm_s, pm_so, pm_c; + +static struct { + entity ent; + float last_vel_z; + PMS_Data* active_pmsd; + + float seq, server_seq; + float interp_t; + float last_nudge; + + vector error; + float errortime; + + float step, steptime, step_oldz; + + vector vieworg; +} pm; + +vector PM_Org() { return PM_Enabled() ? pm.ent.origin : pmove_org; } +vector PM_Vel() { return PM_Enabled() ? pm.ent.velocity : pmove_vel; } +inline entity PM_Ent() { return pm.ent; } + +float CSQC_JumpSounds_Active() { + if (PM_Enabled()) + return TRUE; // PM sounds are accurate. + return CVARF(fo_csjumpsounds); +} + +// Sets *type to whatever is at the feet of `point`. +static float PM_GetWaterLevel(vector point, float* type) { + float waterlevel = 0; + float offsets[] = { PLAYER_MINS.z + 1, (PLAYER_MINS.z + PLAYER_MAXS.z) / 2, + DEFAULT_VIEWHEIGHT }; + for (int i = 0; i < 3; i++) { + float cont = pointcontents(point + offsets[i] * '0 0 1'); + if (i == 0) + *type = cont; + if (cont == CONTENT_WATER || cont == CONTENT_SLIME || cont == CONTENT_LAVA) + waterlevel++; + else + break; + } + return waterlevel; +} + +static void PM_Sounds(float is_jumping, float is_jumpframe, + float is_landing, + float last_vel_z) { + float fluidtype; + static float last_waterlevel; // Hacky ... + float waterlevel = PM_GetWaterLevel(PM_Org(), &fluidtype); + + if (is_landing && last_vel_z < -300) { // Hard or water land. + if (fluidtype == CONTENT_WATER) + localsound("player/h2ojump.wav", CHAN_BODY, 1); + else if (last_vel_z < -650) // Will take damage. + localsound("player/land2.wav", CHAN_VOICE, 1); + else + localsound("player/land.wav", CHAN_VOICE, 1); + } + + // TODO: Model health above and not play jump when landing into death. + if (is_jumping) { + static float swimsound_next; + if (waterlevel >= 2) { + if (time > swimsound_next) { + swimsound_next = time + 1; + localsound(random() < 0.5 ? "player/water1.wav" : "player/water2.wav", + CHAN_BODY, 1); + } + } else if (is_jumpframe) { + // The default pmove implementation and server behave slightly + // differently here in two important ways here: + // 1) Jump calculations run _prior_ to movement rather than post, + // meaning you can test for jumping and onground directly. We + // get around this by just using prior frame's onground. + // 2) Something (probably airstep) masks on-grounds that get + // skipped, which otherwise results in false jumps moving up + // stairs when combined with (1). We can test for this by + // watching to see whether we actually got a sufficiently large + // velocity increase. + localsound("player/plyrjmp8.wav", CHAN_AUTO, CVARF(fo_jumpvolume)); + } + } + + if (waterlevel > 0 && last_waterlevel == 0) { // Water entry. + string sound = ""; + switch(fluidtype) { + case CONTENT_LAVA: sound = "player/inlava.wav"; break; + case CONTENT_WATER: sound = "player/inh2o.wav"; break; + case CONTENT_SLIME: sound = "player/slimbrn2.wav"; break; + } + localsound(sound, CHAN_BODY, 1); + } else if (waterlevel == 0 && last_waterlevel > 0) { // Water exit. + localsound("misc/outwater.wav", CHAN_BODY, 1); + } + + last_waterlevel = waterlevel; +} + +// Some versions of FTE do not correctly update pmove_vel (although other state, +// e.g. PM_Org() is). Until we're directly tied to pmove, we snoop on recent +// values when we think this is happening. +float recent_pmove_vel_z; + +void PM_PredictJump_Engine() { + if (!CSQC_JumpSounds_Active() || getstatf(STAT_PAUSED)) + return; + + if (PM_Enabled()) // Handled by PredictJump_Pmove + return; + + static float last_onground, last_vel_z; + + float jumping = input_buttons & BUTTON2; + float onground = pmove_onground; //pmove_onground; + float landing = (onground && !last_onground); + float vel_z = pmove_vel_z ?: recent_pmove_vel_z; + + if (!game_state.is_alive) { + last_onground = 1; + last_vel_z = 0; + return; + } + + float is_jump = last_onground && !onground && vel_z > last_vel_z + 200; + + PM_Sounds(jumping, is_jump, landing, last_vel_z); + + last_vel_z = vel_z; + last_onground = onground; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Pmove +//////////////////////////////////////////////////////////////////////////////// + +enum { + SERVER, + PMOVE, + ERROR_POS, + ERROR_VEL, + ERROR_VIEW, + NUM_DBG_GRAPH_TYPES, +}; + +DEFCVAR_FLOAT(fo_smartjump, 1); + +DEFCVAR_FLOAT(fopm_noerror, 0); +DEFCVAR_FLOAT(fopm_nocache, 0); +DEFCVAR_FLOAT(fopm_nostep, 0); +DEFCVAR_FLOAT(fopm_nonudge, 0); +DEFCVAR_FLOAT(fopm_errortime, 0); +DEFCVAR_FLOAT(fopm_debug, 0); + +DEFCVAR_FLOAT(fopmd_graph_x, -10); +DEFCVAR_FLOAT(fopmd_graph_y, 100); +DEFCVAR_FLOAT(fopmd_graph_w, 200); +DEFCVAR_FLOAT(fopmd_graph_h, 100); + +DEFCVAR_FLOAT(v_viewheight, 0); + +enumflags { + PMDG_ON, // 1 for laziness.. + PMDG_NO_ALIGN_INTERP, // 2 + PMDG_ERROR_VEL, // 4 + PMDG_ERROR_POS, // 8 + PMDG_ERROR_VIEW, // 16 +}; +DEFCVAR_FLOAT(fopmd_graph, 0); + +static const float NHIST = 21; +struct PM_History { + vector org, vel; + float seq; +}; +static PM_History hist[NHIST]; + +void PMH_Log(float seq) { + PM_History* pmh = &hist[seq % NHIST]; + pmh->org = PM_Org(); + pmh->vel = PM_Vel(); + pmh->seq = seq; +} + +PM_History* PMH_Get(float seq) { + PM_History* pmh = &hist[seq % NHIST]; + return pmh->seq == seq ? pmh : 0; +} + +void PM_Init() { + pm.ent = spawn(); + pm.ent.solid = SOLID_NOT; + setsize(pm.ent, '-16 -16 -24', '16 16 32'); +} + +// Should be invoked immediately after RunMovement() to ensure correctness of +// `seq` versus state. On server packets we manually set `seq` to sf + 1. +static void PM_SavePMS(PMS_Data *pmsd) { + entity ent = pm.ent; + pmsd->org = ent.origin; + pmsd->vel = ent.velocity; + + pmsd->seq = pm.seq; + pmsd->server_seq = pm.server_seq; + pmsd->interp_t = pm.interp_t; +} + +static void PM_ActivatePMS(PMS_Data *pmsd) { + entity ent = pm.ent; + ent.origin = pmsd->org; + ent.velocity = pmsd->vel; + + pm.seq = pmsd->seq; + pm.server_seq = pmsd->server_seq; + pm.interp_t = pmsd->interp_t; + pm.active_pmsd = pmsd; + + pm.ent.pmove_flags = 0; // With auto-bunny we only care about clearing +}; + +static float cache_invalidated; +static PMS_Data* SimPMSD() { + return (cache_invalidated || CVARF(fopm_nocache)) ? &pm_s : &pm_c; +} + +void PM_InputFrame(); + +enum { + NT_EMPTY, + NT_CONC, + NT_DASH, + NT_EXPLOSION, + NT_BOUNCE, +}; + +enum { + NA_NONE, + NA_SEQ, + NA_SEQTIME, + NA_ITIME, + NA_ITIME_EXPLICIT_EXPIRE, +}; + +struct PM_Nudge { + float type; + + entity src; + float src_no; + + float nat, seq, itime; + float expire_frame, expire_time, expire_spawnseq; + + vector org; + float aux; +}; + +const static float NUDGE_SEQ = 3; +const static float NUDGE_EPS = NUDGE_SEQ * SERVER_FRAME_DT; +static PM_Nudge nudges[8]; +static float num_nudges; + +static float nudge_expired(PM_Nudge* n) { + if (n->expire_frame && n->expire_frame <= pstate_server.seq) + return TRUE; + + if (n->expire_time && n->expire_time <= pstate_server.server_time) + return TRUE; + + if (n->expire_spawnseq && n->expire_spawnseq != game_state.spawn_gen) + return TRUE; + + switch (n->nat) { + case NA_NONE: return TRUE; + case NA_SEQ: return n->seq + NUDGE_SEQ <= pstate_server.seq; + case NA_SEQTIME: + return n->seq + ceil(n->itime / SERVER_FRAME_DT) + NUDGE_SEQ <= pstate_server.seq; + case NA_ITIME: return n->itime + NUDGE_EPS <= pstate_server.server_time; // Slack + case NA_ITIME_EXPLICIT_EXPIRE: return FALSE; // TRUE handled above + } + + return TRUE; +} + +static PM_Nudge* find_nudge_slot() { + for (float i = 0; i < nudges.length; i++) { + PM_Nudge* n = &nudges[(num_nudges++) % nudges.length]; + + if (!nudge_expired(n)) + continue; + + cache_invalidated = TRUE; + n->nat = NA_NONE; + n->src_no = 0; + n->src = 0; + n->expire_frame = 0; + n->expire_time = 0; + n->expire_spawnseq = 0; + return n; + } + + if (CVARF(fopm_debug)) + printf("ERROR: no nudge slots!\n"); + return 0; +} + +.PM_Nudge* nudge; + +static void PM_RemoveSelfNudges() { + if (!self.nudge) + return; + + PM_Nudge* n = self.nudge; + n->expire_frame = servercommandframe; +} + +void PM_AddNudgeConc(float itime, float mag, float flip) { + // This is kind of painful but not much easier.. + static float filter_itime; + if (itime <= filter_itime) + return; + filter_itime = itime; + + PM_Nudge* n = find_nudge_slot(); + if (!n) + return; + + n->type = NT_CONC; + n->nat = NA_ITIME_EXPLICIT_EXPIRE; + n->expire_time = itime; + n->expire_spawnseq = game_state.spawn_gen; + + n->itime = itime; + n->org.x = mag; + n->org.y = flip; +} + +static void PM_NudgeConc(PM_Nudge* nudge, entity ent) { + if (pointcontents(ent.origin) == CONTENT_WATER) + ent.flags |= FL_INWATER; // pmove doesn't usually maintain this. + Conc_Stumble(ent, nudge->org.x, nudge->org.y); +} + +static void PM_NudgeDash(PM_Nudge* nudge, entity ent) { + ent.velocity = nudge->org; +} + +void PM_AddNudgeDash(float cseq) { + PM_Nudge* n = find_nudge_slot(); + if (!n) + return; + n->type = NT_DASH; + n->nat = NA_SEQ; + n->seq = cseq; + + n->org = v_forward * 540; + n->org.z = 181; +} + +float nudge_i(PM_Nudge* n) { + for (float i = 0; i < nudges.length; i++) + if (&nudges[i] == n) + return i; + return -1; +} + +static void PM_NudgeExplosion(PM_Nudge* nudge, entity ent) { + float dmg = nudge->aux; + if (vlen(ent.origin - nudge->org) > dmg + 40) + return; + + const float MOVE_WORLDONLY = 3; + traceline(ent.origin, nudge->org, MOVE_WORLDONLY, pm.ent); + if (trace_fraction < 1) // No knock from occluded explosions + return; + + float flg = KF_BOTH_PLAYER; // Assumes everything is from a player for now + if (nudge->src.owner == pengine.player_ent) + flg |= KF_SELF; + + dmg = CalcRadiusDamage(nudge->org, ent, flg, dmg); + if (flg & KF_SRC_PLAYER) + dmg *= 0.9; + dmg = Class_ScaleMoment(WP_PlayerClass(), dmg); + vector knock = CalcKnock(nudge->org, ent, flg, dmg); + + pm.ent.velocity += knock; +} + +// Can be either by {seq + itime} for predicted projectiles, or by {itime} for +// server projectiles. +static PM_Nudge* PM_AddNudgeExplosion(float nat, float seq, float itime, entity ent, float dmg) { + if (vlen(PM_Org() - ent.origin) > dmg * 3) // Fine tune this.. + return 0; + + PM_Nudge* n = find_nudge_slot(); + if (!n) + return 0; + + n->type = NT_EXPLOSION; + n->nat = nat; + n->seq = seq; + n->itime = itime; + + // This is slightly imprecise, since you could throw a grenade then spawn + // and jump on it, but it's more right than wrong; and the wrong cases lead + // to false lerps. + if (ent.owner == pengine.player_ent) + n->expire_spawnseq = game_state.spawn_gen; + + n->org = ent.origin; // We end up mostly using the ent here + n->aux = dmg; + + n->src = ent; + n->src_no = ent.entnum; + + ent.nudge = n; + ent.removefunc = PM_RemoveSelfNudges; + + return n; +} + +static void PM_AddNudgeBounce(float itime, entity ent, float dmg) { + if (vlen(PM_Org() - ent.origin) > dmg * 3) // Fine tune this.. + return; + + PM_Nudge* n = find_nudge_slot(); + if (!n) + return; + + n->type = NT_BOUNCE; + n->nat = NA_ITIME; + n->itime = itime; + + n->org = ent.origin; + n->aux = dmg; + + n->src = ent; + n->src_no = ent.entnum; + + ent.nudge = n; + ent.removefunc = PM_RemoveSelfNudges; +} + +static void PM_NudgeBounce(PM_Nudge* nudge, entity ent) { + float dmg = nudge->aux; + + if (vlen(ent.origin - nudge->org) > dmg + 40) + return; + + vector targ_org = ent.origin + (ent.mins + ent.maxs) * 0.5; + float points = dmg - vlen(targ_org - nudge->org) / 2; + + ent.velocity = (targ_org - nudge->org) * points/20; + + NB_ConcCapAction(ent, pstate_pred.playerclass, &pstate_pred.tfstate, + pm.interp_t, &pstate_pred.conc_cap_time, kLaunch); +} + + +void PM_AddGrenadeExplosion(float itime, entity ent) { + if (!PM_Enabled()) + return; + + FO_GrenExp exp; + if (!FO_GrenGetExp(ent.fpp.gren_type, exp)) + return; + + float dmg = exp.dmg; + + PM_Nudge* n; + if (exp.type == kRadiusDamage) + PM_AddNudgeExplosion(NA_ITIME, 0, itime, ent, dmg); + else + PM_AddNudgeBounce(itime, ent, dmg); +} + +void PM_AddSimExplosion(float itime, entity ent) { + if (!PM_Enabled()) + return; + + if ((CVARF(fo_beta_nudge_explosion) & 2 == 0) && + ent.owner != pengine.player_ent) + return; + + float dmg = 0; + switch (ent.fpp.index) { + case FPP_ROCKET: + case FPP_INCENDIARY: + dmg = 92; + break; + default: + return; // Not recognized + } + + float seq = 0, nat = NA_ITIME; + if (ent.forward_knock && ent.owner == pengine.player_ent) { + if (ent.forward_knock == -1) + return; + nat = NA_ITIME_EXPLICIT_EXPIRE; // Knock forwards are synchronized + itime = ent.forward_knock; + } else if (ent.created_seq && ent.entnum == 0) { + float dtime = 0; + if (ent.s_time) + dtime = max(itime - ent.s_time, 0); + seq = ent.created_seq; + itime = dtime; + nat = NA_SEQTIME; // Same as NA_SEQ when itime == 0 + } + + PM_AddNudgeExplosion(nat, seq, itime, ent, dmg); +} + +float MatchNudge(PM_Nudge* n, float seq, float sitime, float eitime, float slack = 0) { + if (nudge_expired(n)) + return FALSE; + + switch (n->nat) { + case NA_SEQ: return seq == n->seq; + case NA_SEQTIME: return seq == n->seq + floor(n->itime / SERVER_FRAME_DT); + case NA_ITIME_EXPLICIT_EXPIRE: return n->itime > sitime && n->itime <= eitime; + case NA_ITIME: return (n->itime > sitime - slack) && (n->itime <= eitime); + default: + return FALSE; + } +} + +// TODO: This obviously wants to be smarter +static void PM_ApplyNudges(entity ent, float seq, float sitime, float eitime, float slack = 0) { + for (float i = 0; i < nudges.length; i++) { + PM_Nudge* n = &nudges[i]; + + if (!MatchNudge(n, seq, sitime, eitime, slack)) + continue; + + pm.last_nudge = seq; + switch (n->type) { + case NT_CONC: + PM_NudgeConc(n, ent); + break; + case NT_DASH: + PM_NudgeDash(n, ent); + break; + case NT_EXPLOSION: + PM_NudgeExplosion(n, ent); + break; + case NT_BOUNCE: + PM_NudgeBounce(n, ent); + break; + } + } +} + +static void RunPlayerPhysics() { + entity ent = pm.ent; + + pm.last_vel_z = ent.velocity_z; + runstandardplayerphysics(ent); +} + +static void RunDeadMovement(float endframe) { + entity ent = pm.ent; + float held_movetype = ent.movetype; + float held_solid = ent.solid; + float held_buttons = input_buttons; + vector held_movevalues = input_movevalues; + + ent.movetype = MOVETYPE_TOSS; + ent.solid = SOLID_NOT; + input_buttons = 0; + input_movevalues = '0 0 0'; + while (pm.seq <= endframe) { + runstandardplayerphysics(ent); + pm.seq++; + } + + ent.movetype = held_movetype; + ent.solid = held_solid; + input_buttons = held_buttons; + input_movevalues = held_movevalues; +} + +// End state: +// pmsd.seq --> endframe + 1 +static void PM_RunMovement(PMS_Data* pmsd, float endframe) { + entity ent = pm.ent; + + if (pm.active_pmsd != pmsd || pm.seq > endframe) { + ASSERTF_GT(endframe, pm.server_seq); + PM_ActivatePMS(pmsd); + } + + if (!game_state.is_spectator && !game_state.is_alive) { + RunDeadMovement(endframe); + return; + } + + ASSERTF_GT(pm.seq, 0); + + pm.ent.pmove_flags = 0; // With auto-bunny we only care about clearing + while (pm.seq <= endframe) { + if (!getinputstate(pm.seq)) + break; + + // We have to apply this on the leading edge since INPUT_FRAME + // modifications do not occur until the frame is finalized. + if (pm.seq == clientcommandframe) + PM_InputFrame(); + + RunPlayerPhysics(); + + if (!CVARF(fopm_nonudge)) { + float slack = (pm.seq == pm.server_seq + 1) ? NUDGE_EPS : 0; + PM_ApplyNudges(ent, pm.seq, pm.interp_t, pm.interp_t + input_timelength, slack); + + if ((ent.pmove_flags & PMF_JUMP_HELD) && (pstate_pred.tfstate & TFSTATE_CONC)) + Conc_Jump(&pstate_pred.conc_state, ent); + + if ((ent.pmove_flags & PMF_JUMP_HELD) && (pstate_pred.tfstate & TFSTATE_CONC_CAP)) { + NB_ConcCapAction(ent, pstate_pred.playerclass, &pstate_pred.tfstate, + pm.interp_t, &pstate_pred.conc_cap_time, + kLand); + } + } + + pm.seq++; + pm.interp_t += input_timelength; + } + + // Add in anything that was applied after (for low packet rate protocols) + input_angles = view_angles; +} + +static void PM_SetEnabled(float enabled) { + pengine.pm_enabled = enabled; + + PM_Refresh(); + + if (!enabled) + return; + + pm.errortime = pm.steptime = 0; + pm.step_oldz = pm.ent.origin_z; +} + +static float ErrorTime() { + static float kDefault = 25 * MSEC; + static float kMax = 50 * MSEC; + + if (!CVARF(fopm_errortime)) + return kDefault; + + return min(CVARF(fopm_errortime), kMax); +} + +static void PM_UpdateError() { + entity ent = pm.ent; + + ent.owner = edict_num(player_localentnum); + if (CVARF(fopm_noerror) || !game_state.is_alive || + (clientcommandframe == servercommandframe + 1)) { + pm.error = '0 0 0'; + pm.errortime = 0; + return; + } + + static PMS_Data last; + if (last.server_seq != pm_s.server_seq - 1) + last = pm_so; + + // Run prior prediction to present. + PM_RunMovement(&last, clientcommandframe); + vector err = ent.origin; + + // Repeat with updated state. + PM_RunMovement(SimPMSD(), clientcommandframe - 2); + PM_SavePMS(&last); // Save at -2 to replay nudges going into -1 + PM_RunMovement(SimPMSD(), clientcommandframe); + + // New nudges can result in small errors on replay due to timeshift, we + // could backdate all the nudges and recompute the world but as a first + // simpler pass let's just filter them out. + if (pm.last_nudge == clientcommandframe - 1) + err = '0 0 0'; + else + err -= ent.origin; + + float nerr = vlen(err); + if (nerr > 64) { // teleport + pm.error = '0 0 0'; + pm.errortime = 0; + } else { // figure out the error amount, and add it to accumulated lerp + pm.error *= max(pm.errortime - time, 0) / ErrorTime(); + pm.error += err; + + if (vlen(pm.error) > 1) { + pm.errortime = time + ErrorTime(); + } else { + pm.error = '0 0 0'; + pm.errortime = 0; + } + } +} + +DEFCVAR_FLOAT(v_deathtilt, 1); + +static void PM_UpdateLocalMovement() { + entity ent = pm.ent; + + if (game_state.is_ceasefire || clientcommandframe > servercommandframe + 63) { + PM_ActivatePMS(&pm_s); + } else { + PM_RunMovement(SimPMSD(), clientcommandframe); + } + + vector org = pm.ent.origin; + + // Smooth stair stepping + if (org_z > pm.step_oldz + 8 && org_z < pm.step_oldz + 24 && + ent.velocity_z == 0) { // Evaluate out the remaining old step + if (pm.steptime - time > 0) + pm.step = (pm.steptime - time) * (1 / STEPTIME) * pm.step; + else + pm.step = 0; + + // Work out the new step + pm.step += (pm.step_oldz - org_z); + pm.steptime = time + STEPTIME; + } + pm.step_oldz = org_z; + + float viewheight = CVARF(v_viewheight); + if (viewheight < -7) + viewheight = -7; + else if (viewheight > 7) + viewheight = 7; + + pm.vieworg = org; + pm.vieworg.z += getstatf(STAT_VIEWHEIGHT) + viewheight; + + if (!game_state.is_alive && WP_PlayerClass()) + view_angles.z = CVARF(v_deathtilt) * 80; + + // Correct view position over ErrorTime() + if (pm.errortime - time > 0) + pm.vieworg += (pm.errortime - time) * (1 / ErrorTime()) * pm.error; + + if (!CVARF(fopm_nostep)) + if (pm.steptime - time > 0) + pm.vieworg.z += (pm.steptime - time) * (1 / STEPTIME) * pm.step; +} + +void PMD_UpdateImpulse(int seq); + +void PM_SyncTo(float seq) { + if (!PM_Enabled()) + return; + + if (!game_state.is_alive) + return; + + entity ent = pm.ent; + PM_RunMovement(SimPMSD(), seq); + + // Note: ~FL_ONGROUND and jump occur in same frame, produces JUMP_HELD + float jumping = input_buttons & BUTTON2; + float landing = pm.last_vel_z < 0 && (ent.flags & FL_ONGROUND); + float jump_frame = ent.pmove_flags & PMF_JUMP_HELD; + + PM_Sounds(jumping, jump_frame, landing, pm.last_vel_z); +} + +static void PM_HandleRemovedNudges() { + if (!pstate_server.num_filter_ents) + return; + + float c = 0; + for (float i = 0; i < nudges.length; i++) { + PM_Nudge* n = &nudges[i]; + if (nudge_expired(n)) + continue; + + if (n->type == NT_CONC && n->itime < pstate_server.conc_state.next) { + n->expire_frame = -1; + continue; + } + + for (float j = 0; j < pstate_server.num_filter_ents; j++) { + if (n->src_no == pstate_server.filter_ents[j]) { + n->nat = NA_SEQ; + n->seq = n->expire_frame = pstate_server.seq; + } + } + } +} + +void PM_Update(float sendflags) { + float was_enabled = PM_Enabled(); + float enabled = (pstate_server.predict_flags & PF_PMOVE) && + game_state.is_player; + + if (isdemo()) + enabled = 0; + + if (enabled != was_enabled || + game_state.is_player != prev_game_state.is_player) + PM_SetEnabled(enabled); + + if (sendflags & FOWP_PMOVE == 0) + return; + + PM_HandleRemovedNudges(); + + pm_so = pm_s; + pm.server_seq = servercommandframe; + pm.seq = servercommandframe + 1; // server state includes move + pm.interp_t = pstate_server.server_time; + PM_SavePMS(&pm_s); + + if (!CVARF(fopm_nocache)) { + // clientcommandframe) so that we can accelerate the common case of + // computation at clientcommandframe (which does require constant + // re-evaluation). In the case there's no separation (e.g. lan pings) + // then the server frame is directly used as the cache frame. + if (clientcommandframe > servercommandframe + 1) + PM_RunMovement(&pm_s, clientcommandframe - 1); + PM_SavePMS(&pm_c); + cache_invalidated = FALSE; + } + + if (enabled && was_enabled) + PM_UpdateError(); + PMD_UpdateImpulse(servercommandframe); +} + +void PM_Refresh() { + if (!PM_Enabled()) + return; + + entity ent = pm.ent; + if (game_state.is_player) { + ent.owner = edict_num(player_localentnum); + ent.movetype = MOVETYPE_WALK; + ent.solid = SOLID_SLIDEBOX; + } else { + ent.owner = world; // Needs more than this for spec.. + ent.movetype = MOVETYPE_NOCLIP; + ent.solid = SOLID_NOT; + } +} + +DEFCVAR_FLOAT(cl_upspeed, 400); +void PM_InputFrame() { + float iz = input_movevalues.z; + input_movevalues.z = 0; + + float max_speed = isdemo() ? SPEC_MAXSPEED : pstate_pred.csqc_maxspeed; + +#if 0 // Quick fix + // cl_smartjump replacement + float type; + if (input_buttons & BUTTON2) { + if ((game_state.is_player && CVARF(fo_smartjump) && + (PM_GetWaterLevel(PM_Org(), &type) >= 2)) + || game_state.is_spectator) { + input_buttons &= ~BUTTON2; + iz = min(CVARF(cl_upspeed), Class_MaxSpeed(WP_PlayerClass())); + } + } +#endif + if (input_buttons & BUTTON2) { + if (game_state.is_spectator) { + input_buttons &= ~BUTTON2; + iz = CVARF(cl_upspeed); + } + } + + // Technically the sync with pstate_pred being on the right frame is loose.. + if (pstate_pred.tfstate & TFSTATE_AIMING) + max_speed = 80; + + if (vlen(input_movevalues) > max_speed) + input_movevalues = normalize(input_movevalues) * max_speed; + if (iz) + input_movevalues.z = min(max_speed, input_movevalues.z + iz); +} + +//////////////////////////////////////////////////////////////////////////////// +// View/Render +//////////////////////////////////////////////////////////////////////////////// + +DEFCVAR_STRING(r_brightlight_colour, "2.0 1.0 0.5 400"); +DEFCVAR_STRING(r_dimlight_colour, "2.0 1.0 0.5 200"); +DEFCVAR_STRING(r_redlight_colour, "3.0 0.5 0.5 200"); +DEFCVAR_STRING(r_bluelight_colour, "0.5 0.5 3.0 200"); +veci brightlight_l, dimlight_l, redlight_l, bluelight_l; + +static void ParseCvars() { + static float next_refresh; + + if (time < next_refresh) + return; + next_refresh = time + 0.5; + + cvar_parse4(CVARS(r_brightlight_colour), brightlight_l); + cvar_parse4(CVARS(r_dimlight_colour), dimlight_l); + cvar_parse4(CVARS(r_redlight_colour), redlight_l); + cvar_parse4(CVARS(r_bluelight_colour), bluelight_l); +} + +#define MUX_IN(ef, bit, src) \ + do { if ((ef) & (bit)) { rad = max(rad, src.i); col += src.v; } } while (0) + +void LocalEffects(float effects, vector org) { + float rad = 0; + vector col = '0 0 0'; + + const float MASK_ALL = EF_BRIGHTLIGHT | EF_DIMLIGHT | EF_RED | EF_BLUE; + + if (effects & MASK_ALL == 0) + return; + + ParseCvars(); + + MUX_IN(effects, EF_BRIGHTLIGHT, brightlight_l); + MUX_IN(effects, EF_DIMLIGHT, dimlight_l); + MUX_IN(effects, EF_RED, redlight_l); + MUX_IN(effects, EF_BLUE, bluelight_l); + + rad = min(rad, 400); + dynamiclight_add(org, rad, col, 0, "", 0); +} +#undef MUX_IN + +void PM_UpdateView() { + PM_UpdateLocalMovement(); + setviewprop(VF_ORIGIN, pm.vieworg); + setviewprop(VF_ANGLES, view_angles); + + makevectors(view_angles); + SetListener(pm.vieworg, v_forward, v_right, v_up); + + LocalEffects(pstate_server.effects, pm.vieworg); +} + +//////////////////////////////////////////////////////////////////////////////// +// Debug +//////////////////////////////////////////////////////////////////////////////// + +static const float NIMP = 300; +struct PmoveDebug { + float points[NIMP]; + int nimp; +}; + +static PmoveDebug pmd[NUM_DBG_GRAPH_TYPES]; + +static void PMD_AddPoint(int ptype, float val) { + PmoveDebug* pd = &pmd[ptype]; + + pd->points[pd->nimp++ % NIMP] = val; +} + +static void PMD_UpdateImpulse(int seq) { + if (!CVARF(fopmd_graph)) + return; + + static int old_seq; + if (old_seq == seq) + return; + old_seq = seq; + + float cseq = clientcommandframe; + if (PM_Enabled()) + PM_RunMovement(SimPMSD(), cseq); + + PMH_Log(cseq); + + vector sv = pm_s.vel; + vector pv = PM_Vel(); + + if (!vlen(pv) && !vlen(sv)) + return; + + PMD_AddPoint(SERVER, vlen(sv)); + PMD_AddPoint(PMOVE, vlen(pv)); + + PMD_AddPoint(ERROR_VIEW, vlen(pm.error) / 128 * 1200); + + PM_History* hist = PMH_Get(servercommandframe); + if (!hist) + return; + + PMD_AddPoint(ERROR_POS, hist ? vlen(pm_s.org - hist.org) : 0); + PMD_AddPoint(ERROR_VEL, hist ? vlen(pm_s.vel - hist.vel) : 0); +} + +static void PMD_Graph(int type, int offset, vector c1, vector c2, vector rgb) { + PmoveDebug* pd = &pmd[type]; + float wx = c2.x - c1.x, wy = c2.y - c1.y; + + float maxv = pd->points[0], minv = pd->points[0]; + for (int i = 1; i < NIMP; i++) { + maxv = max(maxv, pd->points[i]); + minv = min(minv, pd->points[i]); + } + + float h = max(maxv - minv, 1200); + + float px = wx / NIMP, py = (wy - 2) / h; + vector lend = c1 + [0, wy, 0]; + + vector m1 = [0, wy - maxv * py, 0]; + drawline(1, c1 + m1, c1 + m1 + [wx, 0, 0], '50 150 150', 1); + + for (int i = offset; i < NIMP; i++) { + int idx = (pd->nimp + i) % NIMP; + vector sx = + [c1.x + (i - offset) * px, c2.y - (pd->points[idx]) * py + 1, 0]; + vector sy = [sx.x + px, sx.y, 0]; + + drawline(1, sx, sy, rgb, 1); + drawline(1, lend, sx, rgb, 1); + lend = sy; + } +} + +void PMD_DrawGraphs(float width) { + float pmove_on = pstate_server.predict_flags & PF_PMOVE; + if (pmove_on) + drawstring([width - 100, 20, 0], sprintf("PM err: %0.1f", vlen(pm.error)), + '8 8 0', '1 1 1', 1, 0); + + if (!CVARF(fopmd_graph)) + return; + + vector c1 = [CVARF(fopmd_graph_x), CVARF(fopmd_graph_y), 0]; + if (c1.x < 0) + c1.x += width - CVARF(fopmd_graph_w); + vector w = [CVARF(fopmd_graph_w), CVARF(fopmd_graph_h), 0]; + vector c2 = c1 + w; + + drawfill(c1, w, '0.2 0.2 0.2', 1); + drawfill(c2, '5 5 5', '1 0 0', 1); + + float offset = (CVARF(fopmd_graph) & PMDG_NO_ALIGN_INTERP) ? 0 : + clientcommandframe - servercommandframe - 1; + + offset = max(0, offset); + PMD_Graph(PMOVE, 0, c1, c2, '0 0 1'); + PMD_Graph(SERVER, offset, c1, c2, '0 1 0'); + + if ((CVARF(fopmd_graph) & PMDG_ERROR_VIEW) && + (pstate_server.predict_flags & PF_PMOVE)) + PMD_Graph(ERROR_VIEW, 0, c1, c2, '1 0 1'); + if ((CVARF(fopmd_graph) & PMDG_ERROR_VEL)) + PMD_Graph(ERROR_VEL, 0, c1, c2, '1 1 0'); + if ((CVARF(fopmd_graph) & PMDG_ERROR_POS)) + PMD_Graph(ERROR_POS, 0, c1, c2, '1 0 0'); + +} diff --git a/csqc/profile.qc b/csqc/profile.qc new file mode 100644 index 000000000..b328cd340 --- /dev/null +++ b/csqc/profile.qc @@ -0,0 +1,112 @@ +DEFCVAR_FLOAT(fo_enable_profiling, 0); + +inline float sq(float x) { return x * x; } + +struct perf_samples { + float* values; + int max_count; + int count; +}; + +float perf_add_sample(perf_samples* ps, float v) { + float idx = ps->count++ % ps->max_count; + float old = ps->values[idx]; + ps->values[idx] = v; + return old; +} + +struct moving_avg { + perf_samples samples; + float s1, s2; +}; + +void update_online_avg(moving_avg* avg, float newv) { + float oldv = perf_add_sample(&avg->samples, newv); + + avg->s1 += newv - oldv; + avg->s2 += newv*newv - oldv*oldv; +} + +float read_online_avg(struct moving_avg* avg, float* mean, float* variance) { + float n = max(min(avg->samples.count, 2), avg->samples.max_count); + + *mean = avg->s1 / n; + *variance = (avg->s2 - sq(avg->s1) / n) / (n - 1); + return n; +} + +void compute_avg(struct perf_samples* samples, float* mean, float* variance) { + if (samples->count < 2) { + *mean = samples->values[0]; + *variance = 0; + return; + } + + float K = samples->values[0]; + float i, n, s1, s2; + s1 = s2 = 0; + n = min(samples->count, samples->max_count); + for (i = 0; i < n; i++) { + s1 += samples->values[i] - K; + s2 += sq(samples->values[i] - K); + } + + *mean = s1/n + K; + *variance = (s2 - sq(s1) / n) / (n - 1); +} + +void compute_maxmin(struct perf_samples* samples, float* minv, float* maxv) { + *minv = *maxv = 0; + + if (samples->count > 0) + *minv = *maxv = samples->values[0]; + + for (float i = 1; i < min(samples->max_count, samples->count); i++) { + *minv = min(*minv, samples->values[i]); + *maxv = max(*maxv, samples->values[i]); + } +} + +// Must orchestrate calling INIT before use. +#define DECLARE_MOVING_AVG(_v, _c) \ + var float _v##_samples[_c]; \ + var moving_avg _v + +#define INIT_PERF_SAMPLES(_ps, _backing) \ + do { \ + (_ps)->values = _backing; (_ps)->max_count = (_backing).length; (_ps)->count = 0; \ + } while (0) + +#define INIT_MOVING_AVG(_v) INIT_PERF_SAMPLES(_v.samples, _v##_samples) + + +struct perf_sampler { + float interval; + perf_samples samples; + + float next_sample; // In game time, we want no exits. + float sample_start; // External [e.g. gettime(1)] based +}; + +float perf_start_sample(perf_sampler* ps, float force = FALSE) { + if (!CVARF(fo_enable_profiling)) + return FALSE; + + if (time < ps->next_sample && !force) + return FALSE; + ps->sample_start = gettime(1); // cltime + return TRUE; // Might return a (1-based) index at some point +} + +void perf_finish_sample(perf_sampler* ps, float idx) { + if (idx == FALSE) + return; // Not sampled. + ps->next_sample = cltime + (random() + random()) * ps->interval; + perf_add_sample(&ps->samples, gettime(1) - ps->sample_start); +} + +#define DECLARE_PERF_SAMPLER(_v, _c, _i) \ + var float _v##_samples[_c]; \ + var perf_sampler _v = { _i } + +#define INIT_PERF_SAMPLER(_v) INIT_PERF_SAMPLES(_v.samples, _v##_samples) diff --git a/csqc/settings.qc b/csqc/settings.qc new file mode 100644 index 000000000..f52544d4f --- /dev/null +++ b/csqc/settings.qc @@ -0,0 +1,38 @@ +DEFCVAR_FLOAT(fo_grentimer, 2); // Sound + Ping adjust +DEFCVAR_STRING(fo_grentimersound, "grentimer.wav"); +DEFCVAR_FLOAT(fo_grentimervolume, 1); + +// When set (not -1) and spectating, supercedes fo_grentimervolume. +DEFCVAR_FLOAT(fo_spec_grentimervolume, -1); + +DEFCVAR_FLOAT(fo_oldscoreboard, 0); +DEFCVAR_FLOAT(fo_default_weapon, 0); +DEFCVAR_FLOAT(fo_team_color_crosshair, 0); + +DEFCVAR_FLOAT(fo_hitaudio_enabled, 1); +DEFCVAR_FLOAT(fo_hitaudio_hurtself, 1); +DEFCVAR_FLOAT(fo_hitaudio_hurtteam, 1); +DEFCVAR_FLOAT(fo_hitaudio_hurtenemy, 1); +DEFCVAR_FLOAT(fo_hitaudio_killself, 1); +DEFCVAR_FLOAT(fo_hitaudio_killteam, 1); +DEFCVAR_FLOAT(fo_hitaudio_killenemy, 1); +DEFCVAR_FLOAT(fo_hitaudio_noarmour, 1); + +DEFCVAR_FLOAT(fo_hittext_enabled, 1); +DEFCVAR_FLOAT(fo_hittext_size, 16); +DEFCVAR_FLOAT(fo_hittext_speed, 96); +DEFCVAR_FLOAT(fo_hittext_duration, 2); +DEFCVAR_FLOAT(fo_hittext_alpha, 1); +DEFCVAR_FLOAT(fo_hittext_rawdamage, 1); +DEFCVAR_FLOAT(fo_hittext_noarmour, 1); +DEFCVAR_FLOAT(fo_hittext_offset, 32); +DEFCVAR_FLOAT(fo_hittext_friendly, 0); +DEFCVAR_STRING(fo_hittext_colour, "1 1 1"); +DEFCVAR_STRING(fo_hittext_colour2, "1 0 1"); +DEFCVAR_STRING(fo_hittext_colour3, "1 0 0"); + +DEFCVAR_FLOAT(fo_hud_idle_alpha, 0.3); +DEFCVAR_FLOAT(fo_adminrefresh, 2); + +static float __unused; // Needed to avoid a spurious compiler error. + diff --git a/csqc/status.qc b/csqc/status.qc new file mode 100644 index 000000000..00b40211b --- /dev/null +++ b/csqc/status.qc @@ -0,0 +1,1237 @@ +float GetTextAlignOffset(float pos, float width, float iconsize, string text, float textsize, float align); +float fo_to_sui_aligntment(float fo_align); +vector (FO_Hud_Panel* panel) getPanelPosition; +vector (FO_Hud_Panel* panel) getPanelFillSize; +void Hud_DrawPanelLMP(FO_Hud_Panel*, string, string, float); +void Hud_DrawLMPThreshold(FO_Hud_Panel*, string, float); +FO_Hud_Panel* getHudPanel(PanelID); +void FO_Hud_HidePanel(PanelID id); +void FO_Hud_ShowPanel(PanelID id); + +DEFCVAR_FLOAT(fo_hud_noclipicon, 0); + +void(PanelID ignored, string text) drawClipSize = { + string icon = CVARF(fo_hud_noclipicon) ? "" : ICON_CLIPSIZE; + if (game_state.is_alive || fo_hud_editor) + Hud_DrawPanelLMP(getHudPanel(HUDP_CLIPSIZE), text, icon, 1); +}; + +void(PanelID ignored, string text) drawIdentify = { + if (time > last_id_time + 5) + return; + + if (strlen(text) > 0 || fo_hud_editor) + Hud_DrawIdentifyPanel(text); +}; + +void(PanelID ignored, string text) drawTeamScorePanel = { + Hud_DrawTeamScorePanel(); +}; + +void(PanelID ignored, string text) drawMapMenuPanel = { + Hud_DrawMapMenuPanel(); +}; + +void(PanelID ignored, string text) drawFlagInfo = { + Hud_DrawFlagStatusBar(); +}; + +void(PanelID panelid, string text, string icon) drawIconPanel = { + Hud_DrawPanelLMP(getHudPanel(panelid), text, icon, 1); +}; + +void(PanelID panelid, string text) drawFragStreakPanel = { + drawIconPanel(HUDP_FRAGSTREAK, text, ICON_FRAGSTREAK); +}; + +void(PanelID ignored, string text) drawCapsPanel = { + drawIconPanel(HUDP_CAPS, text, ICON_CAPS); +}; + +void(PanelID panelid, string text) drawGren1Panel = { + float playerclass = WP_PlayerClass(); + + if (playerclass < PC_SCOUT || playerclass > PC_ENGINEER) + return; + + string icon = FO_ClassGren(playerclass, 0)->icon; + drawIconPanel(HUDP_GREN1, text, icon); +}; + +void(PanelID ignored, string text) drawGren2Panel = { + float playerclass = WP_PlayerClass(); + + if (playerclass < PC_SCOUT || playerclass > PC_ENGINEER) + return; + + string icon = FO_ClassGren(playerclass, 1)->icon; + drawIconPanel(HUDP_GREN2, text, icon); +}; + +void(PanelID ignored, string text) drawSpecial = { + if (WP_PlayerClass() || fo_hud_editor) + Hud_DrawClassInfoPanel(WP_PlayerClass()); +}; + +void(PanelID panelid, string text) drawGrenTimerPanel = { + float timeleft; + float timercount = 0; + + FO_Hud_Panel* panel = getHudPanel(panelid); + float Scale = panel.Scale; + float TextScale = panel.TextScale; + FO_Hud_Panel panel2; + panel2.Position = panel.Position; + panel2.Scale = panel.Scale; + panel2.TextScale = panel.TextScale; + panel2.Display = panel.Display; + panel2.FillSize = panel.FillSize; + + CsGrenTimer lt = CsGrenTimer::GetLast(); + // We want to render in reverse mod order from the last primed grenade + // here, that way we'll properly stack timers relative to expiry. + for (float i = grentimers.length; i > 0; i--) { + CsGrenTimer gt = grentimers[(lt.index() + i) % grentimers.length]; + + if (!gt.active()) + continue; + + timeleft = floor(gt.expiry() - time) + 1; + if (timeleft < 1) + continue; + + if (timercount) { + panel2.Scale = Scale * 0.8; + panel2.TextScale = TextScale * 0.8; + } + + // It's technically possible to get a primed message before thrown + // if things get reordered, but since we can only ever have one + // grenade primed, we can obscure this by always considering + // grenades after the first as primed. + float alpha = (gt.test_flag(FL_GT_THROWN) || timercount) ? 0.3 : 1.0; + string icon = FO_GrenDesc(gt.grentype())->icon; + Hud_DrawPanelLMP(&panel2, ftos(timeleft), icon, alpha); + panel2.Position = panel2.Position + + [0, panel2.FillSize.y * panel2.Scale]; + timercount++; + } + + if(fo_hud_editor && !timercount) + Hud_DrawPanelLMP(getHudPanel(panelid), text, ICON_GREN_NORMAL, 1); +}; + +void(PanelID panelid, string text) drawFacePanel = { + if (!CVARF(fo_fte_hud)) + return; + + local float playerclass = WP_PlayerClass(); //we could add different faces per class? + + local string icon = FaceInvisibleInvulnerableIcon.icon; + + if(playerclass) { + local float health = getstatf(STAT_HEALTH); + if(health > 0) { + local float items = getstatf(STAT_ITEMS); + //we should add all the combos :) + if(items & IT_INVULNERABILITY && items & IT_INVISIBILITY) icon = FaceInvisibleInvulnerableIcon.icon; + else if(items & IT_INVULNERABILITY) icon = FaceInvulnerableIcon.icon; + else if(items & IT_INVISIBILITY) icon = FaceInvisibleIcon.icon; + else if(items & IT_QUAD) icon = FaceQuadIcon.icon; + else { + local float index = 0; + if(health >= 80) index = 0; + else if(health >= 60) index = 1; + else if(health >= 40) index = 2; + else if(health >= 20) index = 3; + else index = 4; + + if(time < painfinished) { + index += 5; + } + + icon = FaceIcons[index].icon; + } + } else { + icon = FaceIcons[4].icon; + } + } + + drawIconPanel(panelid, text, icon); +}; + +void(PanelID panelid, string text) drawArmourPanel = { + if(!CVARF(fo_fte_hud)) + return; + + local float playerclass = WP_PlayerClass(); + + local string icon = fo_hud_editor?ArmourIcons[1].icon:""; + + if(playerclass) { + local float items = getstatf(STAT_ITEMS); + if(items & IT_INVULNERABILITY) icon = ArmourIcons[0].icon; + else if(items & IT_ARMOR1) icon = ArmourIcons[1].icon; + else if(items & IT_ARMOR2) icon = ArmourIcons[2].icon; + else if(items & IT_ARMOR3) icon = ArmourIcons[3].icon; + } + + if(icon != "") drawIconPanel(panelid, text, icon); +}; + +void(PanelID panelid, string text) drawAmmoPanel = { + if(!CVARF(fo_fte_hud)) + return; + + local string icon = fo_hud_editor?AmmoIcons[0].icon:""; + + local float items = getstatf(STAT_ITEMS); + if(items & IT_SHELLS) icon = AmmoIcons[0].icon; + else if(items & IT_NAILS) icon = AmmoIcons[1].icon; + else if(items & IT_ROCKETS) icon = AmmoIcons[2].icon; + else if(items & IT_CELLS) icon = AmmoIcons[3].icon; + + if(icon != "") drawIconPanel(panelid, text, icon); +}; + +void(PanelID panelid, string text) drawInvIconPanel = { + if(!CVARF(fo_fte_hud)) + return; + + if(text && text != "") { + drawIconPanel(panelid, "", text); + } else if(fo_hud_editor) { + drawIconPanel(panelid, "", FaceIcons[0].icon); + } +}; +void(PanelID panelid, string text) drawInvShellsPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawIconPanel(panelid, text, AmmoIcons[0].icon); +}; +void(PanelID panelid, string text) drawInvNailsPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawIconPanel(panelid, text, AmmoIcons[1].icon); +}; +void(PanelID panelid, string text) drawInvRocketsPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawIconPanel(panelid, text, AmmoIcons[2].icon); +}; +void(PanelID panelid, string text) drawInvCellsPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawIconPanel(panelid, text, AmmoIcons[3].icon); +}; + +string(float gunnum) getGunIcon = { + string retval = ""; + if(getstatf(STAT_ITEMS) & gunnum) { + switch(gunnum) { + case IT_SHOTGUN: + retval = "shotgun"; + break; + case IT_SUPER_SHOTGUN: + retval = "sshotgun"; + break; + case IT_NAILGUN: + retval = "nailgun"; + break; + case IT_SUPER_NAILGUN: + retval = "snailgun"; + break; + case IT_GRENADE_LAUNCHER: + retval = "rlaunch"; + break; + case IT_ROCKET_LAUNCHER: + retval = "srlaunch"; + break; + case IT_LIGHTNING: + retval = "lightng"; + break; + } + if(retval != "") { + retval = strcat((getstatf(STAT_ACTIVEWEAPON) == gunnum)?"inv2_":"inv_", retval); + } + } + return retval; +}; + +void(PanelID panelid, string text, float threshold) drawBigNumberPanel = { + Hud_DrawLMPThreshold(getHudPanel(panelid), text, threshold); +}; + +void(PanelID panelid, string text) drawHealthArmourTextPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawBigNumberPanel(panelid, text, 26); +}; + +void(PanelID panelid, string text) drawAmmoTextPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawBigNumberPanel(HUDP_AMMO, text, 11); +}; + +void(PanelID panelid, string text) drawClockPanel = { + if(!CVARF(fo_fte_hud)) + return; + + drawBigNumberPanel(HUDP_GAMECLOCK, text, 0); +}; + +string clockString() { + float minutes = time/60; + float seconds = (float)time - (minutes*60); + + //return strcat(ftos(minutes),":", ftos(seconds)); + //return "00:00"; + return ftos(rint(time)); +}; + +string gameClockString() { + float minutes = 0; + float seconds = 0; + float timelimit = stof(serverkey("timelimit")); + + float end = getstatf(STAT_ROUND_END); + if (!end) + end = timelimit * 60; + + if (end && end > servertime) { + minutes = (end - servertime)/60; + seconds = (end - servertime)%60; + } else { + minutes = time/60; + seconds = time%60; + } + + string sm = strcat((minutes<10?"0":""),ftos(floor(minutes))); + string ss = strcat((seconds<10?"0":""),ftos(floor(seconds))); + + return strcat(sm,":",ss); +}; + +static void(PanelID panelid, string text) doNothing = {}; + +void(PanelID panelid, string text) drawSimplePanel = { + if (fo_hud_editor) { + FO_Hud_Panel* panel = getHudPanel(panelid); + if(panel && &panel != &NullPanel) { + if (hud_panel(panelid, getPanelPosition(panel), getPanelFillSize(panel), 0.3, 1)) { + // click event + } + } + } +} + +void(PanelID panelid, string text) drawTextPanel = { + FO_Hud_Panel* panel = getHudPanel(panelid); + + vector position = getPanelPosition(panel); + vector size = getPanelFillSize(panel); + vector mediumtext = MENU_TEXT_SMALL * panel.Scale; + local float padding = 4 * panel.Scale; + local float transparency = 0.3; + local float lines; + if (fo_hud_editor) { + string name = getHudPanel(panelid)->Name; + HRC_drawstring( + [GetTextAlignOffset(position.x, size.x, padding, name, + mediumtext.x, panel.Orientation), + padding*2 + position.y], + name, mediumtext, MENU_TEXT_1, 1, 0 + ); + if (hud_panel(panelid, position, size, transparency, panel.Display)) { + // click event + } + } else { + if (showingscores || !panel.Display || text == "") + return; + + lines = tokenizebyseparator(text, "\n"); + string line; + for (float i = 0; i <= lines; i++) { + line = argv(i); + // tokenize doesn't handle newlines very well + line = strtrim(strreplace("\n", "", line)); + if (strlen(line) > 0) { + HRC_drawstring( + [GetTextAlignOffset(position.x, size.x, padding, line, + mediumtext.x, panel.Orientation), + position.y + padding + mediumtext.y*i], + line, mediumtext, MENU_TEXT_1, 1, 0 + ); + } + } + } +} + +void(PanelID panelid, string text) drawMOTDPanel = { + FO_Hud_Panel* panel = getHudPanel(panelid); + + // No need to render MOTD once expired. + if (time > motd_expiry && panel->Display) + FO_Hud_HidePanel(panel->id); + + //vector position = getPosition(id); + //vector size = getFillSize(id); + vector position = getPanelPosition(panel); //panel.Position; + vector size = panel.FillSize * panel.Scale; + + float textscale = panel.TextScale; + if(!textscale) { + textscale = panel.Scale; + } + vector mediumtext = MENU_TEXT_SMALL * panel.Scale; + local float padding = 4 * panel.Scale; + local float transparency = 0.3; + local float lines; + local string tempstr; + if (fo_hud_editor) { + if (hud_panel(panelid, position, size, transparency, panel.Display)) { + // click event + } + } + + + if ((showingscores || !panel.Display || text == "") && !fo_hud_editor) + return; + + //if(text && text != "") { + if((!intermission && time < motd_expiry && !team_no) || fo_hud_editor) { + sui_push_frame(position, size); + sui_set_align([fo_to_sui_aligntment(panel.Orientation), SUI_ALIGN_CENTER]); + local string motd = serverkey("hostname"); + sui_text([0,0], mediumtext*textscale*1.5, motd, MENU_TEXT_4, 1, 0); + /* + HRC_drawstring( + position + [size.x / 2 - stringwidth(motd,1,mediumtext*1.5)/2,padding*2], + motd, + mediumtext*1.5, + MENU_TEXT_4, + 1, tokenizebyseparator + 0 + ); + */ + if(strlen(SBAR.MOTD) <= 1) { + motd = "Welcome to FortressOne\nwww.fortressone.org"; + } else { + motd = SBAR.MOTD; + } + lines = tokenizebyseparator(motd, "\n"); + for (float i = 0; i <= lines; i++) { + tempstr = argv(i); + // tokenize doesn't handle newlines very well + tempstr = strreplace("\n", "", tempstr); + tempstr = strtrim(tempstr); + if (strlen(tempstr) > 0) { + sui_text([0,mediumtext.y*textscale * (i+2)], mediumtext*textscale, tempstr, MENU_TEXT_1, 1, 0); + /* + HRC_drawstring( + position + [size.x / 2 - stringwidth(motd,1,mediumtext)/2,padding*2 + mediumtext.y*(i+2)], + motd, + mediumtext, + MENU_TEXT_1, + 1, + 0 + ); + */ + } + } + sui_pop_frame(); + } + //} +} + +void(PanelID panelid, string text) drawGameModePanel = { + FO_Hud_Panel* panel = getHudPanel(panelid); + + vector position = getPanelPosition(panel); + vector size = getPanelFillSize(panel); + vector mediumtext = MENU_TEXT_SMALL * panel.Scale; + local float padding = 4 * panel.Scale; + local float transparency = 0.3; + local float lines; + local string message = ""; + if (fo_hud_editor) { + message = "Game Mode"; + if (hud_panel(panelid, position, size, transparency, panel.Display)) { + // click event + } + } else { + if(showingscores || !panel.Display) { + return; + } + if(SBAR.GameMode & GAMEMODE_VOTE) { + message = "Vote Mode"; + } else { + if(SBAR.GameMode & GAMEMODE_DUEL) { + message = "Duel Mode"; + } else if(SBAR.GameMode & GAMEMODE_QUAD) { + message = "Quad Mode"; + } else if(SBAR.GameMode & GAMEMODE_CLAN) { + message = "Clan Battle"; + } + if(prematch && !round_over) { + message = strcat(message, ": prematch"); + } else { + //Quad mode only + if(SBAR.GameMode & 2) { + if(round_over) { + if(prematch) { + message = strcat(message, ": match over"); + } else { + message = strcat(message, ": intermission"); + } + } else if(quad_round > 0) { + message = strcat(message, ": round ", ftos(quad_rounds_total + 1 - quad_round), "/", ftos(quad_rounds_total)); + } + } + } + } + } + if(message) { + HRC_drawstring( + [GetTextAlignOffset( + position.x, size.x, padding, message, mediumtext.x, + panel.Orientation), position.y], + message, mediumtext, MENU_TEXT_1, 1, 0 + ); + } +} + +void(PanelID panelid, string text) drawNotificationPanel = { + FO_Hud_Panel* panel = getHudPanel(panelid); + + vector position = getPanelPosition(panel); + vector size = getPanelFillSize(panel); + vector textsize = MENU_TEXT_SMALL * panel.Scale; + vector textcolour = MENU_TEXT_4; + local float padding = 4 * panel.Scale; + local float lines; + local float pos_aligned; + local string message = ""; + if (fo_hud_editor) { + message = "Notification panel"; + if (hud_panel(panelid, position, size, 0.3, panel.Display)) { + // click event + } + } else { + if(showingscores || !panel.Display) { + return; + } + if (prematch && team_no && WP_PlayerClass() && !SBAR.CountdownStarted && + game_state.is_player) { + if(SBAR.ReadyStatus & PLAYER_READY) { + message = "Ready"; + textcolour = MENU_TEXT_GREEN; + } else if(round_over) { + message = "Change map to continue playing"; + } else { + textcolour = MENU_TEXT_WARNING; + tokenize(findkeysforcommand("ready")); + string keynum = argv(0); + + if(SBAR.ReadyStatus & LAST_NOT_READY) { + local vector alert_text_size = MENU_TEXT_LARGE * panel.Scale; + local vector alert_text_position; + alert_text_position.y = ScreenSize.y / 3; + alert_text_position.x = (ScreenSize.x / 2) - (size.x / 2); + local string alert_text_message = "You're last"; + + pos_aligned = GetTextAlignOffset( + alert_text_position.x, + size.x, + padding, + alert_text_message, + alert_text_size.x, + panel.Orientation + ); + + HRC_drawstring( + [pos_aligned, alert_text_position.y], + alert_text_message, alert_text_size, textcolour, 1, 0 + ); + + position.y = alert_text_position.y + alert_text_size.y + textsize.y; + position.x = (ScreenSize.x / 2) - (size.x / 2); + + if(keynum != "-1") { + message = strcat("Press ", keynumtostring(stof(keynum)), " to start"); + } else { + message = "Type 'ready' in the console to start"; + } + } else if (SBAR.ReadyStatus & ENEMY_TEAM_READY) { + local vector alert_text_size = MENU_TEXT_MEDIUM * panel.Scale; + local vector alert_text_position; + alert_text_position.y = ScreenSize.y / 3; + alert_text_position.x = (ScreenSize.x / 2) - (size.x / 2); + local string alert_text_message = "Enemy team is ready!"; + + pos_aligned = GetTextAlignOffset( + alert_text_position.x, + size.x, + padding, + alert_text_message, + alert_text_size.x, + panel.Orientation + ); + + HRC_drawstring( + [pos_aligned, alert_text_position.y], + alert_text_message, alert_text_size, textcolour, 1, 0 + ); + + position.y = alert_text_position.y + alert_text_size.y + textsize.y; + position.x = (ScreenSize.x / 2) - (size.x / 2); + + if(keynum != "-1") { + message = strcat("Press ", keynumtostring(stof(keynum)), " to ready up"); + } else { + message = "Type 'ready' in the console to ready up"; + } + } else { + if(keynum != "-1") { + message = strcat("Press ", keynumtostring(stof(keynum)), " to ready up"); + } else { + message = "Type 'ready' in the console to ready up"; + } + } + } + } else if (pick_up_time && time < pick_up_time + 1.5) { + local vector alert_text_size = MENU_TEXT_SMALL * panel.Scale; + local vector alert_text_position; + alert_text_position.y = ScreenSize.y / 3; + alert_text_position.x = (ScreenSize.x / 2) - (size.x / 2); + + local string flag_team_str = "the"; + local vector menu_text_color = MENU_TEXT_4; + + switch(flag_team) { + case 1: + flag_team_str = "blue's"; + menu_text_color = MENU_TEXT_BLUE_FO; + break; + case 2: + flag_team_str = "red's"; + menu_text_color = MENU_TEXT_RED_FO; + break; + case 3: + flag_team_str = "yellow's"; + menu_text_color = MENU_TEXT_YELLOW_FO; + break; + case 4: + flag_team_str = "green's"; + menu_text_color = MENU_TEXT_GREEN_FO; + break; + } + + local string alert_text_message = sprintf("You got %s flag!", flag_team_str); + + pos_aligned = GetTextAlignOffset( + alert_text_position.x, + size.x, + padding, + alert_text_message, + alert_text_size.x, + panel.Orientation + ); + + HRC_drawstring( + [pos_aligned, alert_text_position.y], + alert_text_message, alert_text_size, menu_text_color, 1, 0 + ); + } + } + if(message) { + pos_aligned = GetTextAlignOffset(position.x,size.x,padding,message,textsize.x,panel.Orientation); + HRC_drawstring( + [pos_aligned, position.y], message, textsize, textcolour, 1, 0 + ); + } +} + +string (float val) AbbreviateNumberToString = { + val = rint(val); + if (val < 10000) + return ftos(rint(val)); + + float count = 0; + while (val >= 1000) { + val /= 1000; + count++; + } + + // Try to (with max density) use up to 4 characters. + const string abbr = "kmb"; + if (count == 0) + return ftos(val); + else if (val < 100) + return sprintf("%0.1f%c", val, abbr[min(count - 1, 2)]); + else + return sprintf("%d%c", val, abbr[min(count - 1, 2)]); +}; + +void (vector position, vector size, string val, vector textcolour, float alpha + , float flags, string colname) drawShowScoresColumnVal = { + if (strlen(val) < strlen(colname)) + val = strpad(strlen(colname) * -1, val); + sui_text(position, size, val, textcolour, 1, 0); +}; + +enum { + SCT_BLUE, + SCT_RED, + SCT_YELLOW, + SCT_GREEN, + SCT_SPECS, + NUM_SCT, +}; + +struct FO_ScoreBoardTeam { + FO_ScoreBoardLine lines[FO_SCOREBOARDLINES_LENGTH]; + int count; +}; + +struct FO_ScoreBoard { + FO_ScoreBoardTeam team[NUM_SCT]; +}; + +static FO_ScoreBoard score_board; + +DEFCVAR_FLOAT(fo_simple_names, 0); + +vector drawShowScoresTeamPanel (FO_Hud_Panel *panel, FO_ScoreBoardTeam* team, + float padding, vector team_colour) { + vector smalltext = MENU_TEXT_SMALL * panel.Scale; + vector yspacer = [0, 20]; + vector position = [0, 0]; + vector size = getPanelFillSize(panel); + + position = [0,0]; + sui_set_align([SUI_ALIGN_START, SUI_ALIGN_START]); + size = [size_x, smalltext_y]; + sui_fill(position, size, MENU_BG, 1, 0); + + // FIXME - hardcoded 4 length on all column names... (pl has 2, name and score have 5) + // of just never have any other columns with more than 4 chars + float txtlength = strlen("name") * smalltext.x; + float namespace = size_x - ((txtlength + padding) * (FO_ScoreBoardColumns.length - 1) - padding); + + float classspace = strlen("class") * smalltext.x; + + FO_ScoreBoardLine* lines = &team->lines; + for (float i = 0; i < FO_ScoreBoardColumns.length; i++) { + string colname = FO_ScoreBoardColumns[i]; + if (lines[i].team_no == TEAM_SPECTATOR || lines[i].team_no == TEAM_OBSERVER) { + switch (colname) { + case "class": + colname = "type"; + case "ping": + case "pl": + case "name": + sui_text(position, smalltext, colname, SCOREB_HEADER, .7, 0); + break; + } + } else { + sui_text(position, smalltext, colname, SCOREB_HEADER, .7, 0); + } + + txtlength = strlen(colname) * smalltext.x; + if (colname == "name") + position_x += namespace; + else + position_x += txtlength + padding; + } + + position = [0, smalltext.y]; + float lineCount = 1; + for (float i = 0; i < team->count; i++) { + string name = lines[i].name; + if (!name) + continue; + + lineCount++; + float alpha = 1; + if (lineCount % 2 == 0) + alpha = .7; + else + alpha = .3; + + vector fill_colour = team_colour; + if (lines[i].name == getplayerkeyvalue(player_localnum, "name")) { + alpha = 0.5; + fill_colour = SCOREB_SELF_BG; + } else if (lines[i].name == getplayerkeyvalue(player_localentnum - 1, "name")) { + alpha = 0.5; + fill_colour = SCOREB_SPECTATED_BG; + } + + sui_fill(position, size, fill_colour, alpha, 0); + + float col = 0, alpha = 1, flags = 0; + string val = AbbreviateNumberToString(lines[i].ping); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].pl); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + if (team_no == lines[i].team_no || lines[i].team_no == TEAM_SPECTATOR || lines[i].team_no == TEAM_OBSERVER + || game_state.is_spectator) { + string c = lines[i].classtext; + if (lines[i].team_no != TEAM_SPECTATOR && lines[i].team_no != TEAM_OBSERVER) { + float cspace = strlen(c) * smalltext.x; + if (cspace > classspace) { + float max = classspace / smalltext.x; + // TODO - maybe make this ok with non fixed width fonts one day... we're 90% of the way there + c = substring(c, 0, max); + } + } + + sui_text(position, smalltext, c, SCOREB_FIELD, 1, 0); + } + + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + if (lines[i].team_no != TEAM_SPECTATOR && lines[i].team_no != TEAM_OBSERVER) { + val = AbbreviateNumberToString(lines[i].score); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + } + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + string n = lines[i].name; + if (CVARF(fo_simple_names)) + n = strdecolorize(n); + + if (stringwidth(n, TRUE, smalltext) >= namespace - padding) { + n = strdecolorize(n); + n = substring(n, 0, floor(namespace - padding) / smalltext.x); + } + sui_text(position, smalltext, n, SCOREB_NAME, 1, 0); + position_x += namespace; + + vector iconpos = [size_x, position_y]; + //check for icon files existing, otherwise use fallbacks + float filehandle; + if(lines[i].ready) { + if (FO_ScoreBoardAssets.icon_ready != "") + sui_pic(iconpos, smalltext, FO_ScoreBoardAssets.icon_ready, '1 1 1', alpha, flags); + else + sui_text(iconpos, smalltext, "R", MENU_TEXT_GREEN_FO, 1, 0); + iconpos_x += smalltext.x; + } + + if(lines[i].chat & 1) { + if (FO_ScoreBoardAssets.icon_chat != "") { + sui_pic(iconpos, smalltext, FO_ScoreBoardAssets.icon_chat, '1 1 1', alpha, flags); + } else { + //use ezquake chaticons image. Couldn't get drawsubpic to work, so now we get this instead + drawsetcliparea(iconpos_x + panel.Position.x, iconpos_y + panel.Position.y + 20, smalltext.x, smalltext.y); + sui_pic(iconpos, [smalltext_x * 4,smalltext_y * 256 / 56 ], "textures/chaticons.png", '1 1 1', alpha, flags); + drawresetcliparea(); + } + iconpos_x += smalltext.x; + } + + if(lines[i].chat & 2) { + if (FO_ScoreBoardAssets.icon_afk != "") { + sui_pic(iconpos, smalltext, FO_ScoreBoardAssets.icon_afk, '1 1 1', alpha, flags); + } else { + drawsetcliparea(iconpos_x + panel.Position.x, iconpos_y + panel.Position.y + 20, smalltext.x, smalltext.y); + sui_pic(iconpos - [smalltext_x,0], [smalltext_x * 4,smalltext_y * 256 / 56 ], "textures/chaticons.png", '1 1 1', alpha, flags); + drawresetcliparea(); + } + iconpos_x += smalltext.x; + } + + if (lines[i].team_no == TEAM_SPECTATOR || lines[i].team_no == TEAM_OBSERVER) { + position_y += smalltext_y; + position_x = 0; + continue; + } + + val = AbbreviateNumberToString(lines[i].caps); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].touches); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].kills); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].teamkills); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].deaths); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].afflicted); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].teamafflicted); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].damagegiven); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + val = AbbreviateNumberToString(lines[i].damagetaken); + drawShowScoresColumnVal(position, smalltext, val, SCOREB_FIELD, alpha, flags, FO_ScoreBoardColumns[col]); + position_x += strlen(FO_ScoreBoardColumns[col++]) * smalltext.x + padding; + + position_x = 0; + position_y += smalltext_y; + } + + size_y = smalltext.y * lineCount; + sui_border_box([0, 0], size, 1, MENU_BORDER, FO_MENU_TRANSPARENCY, 0); + return position; +}; + + +void drawShowScoresPanel(PanelID panelid, string text) { + FO_Hud_Panel* panel = getHudPanel(panelid); + + if (!panel.Display && !fo_hud_editor) + return; + + vector position = getPanelPosition(panel); + vector size = getPanelFillSize(panel); + vector mediumtext = MENU_TEXT_SMALL * panel.Scale * 2; + vector textcolour = MENU_TEXT_1; + float padding = 4 * panel.Scale; + + if (fo_hud_editor) { + if (hud_panel(panelid, position, size, 0.3, panel.Display)) { + // click event + } + return; + } + + float i; + + for (i = 0; i < NUM_SCT; i++) + score_board.team[i].count = 0; + + sui_push_frame(position, [size.x, 1]); + sui_set_align([SUI_ALIGN_CENTER, SUI_ALIGN_START]); + position = [0,0]; + sui_text(position, mediumtext, "Scoreboard", textcolour, 1, 0); + + for (i = 0; i < FO_SCOREBOARDLINES_LENGTH; i++) { + float ping = getplayerkeyfloat(i, INFOKEY_P_PING); + if (!ping) + continue; + + int sbl_team_no = getplayerkeyfloat(i, "team_no"); + switch (sbl_team_no) { + case 1: case 2: case 3: case 4: + sbl_team_no -= 1; // Maps to SC enum + break; + default: + sbl_team_no = SCT_SPECS; + break; + } + + FO_ScoreBoardTeam* team = &score_board.team[sbl_team_no]; + FO_ScoreBoardLine* sbl = &team->lines[team->count++]; + + sbl->ping = ping; + sbl->pl = getplayerkeyfloat(i, INFOKEY_P_PACKETLOSS); + float class = getplayerkeyfloat(i, "playerclass"); + sbl->classtext = ClassToString(class); + sbl->name = getplayerkeyvalue(i, INFOKEY_P_NAME); + sbl->score = getplayerkeyfloat(i, INFOKEY_P_FRAGS); + sbl->caps = getplayerkeyfloat(i, "caps"); + sbl->touches = getplayerkeyfloat(i, "touches"); + sbl->kills = getplayerkeyfloat(i, "kills"); + sbl->teamkills = getplayerkeyfloat(i, "teamkills"); + sbl->deaths = getplayerkeyfloat(i, "deaths"); + sbl->afflicted = getplayerkeyfloat(i, "afflicted"); + sbl->teamafflicted = getplayerkeyfloat(i, "teamafflicted"); + sbl->damagegiven = getplayerkeyfloat(i, "damagegiven"); + sbl->damagetaken = getplayerkeyfloat(i, "damagetaken"); + sbl->team_no = getplayerkeyfloat(i, "team_no"); + sbl->ready = getplayerkeyfloat(i, "ready"); + sbl->chat = getplayerkeyfloat(i, "chat"); + + if (sbl_team_no == SCT_SPECS) { + float spec = getplayerkeyfloat(i, "*spectator"); + sbl->team_no = (spec == 1) ? TEAM_SPECTATOR : TEAM_OBSERVER; + sbl->classtext = (spec == 1) ? "Spectator" : "Observer"; + } + } + + float sbCount = 0; + + vector smalltext = MENU_TEXT_SMALL * panel.Scale; + vector yspacer = [0, 20]; + size_y = smalltext_y; + + for (i = 0; i < NUM_SCT; i++) { + FO_ScoreBoardTeam* team = &score_board.team[i]; + if (!team->count) + continue; + + position += yspacer; + sui_push_frame(position, size); + position = drawShowScoresTeamPanel(panel, team, padding, + TEXT_TEAM_COLOUR[i]); + sbCount++; + } + + /* + // FIXME + maybe some colours on some columns to help differentiate them + If possible, have AFK/msg indicators + Reset between rounds + Make a summary one for end of game too + show who you are on scoreboard + show who you are speccing + when player comes in after caps occur, they aren't getting team score + +*/ + + for (float i = 0; i < sbCount; i++) + sui_pop_frame(); + sui_pop_frame(); +} + +void (float show) FO_Show_Scores = { + showingscores = show; + getHudPanel(HUDP_SHOWSCORES)->Display = show; +}; + +struct SpeedBarColors { + float max_speed; + float frac; + vector color; +}; + +const float SPEEDBAR_MAX = 3000; +static SpeedBarColors speedbar_colors[] = { + { 300, 0.40, '0.6 0.2 0.2'}, + { 400, 0.10, '0.7 0.2 0.2'}, + { 500, 0.25, '0.7 0.4 0.2'}, + { 750, 0.1225, '0.7 0.7 0.3'}, + {1000, 0.1225, '0.1 0.5 0.1'}, + {SPEEDBAR_MAX, 1, '0.1 0.7 0.1'}, +}; + +static float PlanarSpeed() { + vector pvel = PM_Vel(); + pvel.z = 0; // compiler bug: pvel becomes [1 0 0] if this is inlined + return vlen(pvel); +} + +void drawSpeedBar(PanelID panelid, string text) { + FO_Hud_Panel* panel = getHudPanel(HUDP_SPEEDBAR); + vector fillsize = panel.FillSize * panel.Scale; + float alpha = fo_hud_editor ? 0.2:0.9; + vector position = getPanelPosition(panel); + + if (hud_panel(panel->id, position, fillsize, alpha, panel.Display)) { + // click event + } + + if (fo_hud_editor) + return; + + HRC_drawfill(position, fillsize, '0.5 0.5 0.5'); + + position += '1 1 0'; + fillsize -= '1 2 0'; + float frac_start, frac_end = 1; + + float tspeed = PlanarSpeed(); + float rspeed = min(tspeed, SPEEDBAR_MAX); + + float last_max = 0; + for (int i = 0; i < speedbar_colors.length; i++) { + SpeedBarColors* cur = &speedbar_colors[i]; + + if (frac_end > 0.99) { + frac_start = 0; + frac_end = cur.frac; + } else { + frac_start = frac_end; + frac_end = min(frac_start + cur.frac, 1); + } + + float pos; + if (rspeed > cur.max_speed) { + pos = frac_end; + // When we know we're going to wrap, fill the prior color. + if (frac_end > 0.99) + frac_start = 0; + } else { + pos = (rspeed - last_max) / (cur.max_speed - last_max) * + (frac_end - frac_start) + frac_start; + } + + vector start = position, fill = fillsize; + start.x += frac_start * fill.x; + fill.x *= pos - frac_start; + HRC_drawfill(start, fill, cur.color); + + last_max = cur.max_speed; + if (rspeed < last_max) + break; + } + + HRC_drawstring(position + '3 0 0', ftos((int)tspeed), '9 9 0', '1 1 1'); +} + +void drawReloadProgress(PanelID panelid, string text) { + FO_Hud_Panel* panel = getHudPanel(HUDP_RELOADPROGRESS); + vector fillsize = panel.FillSize * panel.Scale; + vector position = getPanelPosition(panel); + + if (fo_hud_editor && + hud_panel(panel->id, position, fillsize, 0.2, panel.Display)) { + // click event + } + + if (!game_state.is_alive) + return; + + float per = WP_ReloadPercent(); + if (per == 1) + return; + + HRC_drawfill(position, fillsize, '0.5 0.5 0.5'); + vector progress = fillsize; + progress.x *= per; + + position += '1 1 0'; + HRC_drawfill(position, progress - '1 2 0', '0.9 0.9 0.9'); + const string text = "Reloading"; + const vector fontsize = '8 8 0'; + position.x += (fillsize.x - stringwidth(text, 0, fontsize)) / 2; + HRC_drawstring(position, text, fontsize, '1 0.4 0.4'); +} + +string getPingPanelText(); + +var FO_Hud_Panel Hud_Panels[] = { +// id, Name, Position, FillSize, Scale, TextScale, Display, Orientation, +// void drawPanel(PanelID panelid, string text, string icon), +// string getValue(), +// Style, Snap, Status + + {HUDP_TEAMSCORE, "teamscore", FO_HUD_TEAM_SCORE_NAME, '0 0', '72 12', 2,0,1,FO_HUD_INSERT_AFTER, 0, drawTeamScorePanel, {return "";}}, + {HUDP_MAP_MENU, "mapmenu", FO_HUD_MAP_MENU_NAME, '10 30', '800 400',1,0,1,FO_HUD_INSERT_MIDDLE, 1, drawMapMenuPanel, {return "";}}, + {HUDP_SHOWSCORES, "showscores", FO_HUD_SHOWSCORES_NAME, '10 100', '600 200',1,0,1,FO_HUD_INSERT_MIDDLE, 1, drawShowScoresPanel, {return "";}}, + {HUDP_MENU, "menu", "Menu", '10 110', '300 200',1,0,1,0,1, drawSimplePanel, {return "";}}, + {HUDP_MENU_HINT, "menuhint", FO_HUD_MENU_HINT_NAME, '100 300', '300 24', 1,0,1,0,1, drawTextPanel, {return SBAR.Hint;}}, + {HUDP_CLIPSIZE, "clipsize", FO_HUD_CLIPSIZE_NAME, '464 455', '26 26', 0.75,0,1,0,0, drawClipSize, { return WP_GetClip(); }}, + {HUDP_RELOADPROGRESS, "reload_progress", "Reload Progress", '0 0', '100 10', 1,1,1,0,0, drawReloadProgress, {return "";}, 0, HUD_SNAP_CENTER}, + {HUDP_FRAGSTREAK, "fragstreak", FO_HUD_FRAGSTREAK_NAME, '10 50', '26 26', 1,0,1,0,0, drawFragStreakPanel, {return ftos(SBAR.FragStreak);}}, + {HUDP_CAPS, "caps", FO_HUD_CAPS_NAME, '10 80', '26 26', 1,0,1,0,0, drawCapsPanel, {return ftos(SBAR.Caps);}}, + {HUDP_GREN1, "gren1", FO_HUD_GREN1_NAME, '10 110', '26 26', 1,0,1,0,0, drawGren1Panel, {return ftos(WP_GrenCount(1));}}, + {HUDP_GREN2, "gren2", FO_HUD_GREN2_NAME, '10 140', '26 26', 1,0,1,0,0, drawGren2Panel, {return ftos(WP_GrenCount(2));}}, + {HUDP_SPECIAL, "playerclass", FO_HUD_SPECIAL_NAME, '10 170', '50 26', 1,0,1,0,0, drawSpecial, {return ftos(WP_PlayerClass());}}, + {HUDP_IDENTIFY, "identify", FO_HUD_IDENTIFY_NAME, '10 200', '50 26', 1,0,1,FO_HUD_INSERT_MIDDLE, 0, drawIdentify, {return SBAR.Identify;}}, + {HUDP_FLAGINFO, "flaginfo", FO_HUD_FLAGINFO_NAME, '10 230', '26 260', 1,0,1,0,0, drawFlagInfo, {return "";}}, + {HUDP_GRENTIMER, "grentimer", FO_HUD_GRENTIMER_NAME, '100 110', '26 26', 1,0,1,0,0, drawGrenTimerPanel, {return "";}}, + {HUDP_MOTD, "motd", FO_HUD_MOTD_NAME, '150 100', '100 24', 1,0,1,0,0, drawMOTDPanel, {return SBAR.MOTD;}}, + {HUDP_GAME_MODE, "gamemode", FO_HUD_GAME_MODE_NAME, '100 140', '100 10', 1,0,1,0,0, drawGameModePanel, {return "";}}, + {HUDP_NOTIFICATION, "notification", FO_HUD_NOTIFICATION_NAME, '10 100', '100 10', 2,0,1,FO_HUD_INSERT_MIDDLE, 1, drawNotificationPanel, {return SBAR.Hint;}}, + {HUDP_HEALTH, "health", FO_HUD_HEALTH_NAME, '-22 -2', '72 24', 1,0,1,0,0, drawHealthArmourTextPanel, {return ftos(getstatf(STAT_HEALTH));}, 0, 34}, + {HUDP_FACE, "face", FO_HUD_FACE_NAME, '-70 -2', '24 24', 1,0,1,0,0, drawFacePanel, {return "";}, 0, 34}, + {HUDP_AMMO, "ammo", FO_HUD_AMMO_NAME, '90 -2', '72 24', 1,0,1,0,0, drawAmmoTextPanel, {return ftos(WP_CurrentAmmo());}, 0, 34}, + {HUDP_AMMOICON, "ammoicon", FO_HUD_AMMO_ICON_NAME , '42 -2', '24 24', 1,0,1,0,0, drawAmmoPanel, {return "";}, 0, 34}, + {HUDP_ARMOUR, "armour", FO_HUD_ARMOUR_NAME, '-134 -2', '72 24', 1,0,1,0,0, drawHealthArmourTextPanel, {return ftos(getstatf(STAT_ARMOR));}, 0, 34}, + {HUDP_ARMOURICON, "armouricon", FO_HUD_ARMOUR_ICON_NAME,'-182 -2', '24 24', 1,0,1,0,0, drawArmourPanel, {return "";}, 0, 34}, + {HUDP_INVSHELLS, "invshells", FO_HUD_INV_SHELLS_NAME, '-2 -244', '24 24', 1,1.4,1,0,0, drawInvShellsPanel, {return ftos(WP_GetAmmo(AMMO_SHELLS));}, 1, 33}, + {HUDP_INVNAILS, "invnails", FO_HUD_INV_NAILS_NAME, '-2 -220', '24 24', 1,1.4,1,0,0, drawInvNailsPanel, {return ftos(WP_GetAmmo(AMMO_NAILS));}, 1, 33}, + {HUDP_INVROCKETS, "invrockets", FO_HUD_INV_ROCKETS_NAME,'-2 -196', '24 24', 1,1.4,1,0,0, drawInvRocketsPanel, {return ftos(WP_GetAmmo(AMMO_ROCKETS));}, 1, 33}, + {HUDP_INVCELLS, "invcells", FO_HUD_INV_CELLS_NAME, '-2 -172', '24 24', 1,1.4,1,0,0, drawInvCellsPanel, {return ftos(WP_GetAmmo(AMMO_CELLS));}, 1, 33}, + {HUDP_GAMECLOCK, "gameclock", "Game Clock", '0 0' , '108 24', 1,0.8,1,0,0, drawClockPanel, {return gameClockString();}, 0, 10}, + {HUDP_QUAD, "quad", "Quad Icon", '-200 -30','26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatf(STAT_ITEMS) & IT_QUAD)?"textures/wad/sb_quad":"";}, 0, 36}, + {HUDP_PENT, "pent", "Pentagram Icon", '-170 -30','26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatf(STAT_ITEMS) & IT_INVULNERABILITY)?"textures/wad/sb_invuln":"";}, 0, 36}, + {HUDP_RING, "ring", "Ring Icon", '-140 -30','26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatf(STAT_ITEMS) & IT_INVISIBILITY)?"textures/wad/sb_invis":"";}, 0, 36}, + {HUDP_SUIT, "suit", "Biosuit Icon", '-110 -30','26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatf(STAT_ITEMS) & IT_SUIT)?"textures/wad/sb_suit":"";}, 0, 36}, + {HUDP_KEY1, "key1", "Silver Key", '140 -30', '26 26', 0.6,1.4,1,0,0, drawInvIconPanel, {return (getstatf(STAT_ITEMS) & IT_KEY1)?"textures/wad/sb_key1":"";}, 0, 34}, + {HUDP_KEY2, "key2", "Gold Key", '160 -30', '26 26', 0.6,1.4,1,0,0, drawInvIconPanel, {return (getstatf(STAT_ITEMS) & IT_KEY2)?"textures/wad/sb_key2":"";}, 0, 34}, + {HUDP_RUNE1, "rune1", "Earth Rune", '-200 0', '26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatbits(STAT_ITEMS,28,4) & 1)?"textures/wad/sb_sigi1":"";}, 0, 34}, + {HUDP_RUNE2, "rune2", "Black Rune", '-170 0', '26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatbits(STAT_ITEMS,28,4) & 2)?"textures/wad/sb_sigil2":"";}, 0, 34}, + {HUDP_RUNE3, "rune3", "Hell Rune", '-140 0', '26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatbits(STAT_ITEMS,28,4) & 4)?"textures/wad/sb_sigil3":"";}, 0, 34}, + {HUDP_RUNE4, "rune4", "Elder Rune", '-110 0', '26 26', 1,1.4,1,0,0, drawInvIconPanel, {return (getstatbits(STAT_ITEMS,28,4) & 8)?"textures/wad/sb_sigil4":"";}, 0, 34}, + {HUDP_GUN2, "gun2", "Shotgun", '-4 -170', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_SHOTGUN);}, 0, 36}, + {HUDP_GUN3, "gun3", "Super Shotgun", '-4 -150', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_SUPER_SHOTGUN);}, 0, 36}, + {HUDP_GUN4, "gun4", "Nailgun", '-4 -130', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_NAILGUN);}, 0, 36}, + {HUDP_GUN5, "gun5", "Super Nailgun", '-4 -110', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_SUPER_NAILGUN);}, 0, 36}, + {HUDP_GUN6, "gun6", "Grenade Launcher", '-4 -90', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_GRENADE_LAUNCHER);}, 0, 36}, + {HUDP_GUN7, "gun7", "Rocket Launcher", '-4 -70', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_ROCKET_LAUNCHER);}, 0, 36}, + {HUDP_GUN8, "gun8", "Lighning Gun", '-4 -50', '36 24', 0.8,1.4,1,0,0, drawInvIconPanel, {return getGunIcon(IT_LIGHTNING);}, 0, 36}, + {HUDP_SPEED, "speed", "Speed", '4 15', '26 26', 1,1.4,0,0,0, drawTextPanel, {return CVARF(fo_fte_hud) ? ftos((int)PlanarSpeed()) : "";}, 0, 4}, + {HUDP_SPEEDBAR, "speedbar", "Speed Bar", '4 30', '150 10', 1,1.4,0,0,0, drawSpeedBar, {return "";}, 0, 4}, + {HUDP_PING, "ping", "Ping", '4 15', '120 12', 1,1.4,0,0,0, drawTextPanel, getPingPanelText, 0, HUD_SNAP_TOP_RIGHT}, + {HUDP_OPTIONS, "hudoptions", FO_HUD_OPTIONS_NAME, '10 10', '150 182',1, 0,1,0,1, doNothing, {return "";}, 1}, +}; + +inline FO_Hud_Panel* (PanelID panelid) getHudPanel = { + ASSERTD_NE(panelid, HUDP_INVALID); + return &Hud_Panels[panelid - HUDP_FIRST]; +}; + +FO_Hud_Panel* (string save_name) getHudPanelBySaveName = { + for(float i = HUDP_FIRST; i <= HUDP_LAST; i++) { + FO_Hud_Panel* panel = getHudPanel(i); + if (panel->SaveName == save_name) + return panel; + } + return &NullPanel; +}; + +vector (FO_Hud_Panel* panel, float scale) getScaledPanelPosition = { + if(panel.Snap == HUD_SNAP_NONE) { + return panel.Position; + } + vector v = panel.Position; + //if(panel.Snap & HUD_SNAP_LEFT) v.x; + if(panel.Snap & HUD_SNAP_CENTER) v.x += (ScreenSize.x / 2) - ((panel.FillSize.x * scale) / 2); + if(panel.Snap & HUD_SNAP_RIGHT) v.x += ScreenSize.x - (panel.FillSize.x * scale); + //if(panel.Snap & HUD_SNAP_TOP) v.y; + if(panel.Snap & HUD_SNAP_VCENTER) v.y += (ScreenSize.y / 2) - ((panel.FillSize.y * scale) / 2); + if(panel.Snap & HUD_SNAP_BOTTOM) v.y += ScreenSize.y - (panel.FillSize.y * scale); + return v; +} + +vector (FO_Hud_Panel* panel) getPanelPosition = { + return getScaledPanelPosition(panel, panel.Scale); +} + +vector(PanelID panelid) getPosition = { + FO_Hud_Panel* panel = getHudPanel(panelid); + return getPanelPosition(panel); +} +vector(PanelID panelid) getFillSize = { + return getHudPanel(panelid)->FillSize; +} + +vector (FO_Hud_Panel* panel) getPanelFillSize = { + return panel.FillSize; +} + +vector (FO_Hud_Panel* panel) getScaledPanelFillSize = { + return panel.FillSize * panel.Scale; +} diff --git a/csqc/sui_sys.qc b/csqc/sui_sys.qc new file mode 100644 index 000000000..5818d610e --- /dev/null +++ b/csqc/sui_sys.qc @@ -0,0 +1,930 @@ +// Shpuld's Simple UI lib - sui +// Created 11/2018 +// +// sui is a simple QuakeC UI lib for drawing and handling game interfaces. +// The API is made simple and easy to build upon, but cuts have been made +// to keep complexity low. +// + +#ifdef MENU +const float IE_KEYDOWN = 0; /* Specifies that a key was pressed. Second argument is the scan code. Third argument is the unicode (printable) char value. Fourth argument denotes which keyboard(or mouse, if its a mouse 'scan' key) the event came from. Note that some systems may completely separate scan codes and unicode values, with a 0 value for the unspecified argument. */ +const float IE_KEYUP = 1; /* Specifies that a key was released. Arguments are the same as IE_KEYDOWN. On some systems, this may be fired instantly after IE_KEYDOWN was fired. */ +const float IE_MOUSEDELTA = 2; /* Specifies that a mouse was moved (touch screens and tablets typically give IE_MOUSEABS events instead, use _windowed_mouse 0 to test code to cope with either). Second argument is the X displacement, third argument is the Y displacement. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_MOUSEABS = 3; /* Specifies that a mouse cursor or touch event was moved to a specific location relative to the virtual screen space. Second argument is the new X position, third argument is the new Y position. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_ACCELEROMETER = 4; +const float IE_FOCUS = 5; /* Specifies that input focus was given. parama says mouse focus, paramb says keyboard focus. If either are -1, then it is unchanged. */ +const float IE_JOYAXIS = 6; /* Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller. */ + +#define printf(x, ...) print(sprintf(x, __VA_ARGS__)) + +#endif +float _sui_draw_initialized; + + +// framing + +// pseudo windowing, sets a new "frame" for whatever we're drawing, instead of +// always using screen [0, 0] as min and [screen_width, screen_height] as max. +// also allows for aligning content to frame start/end/center on both axis + +struct _frame_t { + vector pos; + vector size; + vector align; +}; +const float MAX_FRAMES = 64; +_frame_t _frames[MAX_FRAMES]; +float _frame_index; + +const float SUI_ALIGN_START = 0; +const float SUI_ALIGN_CENTER = 1; +const float SUI_ALIGN_END = 2; + +void() sui_reset_align = +{ + _frames[_frame_index].align = [SUI_ALIGN_START, SUI_ALIGN_START]; +}; + +void(float align) sui_set_x_align = +{ + _frames[_frame_index].align.x = align; +}; + +void(float align) sui_set_y_align = +{ + _frames[_frame_index].align.y = align; +}; + +void(vector align) sui_set_align = +{ + _frames[_frame_index].align = align; +}; + +void(__inout vector point) sui_transform_point = +{ + int idx = _frame_index; + switch (_frames[idx].align.x) + { + case SUI_ALIGN_START: point_x += _frames[idx].pos.x; break; + case SUI_ALIGN_CENTER: point_x += _frames[idx].pos.x + _frames[idx].size.x * 0.5; break; + case SUI_ALIGN_END: point_x += _frames[idx].pos.x + _frames[idx].size.x; break; + default: break; + } + switch (_frames[idx].align.y) + { + case SUI_ALIGN_START: point_y += _frames[idx].pos.y; break; + case SUI_ALIGN_CENTER: point_y += _frames[idx].pos.y + _frames[idx].size.y * 0.5; break; + case SUI_ALIGN_END: point_y += _frames[idx].pos.y + _frames[idx].size.y; break; + default: break; + } +}; + +void(__inout vector point, vector size) sui_transform_box = +{ + int idx = _frame_index; + switch (_frames[idx].align.x) + { + case SUI_ALIGN_START: + point_x += _frames[idx].pos.x; + break; + case SUI_ALIGN_CENTER: + point_x += _frames[idx].pos.x + _frames[idx].size.x * 0.5 - size_x * 0.5; + break; + case SUI_ALIGN_END: + point_x += _frames[idx].pos.x + _frames[idx].size.x - size_x; + break; + default: break; + } + switch (_frames[idx].align.y) + { + case SUI_ALIGN_START: + point_y += _frames[idx].pos.y; + break; + case SUI_ALIGN_CENTER: + point_y += _frames[idx].pos.y + _frames[idx].size.y * 0.5 - size_y * 0.5; + break; + case SUI_ALIGN_END: + point_y += _frames[idx].pos.y + _frames[idx].size.y - size_y; + break; + default: break; + } +}; + +vector() sui_current_frame_pos = +{ + return _frames[_frame_index].pos; +}; + +vector() sui_current_frame_size = +{ + return _frames[_frame_index].size; +}; + +float _sui_is_clipping; +vector _sui_clip_area_mins; +vector _sui_clip_area_maxs; +void() sui_clip_to_frame = +{ + vector pos = _frames[_frame_index].pos; + vector size = _frames[_frame_index].size; + _sui_is_clipping = TRUE; + _sui_clip_area_mins = pos; + _sui_clip_area_maxs = pos + size; + drawsetcliparea(pos.x, pos.y, size.x, size.y); +}; + +void() sui_reset_clip = +{ + _sui_is_clipping = FALSE; + drawresetcliparea(); +}; + +float() sui_is_clipping = +{ + return _sui_is_clipping; +}; + +void(vector pos, vector size) sui_push_frame = +{ + sui_transform_box(pos, size); + + _frame_index += 1; + if (_frame_index >= MAX_FRAMES) + { + printf("^3sui warning: amount of frames = %.0f exceeds MAX_FRAMES = %.0f, consider increasing MAX_FRAMES\n", _frame_index, MAX_FRAMES); + return; + } + + _frames[_frame_index].pos = pos; + _frames[_frame_index].size = size; + _frames[_frame_index].align = [SUI_ALIGN_START, SUI_ALIGN_START]; // TODO allow customizing this +}; + +void() sui_pop_frame = +{ + if (_frame_index > 0) _frame_index -= 1; +}; + +void() sui_reset_frame = +{ + _frame_index = 0; + sui_reset_align(); +}; + + +// actions + +// interaction for sui elements, relies a lot on reading globals to see which +// element id is under cursor or held or whatever, not the most elegant +// solution but in this highly imperative world of QuakeC we can live with it + +float _holding_click; +vector _cursor_click; +vector _cursor_position; +vector _cursor_relative_click; +vector _cursor_relative_hover; +struct _action_element_t { + vector pos; + vector size; + PanelID id; + void(float index, vector click_ratios) action; +}; +const float MAX_ACTION_ELEMENTS = 256; +_action_element_t _action_elements[MAX_ACTION_ELEMENTS]; +float _action_elements_index; + + +// TODO better naming +float(vector point, vector min, vector max) is_2dpoint_in_bounds = +{ + if (point_x <= min_x || point_y <= min_y) return FALSE; + if (point_x > max_x || point_y > max_y) return FALSE; + return TRUE; +}; + +// TODO better naming +float(vector point, vector pos, vector size) is_2dpoint_in_bbox = +{ + return is_2dpoint_in_bounds(point, pos, pos + size); +}; + +void() _action_element_count_sanity = +{ + if (_action_elements_index > MAX_ACTION_ELEMENTS) + { + // let the user know if they're hitting the bounds + printf("^3sui warning: amount of action elements = %.0f exceeds MAX_ACTION_ELEMENTS = %.0f, consider increasing MAX_ACTION_ELEMENTS\n", _action_elements_index, MAX_ACTION_ELEMENTS); + } +}; + +const float MAX_MOUSE_ACTIONS = 16; +PanelID _hover_actions[MAX_MOUSE_ACTIONS]; +PanelID _click_actions[MAX_MOUSE_ACTIONS]; +PanelID _hold_actions[MAX_MOUSE_ACTIONS]; +PanelID _release_actions[MAX_MOUSE_ACTIONS]; +PanelID _last_clicked_actions[MAX_MOUSE_ACTIONS]; + +float _hover_action_count; +float _click_action_count; +float _hold_action_count; +float _release_action_count; +float _last_clicked_action_count; + + +// Resets things you might want to persist normally +void() sui_reset_actions = +{ + _hover_action_count = 0; + _click_action_count = 0; + _hold_action_count = 0; + _release_action_count = 0; + _last_clicked_action_count = 0; + _holding_click = FALSE; +}; + +// Per frame reset? +void() sui_reset_click = +{ + _hold_action_count = 0; + _click_action_count = 0; + _holding_click = FALSE; +}; + +float() sui_click_held = { return _holding_click; }; + + +// click: on mouse1 button down AND button op, once + +// Returns true if id was the topmost click (what usually is cared about the most) +float(PanelID id) sui_is_clicked = +{ + return _click_action_count > 0 && _click_actions[0] == id; +}; + +// hover: mouse is on top of the action element id + +float(PanelID id) sui_is_hovered = +{ + return _hover_action_count > 0 && _hover_actions[0] == id; +}; + +float(PanelID id) sui_hover_index = +{ + for (int i = 0; i < _hover_action_count; i++) + { + if (_hover_actions[i] == id) return i; + } + return -1; +}; + + +// hold: mouse button was clicked on top of this id and is held down, but not necessarily over this id anymore + +float(PanelID id) sui_is_held = +{ + return _hold_action_count > 0 && _hold_actions[0] == id; +}; + +float sui_has_hold(PanelID id) { + for (float i = 0; i < _hold_action_count; i++) + if (_hold_actions[i] == id) + return TRUE; + + return FALSE; +} + +// last clicked: is this the last action element that was clicked, good for focusing on input boxes for example + +float(PanelID id) sui_is_last_clicked = +{ + return _last_clicked_action_count > 0 && _last_clicked_actions[0] == id; +}; + +// release: a thing was held, but now it was released, once + +float(PanelID id) sui_is_released = +{ + return _release_action_count > 0 && _release_actions[0] == id; +}; + +float(float num) mouse_action_sanity = +{ + if (num >= MAX_MOUSE_ACTIONS) + { + printf("^3sui warning: you have exceeded the amount of overlapping action elements with %.0f, MAX_MOUSE_ACTIONS = %.0f\n", num, MAX_MOUSE_ACTIONS); + return TRUE; + } + return FALSE; +}; + +// mouse move, mostly just update hovers + +void(vector pos) _sui_mouse_move = +{ + _cursor_position = pos; + _action_element_count_sanity(); + + // Reset hover, it'll be back to what it used to be before draw gets called if mouse is still on same element + _hover_action_count = 0; + + // Iterate front to back, so topmost element gets the click/hover + for (int i = min(MAX_ACTION_ELEMENTS, _action_elements_index) - 1; i >= 0; i--) + { + if (is_2dpoint_in_bbox(_cursor_position, _action_elements[i].pos, _action_elements[i].size)) + { + if (mouse_action_sanity(_hover_action_count)) break; + + if (_hover_action_count == 0) _cursor_relative_hover = _cursor_position - _action_elements[i].pos; + _hover_actions[_hover_action_count] = _action_elements[i].id; + _hover_action_count += 1; + } + } +}; + +// JERK ALERT: hard to pass input params for it without them just being the globals +// ... so it just straight up uses the globals... optimization +void() _sui_mouse1_down = +{ + // Cheap but it should work... + _cursor_click = _cursor_position; + _cursor_relative_click = _cursor_relative_hover; + for (int i = 0; i < _hover_action_count; i++) _hold_actions[i] = _hover_actions[i]; + _hold_action_count = _hover_action_count; + _holding_click = TRUE; + _last_clicked_action_count = 0; +}; + +void() _sui_mouse1_up = +{ + // Can't be cheap here, we have to get the action fn of the element anyway so.. + _action_element_count_sanity(); + + // Assume we won't hit anything + _click_action_count = 0; + _last_clicked_action_count = 0; + + // Iterate front to back, so topmost element gets the click/hover + for (int i = min(MAX_ACTION_ELEMENTS, _action_elements_index) - 1; i >= 0; i--) + { + // If the thing wasn't the same thing we started pressing down on, ignore + for (int j = 0; j < _hold_action_count; j++) + { + if (_hold_actions[j] == _action_elements[i].id) // yes this element was held + { + // Still in bounds? + if (is_2dpoint_in_bbox(_cursor_position, _action_elements[i].pos, _action_elements[i].size)) + { + if (mouse_action_sanity(_click_action_count)) break; + + // Register click + _click_actions[_click_action_count] = _action_elements[i].id; + _last_clicked_actions[_last_clicked_action_count] = _action_elements[i].id; + _click_action_count += 1; + _last_clicked_action_count += 1; + } + } + } + } + + // In case someone is keeping state on hold and wants to do stuff on release, even if cursor has moved + for (int i = 0; i < _hold_action_count; i++) _release_actions[i] = _hold_actions[i]; + _release_action_count = _hold_action_count; + _hold_action_count = 0; + _holding_click = FALSE; +}; + +void(vector pos, vector size, PanelID id, void(float index, vector click_ratios) action) sui_action_element = +{ + if (!_sui_draw_initialized) + { + print("^1sui error: adding sui elements before sui_pre_draw!\n^1 Always do your sui menus between sui_pre_draw and sui_draw!\n"); + } + if (_action_elements_index >= MAX_ACTION_ELEMENTS) + { + // Silently fail here, sui will let us know another way, increase the count + // so that the error in click/mousemove handlers prints correct numbers. + _action_elements_index += 1; + return; + } + + sui_transform_box(pos, size); + + if (_sui_is_clipping) + { + vector oldpos = pos; + pos_x = max(pos_x, _sui_clip_area_mins_x); + pos_y = max(pos_y, _sui_clip_area_mins_y); + + size -= pos - oldpos; + + size_x -= bound(0, (pos_x + size_x - _sui_clip_area_maxs_x), size_x); + size_y -= bound(0, (pos_y + size_y - _sui_clip_area_maxs_y), size_y); + } + + _action_elements[_action_elements_index].pos = pos; + _action_elements[_action_elements_index].size = size; + _action_elements[_action_elements_index].id = id; + _action_elements[_action_elements_index].action = action; + + _action_elements_index += 1; +}; + + +// Input related stuff + +string _sui_binding_command; +string _sui_binding_command_name; + +struct _input_t { + float char; + float scan; +}; +const float MAX_INPUTS = 64; + +_input_t _input_buffer[MAX_INPUTS]; +float _input_index; +float _input_length; + + +// probably good to use it like while (sui_get_input(char, scan)) { ... }; +float(__inout float char, __inout float scan) sui_get_input = +{ + if (_input_index >= _input_length) return FALSE; + + char = _input_buffer[_input_index].char; + scan = _input_buffer[_input_index].scan; + _input_index++; + + return TRUE; +}; + +// if 2 controls want to read the same input for some reason.. +void() sui_reread_input = +{ + _input_index = 0; +}; + +void() sui_clear_input = +{ + _input_length = 0; + _input_index = 0; +}; + +float(float char, float scan) _sui_add_input = +{ + // TODO check if input was listened, return FALSE if not + if (_input_length >= MAX_INPUTS) + { + printf("^3sui warning: exceeded amount of per frame inputs count MAX_INPUTS = %.0f\n" + "^3 - make sure sui_input_event isn't being called without sui_draw being called in update loop\n" + "^3 - consider increasing MAX_INPUTS\n", MAX_INPUTS); + return TRUE; + } + _input_buffer[_input_length].char = char; + _input_buffer[_input_length].scan = scan; + _input_length += 1; + return TRUE; +}; + +// Listen to a certain keycode if it was pressed, this way sui know it was requested +float(float keycode) sui_listen_keycode_down = +{ + return FALSE; +}; + +// all text that was input between last and current frame +string() sui_listen_text_input = +{ + return ""; +}; + +void(float char, float scan, __inout string text, __inout float cursor) sui_handle_text_input = +{ + float maxlen = 128; + + string prev = text; + string pre_cursor, post_cursor; + float length = strlen(prev); + if (char > 31 && char < 128) //an actual input + { + if (length >= maxlen) return; + pre_cursor = substring(prev, 0, cursor); + post_cursor = substring(prev, cursor, length); + + text = sprintf("%s%s%s", pre_cursor, chr2str(char), post_cursor); + cursor += 1; + } + else if (char == 8) // backspace + { + if (cursor <= 0) return; + pre_cursor = substring(prev, 0, cursor - 1); + post_cursor = substring(prev, cursor, length); + cursor -= 1; + cursor = max(0, cursor); + text = strcat(pre_cursor, post_cursor); + } + else if (scan == K_DEL) + { + if (cursor >= length) return; + pre_cursor = substring(prev, 0, cursor); + post_cursor = substring(prev, cursor + 1, length); + text = strcat(pre_cursor, post_cursor); + } + else if (char == 13 || char == 27) // enter or escape + { + // Commit and deselect... + // Let's try a hack.. + _last_clicked_action_count = 0; + } + else if (scan == K_LEFTARROW) + { + cursor -= 1; + cursor = max(0, cursor); + } + else if (scan == K_RIGHTARROW) + { + cursor += 1; + cursor = min(strlen(prev), cursor); + } +}; + +void(float maxlen, __inout string text, __inout float cursor) sui_cap_input_length = +{ + if (strlen(text) > maxlen) + { + text = substring(text, 0, strlen(text)); + cursor = strlen(text); + } +}; + +void(string command) _sui_unbind = +{ + tokenize(findkeysforcommand(command)); + string keyname = keynumtostring(stof(argv(0))); + string altkeyname = keynumtostring(stof(argv(1))); + localcmd(sprintf("unbind %s\n", keyname)); + localcmd(sprintf("unbind %s\n", altkeyname)); +}; + +void(float scan, string command) _sui_do_keybind = +{ + if (scan == K_ESCAPE) + { + _sui_binding_command = ""; + _sui_binding_command_name = ""; + return; + } + if (scan == K_BACKSPACE) + { + _sui_unbind(command); + _sui_binding_command = ""; + _sui_binding_command_name = ""; + return; + } + string keyname = keynumtostring(scan); + _sui_unbind(command); + localcmd(sprintf("bind %s %s\n", keyname, command)); + _sui_binding_command = ""; + _sui_binding_command_name = ""; +}; + +void(string command, string command_name) sui_start_bind = +{ + _sui_binding_command = command; + _sui_binding_command_name = command_name; +}; + +// void(float evtype, float scanx, float chary, float devid) sui_input_event +// same args is CSQC_InputEvent. +// return value tells you if sui used the event or not, in case you want to +// not let engine handle it if it was used. +// Sets all the internal sui action stuff, call it in CSQC_InputEvent +float(float evtype, float scanx, float chary, float devid) sui_input_event = +{ + switch (evtype) + { + case IE_MOUSEABS: + _sui_mouse_move([scanx, chary]); + return TRUE; + case IE_MOUSEDELTA: + // Big question mark... + // maybe make our own delta based sui_cursor here.. + // maybe just ignore delta and let user fake mouseabs with their own + // delta cursor by passing different params to this func...? + // for MVP let's just use mouseabs only + return FALSE; + case IE_KEYDOWN: + if (_sui_binding_command != "") + { + // Nothing + return TRUE; + } + else if (scanx == K_MOUSE1) + { + _sui_mouse1_down(); + return TRUE; + } + else + { + if ((scanx == K_ESCAPE || scanx == K_BACKSPACE) && _sui_binding_command != "") + return TRUE; + else if (scanx == K_ESCAPE) + return FALSE; + return _sui_add_input(chary, scanx); + } + case IE_KEYUP: + if (_sui_binding_command != "") + { + _sui_do_keybind(scanx, _sui_binding_command); + return TRUE; + } + else if (scanx == K_MOUSE1) + { + _sui_mouse1_up(); + return TRUE; + } + break; + default: + break; + } + + return FALSE; +}; + + +// void() sui_pre_draw +// Resets state for sui actions so that no trouble happens. +// Call it before your menu code per frame in your draw/updateview. +void(float width, float height) sui_begin = +{ + _action_elements_index = 0; + _sui_draw_initialized = TRUE; + + sui_reset_frame(); + sui_push_frame([0, 0], [width, height]); +} + +void() sui_draw_bind_overlay; + +// void() sui_end +// Call after your menu code per frame in your draw/updateview. +void() sui_end = +{ + // Todo: move overlay drawing elsewhere: + sui_draw_bind_overlay(); + // Dirty part: + _sui_draw_initialized = FALSE; + // reset "once" type actions + _click_action_count = 0; + _release_action_count = 0; + // empty input buffer + sui_clear_input(); +}; + + +// Different draw components: + +void(vector pos, vector size, vector color, float alpha, float flags) sui_fill = +{ + sui_transform_box(pos, size); + HRC_drawfill(pos, size, color, alpha, flags); +}; + +void(vector pos, vector size, string pic, vector color, float alpha, float flags) sui_pic = +{ + sui_transform_box(pos, size); + + HRC_drawpic(pos, pic, size, color, alpha, flags); +}; + +void(vector pos, vector size, float width, vector color, float alpha, float flags) sui_border_box = +{ + sui_transform_box(pos, size); + + // Top line + HRC_drawfill(pos, [size_x, width], color, alpha, flags); + // Bottom line + HRC_drawfill([pos_x, pos_y + size_y - width], [size_x, width], color, alpha, flags); + // Left line + HRC_drawfill([pos_x, pos_y + width], [width, size_y - width * 2], color, alpha, flags); + // Right line + HRC_drawfill([pos_x + size_x - width, pos_y + width], [width, size_y - width * 2], color, alpha, flags); +}; + + +void(vector pos, vector size, string text, vector color, float alpha, float flags) sui_text = +{ + sui_transform_box(pos, [stringwidth(text, 1, size), size_y]); + + HRC_drawstring(pos, text, size, color, alpha, flags); +}; + +void(float index, vector click_ratios) sui_noop = {}; + +void(float value) sui_slider_noop = {}; + +float(PanelID id, vector pos, vector size, vector minmaxsteps, float value, void(float value) action) sui_slidercontrol = +{ + sui_action_element(pos, size, id, sui_noop); + float newvalue = value; + + sui_transform_box(pos, size); + // user is clicking and holding the slider + if (sui_is_held(id)) + { + float min = minmaxsteps[0]; + float max = minmaxsteps[1]; + float steps = minmaxsteps[2]; + float click_ratio = (_cursor_position_x - pos_x) / size_x; + click_ratio = bound(0, click_ratio, 1); + if (steps > 0) click_ratio = rint(click_ratio * steps) / steps; + newvalue = min + click_ratio * (max - min); + if (newvalue != value) action(newvalue); + } + return newvalue; +}; + +void(PanelID id, vector pos, vector size, __inout string text, __inout float cursor) sui_text_input = +{ + sui_action_element(pos, size, id, sui_noop); + if (sui_is_clicked(id)) cursor = strlen(text); + if (sui_is_last_clicked(id)) + { + float char = 0; + float scan = 0; + while(sui_get_input(char, scan)) sui_handle_text_input(char, scan, text, cursor); + } +}; + +FO_Hud_Panel* (PanelID panelid) getHudPanel; + +void(PanelID id, vector size, vector contentsize, __inout vector offset, vector scrollbar_widths) sui_scrollbar = +{ + vector maxoffset = contentsize - size; + maxoffset_x = max(0, maxoffset_x); + maxoffset_y = max(0, maxoffset_y); + sui_push_frame([0, 0], size); + float ofs; + float length; + vector barpos, barsize; + float scan = 0; + float char = 0; + if (maxoffset_y > 0 && contentsize_y > 0) + { + sui_set_align([SUI_ALIGN_END, SUI_ALIGN_START]); + sui_push_frame([0, 0], [scrollbar_widths_y, size_y]); + ofs = (offset_y / contentsize_y) * size_y; + length = (size_y / contentsize_y) * size_y; + barpos = [0, ofs]; + barsize = [scrollbar_widths_y, length]; + + if (sui_is_held(id)) + { + vector anchor = barpos + _cursor_relative_click; + sui_transform_point(anchor); + float diff = _cursor_position_y - anchor_y; + offset_y += (diff * contentsize_y) / size_y; // * contentsize_y; // (size_y / contentsize_y); + } + + sui_fill(barpos, barsize, '0.1 0.1 0.1' * (1 - sui_is_hovered(id)), 0.66, 0); + sui_action_element(barpos, barsize, id, sui_noop); + sui_pop_frame(); + } + sui_pop_frame(); +}; + +void(PanelID id, vector pos, vector size, vector contentsize, __inout vector offset, vector scrollbar_widths) sui_scroll_view_begin = +{ + // make space for scrollbars + sui_push_frame(pos, size - [scrollbar_widths_y, scrollbar_widths_x]); + sui_action_element([0, 0], size, id, sui_noop); + + if (sui_hover_index(id) > -1) + { + float scrollamount = 0; + float char = 0; + float scan = 0; + sui_reread_input(); + while (sui_get_input(char, scan)) + { + if (scan == K_MWHEELUP) scrollamount -= 20; + if (scan == K_MWHEELDOWN) scrollamount += 20; + } + offset_y += scrollamount; + } + + vector maxoffset = contentsize - size; + maxoffset_x = max(0, maxoffset_x); + maxoffset_y = max(0, maxoffset_y); + offset_x = bound(0, offset_x, maxoffset_x); + offset_y = bound(0, offset_y, maxoffset_y); + + sui_scrollbar(id, size, contentsize, offset, scrollbar_widths); + + offset_x = bound(0, offset_x, maxoffset_x); + offset_y = bound(0, offset_y, maxoffset_y); + + sui_clip_to_frame(); + + + sui_push_frame(-1 * offset, contentsize); +}; + +void() sui_scroll_view_end = +{ + sui_pop_frame(); + sui_reset_clip(); + sui_pop_frame(); +}; + +float _sui_list_item_height; +float _sui_list_first; +float _sui_list_last; +float _sui_list_pos; +int _sui_list_index; +void(PanelID id, vector pos, vector size, vector itemsize, float numitems, __inout vector offset, vector scrollbar_widths) sui_list_view_begin = +{ + vector contentsize = [itemsize_x, itemsize_y * numitems]; + sui_scroll_view_begin(id, pos, size, contentsize, offset, scrollbar_widths); + + _sui_list_item_height = itemsize_y; + float hidden_above = floor(offset_y / itemsize_y); + _sui_list_first = max(0, hidden_above); // Index of first elem + _sui_list_last = min(_sui_list_first + rint(size_y / itemsize_y) + 1, numitems); + _sui_list_pos = hidden_above * itemsize_y; + _sui_list_index = _sui_list_first; +}; + +float(__inout vector pos) sui_list_item = +{ + if (_sui_list_index >= _sui_list_last) return -1; + pos = _sui_list_index * [0, _sui_list_item_height]; + _sui_list_index += 1; + return _sui_list_index - 1; +}; + +void() sui_list_view_end = +{ + sui_scroll_view_end(); +}; + +string(PanelID id, vector pos, vector size, string name, string command) sui_binder = +{ + sui_action_element(pos, size, id, sui_noop); + if (sui_is_released(id)) + { + sui_start_bind(command, name); + } + + tokenize(findkeysforcommand(command)); + string keyname = keynumtostring(stof(argv(0))); + if (keyname == "01") keyname = "unbound"; + + return keyname; +}; + +void() sui_draw_bind_overlay = +{ + if (_sui_binding_command != "") + { + vector size = sui_current_frame_size(); + sui_fill([0, 0], size, '0 0 0', 0.5, 0); + sui_set_align([SUI_ALIGN_CENTER, SUI_ALIGN_CENTER]); + float textsize = 16; + sui_text([0, -16], [textsize, textsize], "Press a key for", '1 1 1', 1, 0); + sui_text([0, 0], [textsize, textsize], sprintf("'%s'", _sui_binding_command_name), '1 1 1', 1, 0); + sui_text([0, 16], [textsize - 4, textsize - 4], "ESC to cancel, BACKSPACE to remove", '1 1 1', 1, 0); + } +}; + +/* + * Flags: 1 = No Border + * + */ +float(PanelID id, vector pos, vector size, string text, vector colour, vector textsize, vector textcolour, float alignment, float padding, float alpha, float textalpha, float flags) hud_colour_button = +{ + sui_push_frame(pos, size); + vector basecolor = sui_is_hovered(id) ? colour + MENU_HIGHLIGHT * 0.1 : colour; + vector bordercolour = colour + '0.05 0.05 0.05'; + basecolor = sui_is_held(id) ? colour - MENU_DARKEN * 0.1 : basecolor; + sui_fill([0, 0], size, basecolor, alpha, 0); + if(!(flags & 1)) { + sui_border_box([0, 0], size, 1, bordercolour, 0.4, 0); + } + sui_set_align([alignment, SUI_ALIGN_CENTER]); + if(!padding && alignment == SUI_ALIGN_START) { + padding = 4; + } + sui_text([padding, 0], textsize, text, textcolour, textalpha, 0); + sui_action_element([0, 0], size, id, sui_noop); + sui_pop_frame(); + + return sui_is_clicked(id); +}; + +float(PanelID id, vector pos, vector size, string text) hud_button = { + return hud_colour_button(id, pos, size, text, MENU_BUTTON, MENU_TEXT_SMALL, MENU_TEXT_1, SUI_ALIGN_CENTER, 0, 0.6, 1, 0); +} + + +// -------------------- END OF SUI SYSTEM STUFF -------------------- diff --git a/csqc/tfx.qc b/csqc/tfx.qc new file mode 100644 index 000000000..e8c4d13ad --- /dev/null +++ b/csqc/tfx.qc @@ -0,0 +1,356 @@ +.entity outline; +.float grentimer_expires; // TODO: integrate this +.float teamno; +static entity local_player; +static float shader_team[2], shader_flag, shader_over_outline; +float update_sentry_angles; +float sentry_angles_y; + +enum { + TeamNone = 0, + TeamBlue = 1, + TeamRed = 2, +}; + + +static float FlagCarried() { // Just awful. + for (int i = 0; i < FlagInfoLines.length; i++) + if (FlagInfoLines[i].state == FLAGINFO_CARRIED) + return TRUE; + return FALSE; +} + +// Until we network player entities we need somewhere to stash things. +struct EntHash { + int entnum; + float grentimer_expires; +}; +static EntHash ent_hash[43]; + +static EntHash* EntGet(int ent_num) { + EntHash* he = __NULL__; + + for (int i = 0; i < ent_hash.length; i++) { + he = &ent_hash[(i + ent_num) % ent_hash.length]; + if (he->entnum == ent_num) + return he; + else if (!he->entnum) + break; + } + + he->entnum = ent_num; + return he; +} + +static float TeamNo(entity player) { + // This is horrific, but the most immediately accessible thing we seem to + // have. getentity() is just unreliable. + float pc = stof(infokey(player, INFOKEY_P_BOTTOMCOLOR)); + switch (pc) { + case 13: return TeamBlue; + case 4: return TeamRed; + } + return TeamNone; +} + +static inline float AttackingTeamNo() { return getstatf(STAT_TEAMNO_ATTACK); } + +static float IsAttacking(entity player) { + /* return TeamNo(player) == getstatf(STAT_TEAMNO_ATTACK); */ + return player.teamno == AttackingTeamNo(); +} + +static float ShowPlayerOutline(entity player) { + if (player.entnum == player_localentnum || player.teamno == TeamNone) + return FALSE; + + if (prematch && TFxEnabled(TFX_PREMATCH_EVERYTHING)) + return TRUE; + + if (!game_state.is_player) + return TFxEnabled(TFX_SPEC_OUTLINE); + + if ((TFxEnabled(TFX_DEFENSE_OUTLINE) && !IsAttacking(player)) || + (TFxEnabled(TFX_OFFENSE_OUTLINE) && IsAttacking(player))) + return player.teamno == local_player.teamno; + + return FALSE; +} + +static void RemoveOutline() { + if (self.outline != __NULL__) + remove(self.outline); +} + +static void AddOutline(entity ent) { + entity o = spawn(); + setmodelindex(o, ent.modelindex); + o.classname = "outline"; + o.fatness = 1; + setsize(o, PLAYER_MINS, PLAYER_MAXS); + + ent.outline = o; + ent.removefunc = RemoveOutline; +} + +static void UpdateOutline(entity player) { + entity o = player.outline; + if (!ShowPlayerOutline(player)) { + o.drawmask = 0; + self.forceshader = 0; + return; + } + + self.forceshader = shader_over_outline; + o.forceshader = shader_team[TeamNo(player) - TeamBlue]; + o.drawmask = MASK_ENGINE; + o.frame = player.frame; + o.angles = player.angles; + if (player == local_player) + o.renderflags |= RF_EXTERNALMODEL; + setorigin(o, player.origin); + +} + +float UpdatePlayer(float isnew) { + if (isnew) { + if (self.entnum == player_localentnum && game_state.is_player) + local_player = self; + + AddOutline(self); + } + + if (self.entnum == player_localentnum) + self.renderflags |= RF_EXTERNALMODEL; + else + self.renderflags &= ~RF_EXTERNALMODEL; + + self.teamno = TeamNo(self); + + UpdateOutline(self); + return TRUE; +} + +static float ShowTeamGrenTimer(float team, float my_team) { + if (!game_state.is_player && TFxEnabled(TFX_SPEC_GRENTIMER)) + return TRUE; + + if (prematch && TFxEnabled(TFX_PREMATCH_EVERYTHING)) + return TRUE; + + if (team == AttackingTeamNo()) { + if (TFxEnabled(TFX_OFFENSE_GRENTIMER) && team == my_team) + return TRUE; + } else { + if (TFxEnabled(TFX_DEFENSE_GRENTIMER) && team == my_team) + return TRUE; + } + + return FALSE; +} + +static entity gren_list[20]; +static int num_gren_list; +static float next_grenlist_update; + +static float RenderGrenTimer(entity p, float test_only) { + const float maxd = 1000, mind = 200; + vector po = p.origin + '0 0 40'; + vector o = PM_Org(); + + if (p == local_player) + return FALSE; + + float diff = vlen(po - o); + if (diff > maxd) + return FALSE; + + traceline(o, po, 3, p); + if (trace_fraction < 1) + return FALSE; + + EntHash* he = EntGet(p.entnum); + float rem = he->grentimer_expires - time; + if (rem < 0) + return FALSE; + + if (test_only) + return TRUE; + + vector colors[] = { '1 0 0', '0.8 0.8 0.4', '0.8 0.5 0.5', '1 1 1'}; + vector c = project(po); + vector size = '8 8 8' * (maxd - max(diff, mind)) / (maxd - mind); + string str = ftos(ceil(rem)); + c.x -= stringwidth(str, FALSE, size) / 2; + drawstring(c, str, size, colors[floor(rem)], 1, 0); + + return TRUE; +} + +entity*(.__variant fld, __variant match, int type=EV_STRING, __out int count) find_list = #0:find_list; + +static void UpdateGrenList() { + if (time < next_grenlist_update) + return; + next_grenlist_update = time + 0.1; + num_gren_list = 0; + + float teams[] = {TeamRed, TeamBlue}; + + for (int i = 0; i < teams.length; i++) { + float team = teams[i]; + if (!ShowTeamGrenTimer(team, local_player.teamno)) + continue; + + int count; + entity* players = find_list(teamno, team, EV_FLOAT, count); + for (int j = 0; j < count; j++) { + entity p = players[j]; + + if (RenderGrenTimer(p, TRUE)) + gren_list[num_gren_list++] = p; + } + } +} + +void TFxRenderGrenadeTimers() { + UpdateGrenList(); + + for (int i = 0; i < num_gren_list; i++) { + entity p = gren_list[i]; + RenderGrenTimer(p, FALSE); + } +} + +static float ShowFlagOutline() { + if (!game_state.is_player && TFxEnabled(TFX_SPEC_SEEFLAG)) + return TRUE; + + if (prematch && TFxEnabled(TFX_PREMATCH_EVERYTHING)) + return TRUE; + + if (TFxEnabled(TFX_OFFENSE_SEEFLAG) && IsAttacking(local_player)) + return TRUE; + + if (TFxEnabled(TFX_DEFENSE_SEEFLAG) && !IsAttacking(local_player) && + !FlagCarried()) + return TRUE; + + return FALSE; +} + +static void UpdateFlagOutline(entity flag) { + entity o = flag.outline; + if (!ShowFlagOutline()) { + o.drawmask = 0; + return; + } + + o.forceshader = shader_flag; + o.drawmask = MASK_ENGINE; + o.angles = flag.angles; + o.frame = flag.frame; + setorigin(o, flag.origin); +} + +float UpdateFlag(float isnew) { + /* if (isnew) { */ + /* float mindex = self.modelindex; */ + /* AddOutline(self); */ + /* // Flags have their own shader already, just make them shiny for now. */ + /* } */ + + /* UpdateFlagOutline(self); */ + + LocalEffects(self.effects, self.origin); + + if (self.skin == team_no) { + local float distance = vlen(self.origin - PM_Org()); + self.alpha = min(0.25 + (distance / 200), 1); + } + + return TRUE; +} + +float UpdateSentry(float isnew) { + if (isnew) { + sentry_angles_y = self.angles_y; + } else { + if (update_sentry_angles > time) { + self.angles_y = sentry_angles_y; + } else { + sentry_angles_y = self.angles_y; + } + } + + return TRUE; +} + +float UpdateSentryBase(float isnew) { + if (isnew) { + sentry_angles_y = self.angles_y; + } else { + if (!SBAR.SentryLevel && SBAR.IsBuilding) { + if (update_sentry_angles > time) { + self.angles_y = sentry_angles_y; + } else { + sentry_angles_y = self.angles_y; + } + } + } + + return TRUE; +} + +void TFxGrenTimerUpdate(float ent_num, float expiry) { + EntHash* he = EntGet(ent_num); + he->grentimer_expires = expiry; +} + +static string OutlineShader(string texture) { + return sprintf( +"{\ + sort 6\ + {\ + map %s\ + rgbGen lightingDiffuse\ + blendfunc GL_ONE GL_ONE_MINUS_SRC_COLOR\ + tcgen environment\ + tcmod rotate 30\ + nodepthtest\ + depthfunc greater\ + }\ +}", texture); +} + +static string rnds(string prefix) { +#if 0 + // Debugging convenience, concatenates a random string when enabled so that + // shaders don't get cached on-client between builds. + for (int i = 0; i < 5; i++) + prefix = strcat(prefix, chr2str('a' + floor(random() * 26))); +#endif + return prefix; +} + +void TF_Init() { +#if 0 + Deltalisten breaks some default shaders even without a forceoverride. Quick + fix to unblock. + + deltalisten("progs/player.mdl", UpdatePlayer, 0); + deltalisten("progs/ff_flag.mdl", UpdateFlag, 0); + deltalisten("progs/tf_flag.mdl", UpdateFlag, 0); + + shader_team[0] = shaderforname(rnds("blue_team_outline"), OutlineShader("textures/sfx/quad.tga")); + shader_team[1] = shaderforname(rnds("red_team_outline"), OutlineShader("textures/sfx/pent.tga")); + shader_flag = shaderforname(rnds("flag_outline"), OutlineShader("textures/sfx/specular.tga")); + + shader_over_outline = shaderforname(rnds("over_outline"), "{ sort 7 }"); +#endif + deltalisten("progs/ff_flag.mdl", UpdateFlag, 0); + deltalisten("progs/tf_flag.mdl", UpdateFlag, 0); + deltalisten("progs/tf_stan.mdl", UpdateFlag, 0); + deltalisten("progs/turrgun.mdl", UpdateSentry, 0); + deltalisten("progs/turrbase.mdl", UpdateSentryBase, 0); +} diff --git a/csqc/vote.qc b/csqc/vote.qc new file mode 100644 index 000000000..e115c0af6 --- /dev/null +++ b/csqc/vote.qc @@ -0,0 +1,108 @@ +void () ApplyMapFilter = { + entity mc = find(world, classname, "map_candidate_filtered"); + entity temp = world; + while(mc) { + remove(mc); + mc = find(mc, classname, "map_candidate_filtered"); + } + num_mapvotes_filtered = 0; + mc = find(world, classname, "map_candidate"); + while(mc) { + if(strstrofs(mc.name, vote_list_filter) > -1) { + temp = spawn(); + temp.classname = "map_candidate_filtered"; + temp.owner = mc; + //temp.name = mc.name; + //temp.description = mc.description; + //temp.groupname = mc.groupname; + //temp.group = mc.group; + //temp.team_num = mc.team_num; + //temp.min_val = mc.min_val; + //temp.max_val = mc.max_val; + //temp.localmap = mc.localmap; + num_mapvotes_filtered++; + } + mc = find(mc, classname, "map_candidate"); + } +} + +/* +"name" "amth1" //map name +"description" "Duel arena, ctf and Sniper War" //description +"groupname" "Duel/Arena" //group +"min_val" "2" //min players +"max_val" "8" //max players +"team_num" "4" //supported teams + +.cnt = number of current votes +*/ + +entity (string n) AddVoteMapGroup = { + if(!n) { + return world; + } + local entity mg = find(world, classname, "map_group"); + while(mg) { + if(mg.netname == n) { + return mg; + } + mg = find(mg, classname, "map_group"); + } + + mg = spawn(); + mg.classname = "map_group"; + mg.name = n; + return mg; +}; + +entity (string n, string desc, string mapgroup, float num_teams, float min_players, float max_players, float islocal) AddVoteMap = { + if(!n) { + return world; + } + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(mc.name == n) { + return mc; + } + mc = find(mc, classname, "map_candidate"); + } + + mc = spawn(); + mc.classname = "map_candidate"; + mc.name = n; + mc.description = desc; + mc.groupname = mapgroup; + mc.group = AddVoteMapGroup(mapgroup); + mc.team_num = num_teams?num_teams:2; + mc.min_val = min_players?min_players:6; + mc.max_val = max_players?max_players:16; + mc.localmap = islocal; + + num_mapvotes++; + ApplyMapFilter(); + return mc; +}; + +float (string n, float isLocal) RemoveVoteMap = { + if(!n) { + return FALSE; + } + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(mc.name == n) { + if(isLocal || isLocal == mc.localmap) { + if(current_vote == mc) { + current_vote = world; + } + remove(mc); + num_mapvotes--; + ApplyMapFilter(); + return TRUE; + } else { + return FALSE; + } + } + mc = find(mc, classname, "map_candidate"); + } + return FALSE; +} diff --git a/csqc/weapon_predict.qc b/csqc/weapon_predict.qc new file mode 100644 index 000000000..a5ca0a9be --- /dev/null +++ b/csqc/weapon_predict.qc @@ -0,0 +1,2201 @@ +DEFCVAR_FLOAT(wpp_debug, 0); + +DEFCVAR_FLOAT(fo_wpp_beta, 0); +DEFCVAR_FLOAT(fo_predict_weapons, 1); +DEFCVAR_FLOAT(fo_predict_projectiles, 1); + +DEFCVAR_FLOAT(fo_pmove, 0); +DEFCVAR_FLOAT(fopmd_force_pos, 0); + +DEFCVAR_FLOAT(fo_client_sniper_sight, 1); + +DEFCVAR_FLOAT(wpp_min_ping, -1); + +DEFCVAR_FLOAT(wpp_adv_selfp_ms, 0); +DEFCVAR_FLOAT(wpp_adv_otherp_ms, 0); + +DEFCVAR_FLOAT(r_drawviewmodel, 1); +DEFCVAR_FLOAT(fo_reloadalpha, 0); +DEFCVAR_FLOAT(fo_reloadvolume, 0); + +// Alpha of weapon when unable to attck (e.g. time < attack_finished). +// Default: -1 means no change to alpha when in this state. +DEFCVAR_FLOAT(fo_nofirealpha, -1); + +// HueTF style -- does not wait for attack finished to render the queued weapon; +// purely visual, still have to wait for attack_finished. +DEFCVAR_FLOAT(fo_hue_weaponswap, 0); + +static float wp_ready; + +struct pengine_t { + float pp_enabled; + float wp_enabled; + float pm_enabled; + float is_effectframe; + float last_effectframe; + + float player_entnum; // player_localentnum iff is_player == 1 + entity player_ent; + entity pweap_ent; + entity viewmodel; + + // Must include MASK_PRED_VIEWMODEL or we lose updates. + // We only add/remove MASK_VIEWMODEL and MASK_PRED_PROJECTILE + float view_mask; +} pengine; + +static float inst_ping_ms; +static float inst_ping_t; +static float last_still_loading; + +inline float PP_Enabled() { return pengine.pp_enabled; } +inline float WP_Enabled() { return pengine.wp_enabled; } +inline float WPP_ViewModelMask() { return pengine.view_mask; } + +inline float PM_Enabled() { return pengine.pm_enabled; } + +float WP_PlayerClass() { + return pstate_server.playerclass; +} + +float WP_MinPing() { + if (CVARF(wpp_min_ping) == -1) + return fo_config.wp_default_min_ping_ms; + else + return CVARF(wpp_min_ping); +} + +void WP_UpdateViewModel(); + +inline float server_time_ms() { + return (pstate_pred.seq - pstate_server.seq) * SERVER_FRAME_MS; +} + +inline float server_time_dt() { + return server_time_ms() / 1000.0; +} + +inline float server_time() { + return time + server_time_dt(); +} + +DEFCVAR_FLOAT(cl_predict_players, 1); +DEFCVAR_FLOAT(cl_predict_players_latency, 0.9); +DEFCVAR_FLOAT(cl_predict_players_nudge, 0.02); +DEFCVAR_FLOAT(cl_predict_players_frac, 0.9); + +static float pred_time_dt; // delta-time that the world predicted forward by + +// Allows temporarily disabling prediction around events that we do not yet +// fully implement a client-side state machine for, e.g. priming a detpack. +static float filter_pproj_time; + +// Match up local interpolation of projectile position with that of players. +static void update_interp_time_dt() { + if (!CVARF(cl_predict_players)) { + pred_time_dt = 0; + return; + } + + // Maybe prefer smoothed value here? + pred_time_dt = inst_ping_t; + + if (CVARF(cl_predict_players_latency) <= 1) + pred_time_dt *= 1 - CVARF(cl_predict_players_latency); + pred_time_dt *= CVARF(cl_predict_players_frac); + + pred_time_dt += CVARF(wpp_adv_selfp_ms) + CVARF(cl_predict_players_nudge); + pred_time_dt = min(pred_time_dt, inst_ping_t); +} + +// Unlike server_time(), interp_time() is continuous and reflects how much we've +// projected the local copy of the world forward. Monotonic. +float interp_time() { + static float last_interp_time = 0; + + last_interp_time = max(last_interp_time, time + pred_time_dt); + return last_interp_time; +} + +#define csqc_print(...) \ + do { if (CVARF(wpp_debug) & 4) { \ + print("CSQC: ", __VA_ARGS__); \ + } else { print(__VA_ARGS__); } } while(0) + +DECLARE_MOVING_AVG(avg_ping, 20); + +string getPingPanelText() { + float minv, maxv, avg, varr; + + read_online_avg(&avg_ping, &avg, &varr); + compute_maxmin(&avg_ping.samples, &minv, &maxv); + float pl = getplayerkeyfloat(player_localnum, INFOKEY_P_PACKETLOSS); + + return sprintf("^7%d^1/^7%d^1/^7%d ^8ms ^7%d^8%%\n", minv, avg, maxv, pl); +} + +static void update_avg_ping() { + // We use a fairly low clamp here because: + // a) We want to limit the effect of momentary total loss + // b) We actually want high pings to have low effective variance and + // trigger it on consistently. + float newv = min(inst_ping_ms, 300); + + update_online_avg(&avg_ping, newv); +} + +static float fill_avg_ping(float* mean, float* variance) { + return read_online_avg(&avg_ping, mean, variance); +} +float csqc_get_user_setting(string s_short, string s_long, string def); + +// Order of preference: +// server disable, client enable/disable, server/ping enable +static float CalcPredEnabled(float current_enable, float cvar, + float server_disable, float dependent_enabled, + string* set_by) { + + if (!dependent_enabled) { + // PP depends on WP, so !WP => !PP + *set_by = "[weapon prediction disabled]"; + return FALSE; + } + + if (game_state.is_spectator) { + *set_by = "[spectator]"; + return FALSE; + } + + if (server_disable) { + *set_by = "[server disable]"; + return FALSE; + } + + if (!cvar) { + *set_by = "[disabled]"; + return FALSE; + } + + *set_by = "[default]"; + return TRUE; + +#if 0 + + if (WP_MinPing() == 0) + return current_enable; + + float ping_enable, avg, variance; + + fill_avg_ping(&avg, &variance); + + if (variance > sq(15)) { + *set_by = "[ping not yet stable]"; + return current_enable; // variance too high, wait. + } + + static float hyst = 0; + ping_enable = avg > WP_MinPing() - hyst; + if (ping_enable != current_enable) { + if (!hyst) // avoid flip-flopping on boundary values. + hyst = 10; + else + hyst += 5; + } + + if (ping_enable) { + *set_by = sprintf("[ping >%d] (avg=%0.0f var=%0.0f)", + WP_MinPing(), avg, variance); + return TRUE; + } else { + *set_by = sprintf("[lan mode <%d]", WP_MinPing()); + return FALSE; + } +#endif +} + +static float client_delay_packets; +void(float seat, string keyname, string newvalue) setlocaluserinfo = #0:setlocaluserinfo; + +#define PRINT_CONFIG(_field) \ + printf(" ." #_field " = %d\n", fo_config.##_field) +#define PRINT_CONFIG_ACTIVE(_field) \ + if (fo_config.##_field) printf(" ." #_field " = %d\n", fo_config.##_field) + +static void PrintFlagField(string desc, float bits, float nbits, + string* bit_desc) { + if (!bits) + return; + + printf(desc, bits); + for (float i = 0; i < nbits; i++) { + float flag = 1 << i; + string enabled = (bits & flag) ? "on" : "off"; + printf(" %3d [%-3s]: %s\n", flag, enabled, bit_desc[i]); + } + printf("\n"); +} + +void WPP_Status() { + string wp_source, pp_source; + float wp_enabled = pengine.wp_enabled, pp_enabled = pengine.pp_enabled; + + CalcPredEnabled(wp_enabled, CVARF(fo_predict_weapons), + fo_config.wp_global_disable, TRUE, &wp_source); + + CalcPredEnabled(pp_enabled, CVARF(fo_predict_projectiles), + fo_config.wp_global_disable, wp_enabled, &pp_source); + + printf("Prediction Status:\n"); + printf(" Weapon prediction %s %s\n", + wp_enabled ? "enabled" : "disabled", wp_source); + printf(" Projectile prediction %s %s\n", + pp_enabled ? "enabled" : "disabled", pp_source); + + float avg, variance; + fill_avg_ping(&avg, &variance); + printf(" inst_ping = %d client_ping = %d avg_ping = %d var = %d\n", + inst_ping_ms, pstate_server.client_ping, avg, variance); + if (fo_config.min_ping_ms) + printf(" client_delay_packets = %d\n", client_delay_packets); + printf(" maxspeed = %d\n", pstate_server.csqc_maxspeed); + printf("\n"); + + printf("Config:\n"); + PRINT_CONFIG(min_ping_ms); + PRINT_CONFIG(qc_physics); + PRINT_CONFIG(static_newmis_ms); + PRINT_CONFIG(dynamic_newmis_ms); + PRINT_CONFIG(wp_default_min_ping_ms); + PRINT_CONFIG_ACTIVE(wp_global_disable); + PRINT_CONFIG_ACTIVE(gren_beta_disable); + PRINT_CONFIG_ACTIVE(old_ng_rof); + PRINT_CONFIG(max_rewind_ms); + PRINT_CONFIG(max_rewind_slow_projectile_ms); + PRINT_CONFIG(max_rewind_fast_projectile_ms); + PRINT_CONFIG(max_rewind_grenade_ms); + PRINT_CONFIG(rewind_fast_projectile_thresh); + PRINT_CONFIG(new_balance); + printf("\n"); + + PrintFlagField("Rewind Settings [rewind_flags=%d]\n", + fo_config.rewind_flags, REWIND_DESC.length, REWIND_DESC); + + PrintFlagField("TFX [tfx_flags=%d]:\n", fo_config.tfx_flags, + TFX_DESC.length, TFX_DESC); + + PrintFlagField("Clown mode [clown_flags=%d]\n", + fo_config.clown_flags, CLOWN_DESC.length, CLOWN_DESC); + + PrintFlagField("NewBalance Flags [newbalance_flags=%d]:\n", + fo_config.new_balance_flags, NBF_DESC.length, NBF_DESC); + + PrintFlagField("Concs [fo_concuss=%d]\n", + fo_config.fo_concuss, FO_CONC.length, FO_CONC); +} +#undef PRINT_CONFIG + +void WPP_UpdateEnable(float force) { + static float next_ping_update; + static float PING_PERIOD = 0.250; + + if (time > next_ping_update) { + update_avg_ping(); + update_interp_time_dt(); + next_ping_update = time + PING_PERIOD / avg_ping.samples.max_count; + } + + if (!wp_ready) + return; + + // Immediate updates any time there's a cvar change. + static float next_wpp_enable_check; + static float last_fo_predict_weapons, last_fo_predict_projectiles; + + if (CVARF(fo_predict_weapons) != last_fo_predict_weapons || + CVARF(fo_predict_projectiles) != last_fo_predict_projectiles) { + next_wpp_enable_check = 0; + last_fo_predict_weapons = CVARF(fo_predict_weapons); + last_fo_predict_projectiles = CVARF(fo_predict_projectiles); + } + +#ifdef 0 + if (avg_ping.samples.count < 5) + return; // Skip until we have a useful number of samples. +#endif + + if (!force && time < next_wpp_enable_check) + return; + next_wpp_enable_check = time + 1; + + float wp_enabled = pengine.wp_enabled, pp_enabled = pengine.pp_enabled; + float wp_new = -1, pp_new = -1; + string wp_source, pp_source; + + wp_new = CalcPredEnabled(wp_enabled, CVARF(fo_predict_weapons), + fo_config.wp_global_disable, TRUE, &wp_source); + + pp_new = CalcPredEnabled(pp_enabled, CVARF(fo_predict_projectiles), + fo_config.wp_global_disable, wp_new, &pp_source); + + static float once; + if (wp_new != pengine.wp_enabled || !once) { + if (!CVARF(fo_predict_projectiles) && game_state.is_player) + printf("FortressOne: Weapon prediction (no projectiles) %s %s\n", + wp_new ? "enabled" : "disabled", wp_source); + pengine.wp_enabled = wp_new; + + if (wp_new) { + entity player = edict_num(player_localentnum); + pengine.player_entnum = player_localentnum; + pengine.player_ent = player; + pengine.pweap_ent.owner = player; + } else { + pengine.player_entnum = -1; + } + } + + if (pp_new != pengine.pp_enabled || !once) { + if (game_state.is_player) + printf("FortressOne: Weapon/Projectile prediction %s %s\n", + pp_new ? "enabled" : "disabled", pp_source); + pengine.pp_enabled = pp_new; + + next_wpp_enable_check += 9; + } + once = TRUE; + + float wpp_status = 0; + if (pengine.wp_enabled) { + wpp_status |= CSQC_WEAP_PRED; + if (pengine.pp_enabled) + wpp_status |= CSQC_PROJ_PRED; + if (CVARF(fo_client_sniper_sight)) + wpp_status |= CSQC_SNIPER_SIGHT; + } + + // Set this independently of init above so that it can come up faster. + if (CVARF(fo_pmove)) + wpp_status |= CSQC_PMOVE; + if (CVARF(fopmd_force_pos)) + wpp_status |= CSQC_FORCE_POS; + + setlocaluserinfo(0, "fo_wpp_status", ftos(wpp_status)); + // We always need to "render" the view model to trigger predraw compute. + pengine.view_mask = MASK_PRED_ENT | MASK_VIEWMODEL | + (pp_new ? MASK_PRED_PROJECTILE : 0); + + if (pengine.viewmodel != __NULL__) + WP_UpdateViewModel(); +} + +DEFCVAR_FLOAT(fo_minping_min, 0); // Can be used to set a lower-bound on + // what minping will be chosen. +DEFCVAR_FLOAT(cl_delay_packets, 0); // Can be used to query, but not set. + +static void set_minping_delay_packets() { + localcmd(sprintf("cl_delay_packets %d\n", client_delay_packets)); + setlocaluserinfo(0, "client_delay_packets", ftos(client_delay_packets)); +} + +struct { + float last_val; + float minv, user_controlled; + float fo_min_ping; +} DelayPacketState; + +static void UpdateDelayPackets(float val, float user) { + string sv = sprintf("%d", val); + setlocaluserinfo(0, "csqc_dp", user ? "0" : "1"); + setlocaluserinfo(0, "client_delay_packets", sv); + + DelayPacketState.user_controlled = user; + DelayPacketState.last_val = val; + + if (val != CVARF(cl_delay_packets)) { + CVARF(cl_delay_packets) = val; + localcmd("cl_delay_packets ", sv, "\n"); + } +} + +void UpdateFoMinPing(string arg) { + float fmp = DelayPacketState.fo_min_ping; + + if (arg != "") { + float nv = stoi(arg); + + // Don't bother posting if we'll ignore it. + if (nv != fmp && nv > fo_config.min_ping_ms) + localcmd(sprintf("say SET: fo_min_ping %d\n", nv)); + + DelayPacketState.fo_min_ping = fmp = nv; + DelayPacketState.user_controlled = FALSE; + } + + float min_ping = max(fo_config.min_ping_ms, fmp); + printf("Min ping is: %d [%s specified]\n", min_ping, + min_ping == fmp ? "client" : "server"); +} + +string(float seat, string keyname) getlocaluserinfo = #0:getlocaluserinfo; +void ModulateDelayPackets() { + static float once; + + float dp = CVARF(cl_delay_packets), nv; + + if (!once) { + if (getlocaluserinfo(0, "csqc_dp") == "1") { + // Set from a previous game, clear. + UpdateDelayPackets(0, TRUE); + return; + } + + DelayPacketState.last_val = dp; + DelayPacketState.user_controlled = TRUE; + once = TRUE; + } + + if (dp != DelayPacketState.last_val && dp > DelayPacketState.minv) { + UpdateDelayPackets(dp, TRUE); + localcmd(sprintf("say SET: cl_delay_packets %d\n", dp)); + } else if (dp < DelayPacketState.minv || + (dp > DelayPacketState.minv && !DelayPacketState.user_controlled)) { + + if (dp != DelayPacketState.last_val) + localcmd(sprintf("say IGNORED: cl_delay_packets %d [< min of %d]\n", + dp, DelayPacketState.minv)); + UpdateDelayPackets(DelayPacketState.minv, FALSE); + } +} + + +void UpdateMinPing() { + static float next_ping_update, next_minv_update; + + ModulateDelayPackets(); + + float min_ping = max(fo_config.min_ping_ms, DelayPacketState.fo_min_ping); + + if (!min_ping) { + DelayPacketState.minv = 0; + return; + } + + // Ideally we want to span more than one update here to better handle + // averaging of clamping to server frames. + const float PING_SAMPLES = 50; + static float num_samples; + static float ping_cache[PING_SAMPLES]; + + if (time < next_ping_update || !game_state.is_player) + return; + next_ping_update = time + 0.2; + + float cache_index = (num_samples++) % ping_cache.length; + + float cping = inst_ping_ms; + ping_cache[cache_index] = max(0, cping - DelayPacketState.last_val); + + float true_avg = 0; + float N = min(num_samples, PING_SAMPLES); + for (int i = 0; i < N; i++) + true_avg += ping_cache[i]; + true_avg /= N; + + // Accuracy limitations hurt us here. + true_avg = max(true_avg, SERVER_FRAME_MS - 1); + + if (time < next_minv_update) + return; + + float old = DelayPacketState.minv; + float target = max(ceil(min_ping - true_avg), 0); + + if (target > 0) + target += 1; // Slightly bias up for stability and better equalization. + + if (fabs(target - old) >= 25) { + target = floor(target * 0.975); // Take big steps conservatively + next_ping_update = time + 3; // Time to reconverge + } else { + target = max(min(target, old + 3), old - 3); // Bound smaller updates. + next_ping_update = time + 3; // Time to reconverge + } + + DelayPacketState.minv = target; + next_minv_update = next_ping_update + 1; // Limit modulation +} + +void WP_UpdatePings() { + inst_ping_ms = getplayerkeyfloat(player_localnum, INFOKEY_P_PING); + inst_ping_t = inst_ping_ms / 1000.0; + + update_interp_time_dt(); + UpdateMinPing(); + WPP_UpdateEnable(FALSE); +} + +float IsEffectFrame() { + if (!pengine.is_effectframe) + return FALSE; + +#if 0 + // At ultra-low latencies it's possible that we're only one frame in front + // of the server. But this can mean that we get that frame back immediately + // (and our input applies to the one after it). + if (servercommandframe + 2 >= pengine.last_effectframe) { + pengine.last_effectframe++; + if (CVARF(wpp_debug) & 8) print("Effect bump!\n"); + } +#endif + return TRUE; +} + +// This covers frames that would have been effect frames, but were missed client +// side due to . It's too loose for things like lining up +// projectiles where we just take the miss, but for things like sound it's +// better to have minutely off than not play. +float IsEffectFrameMulti() { + return (pengine.is_effectframe || + (pstate_pred.seq > pengine.last_effectframe && + pstate_pred.seq < clientcommandframe)); +} + +void Attack_Finished(float attack_time) { + pstate_pred.attack_finished = pstate_pred.client_time + attack_time; +} +//////////////////////////////////////////////////////////////////////////////// +/// Weapon models +//////////////////////////////////////////////////////////////////////////////// +static entity pred_sound_entity; +static float last_pred_sound; + +static inline Slot CurrentSlot() { return pstate_pred.current_slot; } +static inline FO_WeapInfo* SlotWI(Slot slot) { + return FO_SlotWeapInfo(pstate_pred.playerclass, slot); +} + +inline FO_WeapInfo* WP_CurrentWeapon() { + return SlotWI(CurrentSlot()); +} + +void RawPred_Sound(SoundIndex snd, float channel, float vol = 1) { + if (snd == SND_NONE || pstate_pred.tfstate & TFSTATE_FLASHED) + return; + + localsound(Snd_Get(snd)->sound, channel, vol); +} + +void Pred_Sound(SoundIndex snd, float vol = 1) { + if (IsEffectFrame()) + RawPred_Sound(snd, CHAN_WEAPON, vol); +} + +void PredProj_Sound(int proj_type, float vol = 1) { + Pred_Sound(FPP_Get(proj_type)->snd, vol); +} + +void PM_Update(float sendflags); + +DEFCVAR_FLOAT(fo_beta_nudge_explosion, 0); + +void WP_ServerUpdate(float sendflags) { + pstate_server.seq = servercommandframe; + + // Force WF to zero, in case we missed a packet. + if (pstate_server.attack_finished < pstate_server.client_time) + pstate_server.weaponframe = 0; + + // Match up melee animations when they don't match. The easiest way for + // this to happen is effectframe miss. + if (IsSlotMelee(pstate_server.current_slot) && + pstate_server.weaponframe >= 1 && pstate_server.weaponframe < 8 && + pstate_pred.weaponframe >= 1 && pstate_pred.weaponframe < 8) { + pstate_server.weaponframe = (pstate_server.weaponframe - 1) % 4; + if (pstate_pred.weaponframe >= 4) + pstate_server.weaponframe += 4; + pstate_server.weaponframe += 1; + } + + if (PM_Enabled()) + phys_sim_dt = 3 * SERVER_FRAME_DT; + else + phys_sim_dt = -1; + + PM_Update(sendflags); +} + +void FO_ReloadSound(float weapon) { + local float reloadvolume = CVARF(fo_reloadvolume); + + if (reloadvolume == 0) + return; + + local float sample = 0; + switch (WP_CurrentWeapon()->weapon) { + case WEAP_ROCKET_LAUNCHER: + sample = SND_RELOAD_ROCKET; + break; + case WEAP_GRENADE_LAUNCHER: + case WEAP_PIPE_LAUNCHER: + sample = SND_RELOAD_GREN; + break; + case WEAP_SHOTGUN: + sample = SND_RELOAD_SHOTGUN; + break; + case WEAP_SUPER_SHOTGUN: + sample = SND_RELOAD_SUPER_SHOTGUN; + break; + case WEAP_ASSAULT_CANNON: + sample = SND_RELOAD_ASSAULT_CANNON; + } + RawPred_Sound(sample, CHAN_AUTO, reloadvolume); +}; + +inline float WP_IsReloading() { + return pstate_pred.tfstate & TFSTATE_RELOADING; +} + +static float AmmoToStat(float ammo_type) { + switch (ammo_type) { + case AMMO_SHELLS: return STAT_SHELLS; + case AMMO_ROCKETS: return STAT_ROCKETS; + case AMMO_NAILS: return STAT_NAILS; + case AMMO_CELLS: return STAT_CELLS; + } + return STAT_AMMO; +} + +float WP_GetAmmo(float ammo_type) { + if (!WP_Enabled() || !game_state.is_alive) + return getstatf(AmmoToStat(ammo_type)); + + if (ammo_type == AMMO_NONE) + return 0; + + float base = getstatf(AmmoToStat(ammo_type)); + base -= pstate_pred.ammo_used[ammo_type]; + return max(0, base); +} + +float WP_CurrentAmmo() { + if (!WP_Enabled()) + return getstatf(STAT_AMMO); + + return WP_GetAmmo(WP_CurrentWeapon()->ammo_type); +} + + +float WP_CheckAmmo(FO_WeapInfo* wi) { + if (wi->ammo_type == AMMO_NONE) + return TRUE; + + if (wi->weapon == WEAP_MEDIKIT || wi->weapon == WEAP_SPANNER) + return TRUE; + + if (wi->weapon == WEAP_ASSAULT_CANNON && WP_GetAmmo(AMMO_CELLS) < 7) + return FALSE; + + int ammo = WP_GetAmmo(wi->ammo_type); + return ammo >= wi->ammo_per_shot; +} + +void WP_ChangeWeapon(Slot slot) { + if (!WP_CheckAmmo(SlotWI(slot))) + return; + + pstate_pred.last_slot = pstate_pred.current_slot; + pstate_pred.current_slot = slot; + pstate_pred.weaponframe = 0; + // UpdateViewModel will propagate. +} + +// Alternate between opt1/opt2, activating opt1 if neither is active. +// Useful for demoman to have red/yellows bound to 1 key. +void W_ChangeToSlotAlternate(string opt1, string opt2, string opt3, string opt4) { + float v[4], nv = 0, idx = -1; + string s[4] = {opt1, opt2, opt3, opt4}; + + for (int i = 0; i < s.length; i++) { + if (strlen(s[i]) == 0) + break; + + float t = stof(s[i]); + if (t < 1 || t > TF_NUM_SLOTS) + return; + + if (SlotIndex(pstate_pred.current_slot) + 1 == t) + idx = nv; + v[nv++] = t; + } + + idx = (idx + 1) % nv; + localcmd(sprintf("impulse %d\n", TF_SLOT1 + v[idx] - 1)); +} + +Slot WP_BestWeaponSlot() { + for (float i = 1; i <= TF_NUM_SLOTS; i++) { + Slot slot = MakeSlot(i); + FO_WeapInfo* wi = SlotWI(slot); + + if (wi->weapon == WEAP_NONE || !WP_CheckAmmo(wi)) + continue; + + return slot; + } + + return SlotMelee; +} + +void W_ChangeToBestWeapon() { + WP_ChangeWeapon(WP_BestWeaponSlot()); +} + +float WP_LockedCannon() { + if (!WP_Enabled()) + return SBAR.LockedCannon; + return pstate_pred.tfstate & TFSTATE_LOCK; +} + +static void WP_HandleHeavyInputs() { + if (pstate_pred.playerclass != PC_HVYWEAP) + return; + + // +special toggles lock. + if (pstate_pred.buttons_down & BUTTON3) + pstate_pred.tfstate ^= TFSTATE_LOCK; + + float match = TRUE; + switch (pstate_pred.impulse) { + case TF_LOCKON: pstate_pred.tfstate |= TFSTATE_LOCK; break; + case TF_LOCKOFF: pstate_pred.tfstate &= ~TFSTATE_LOCK; break; + case TF_SPECIAL_SKILL: pstate_pred.tfstate ^= TFSTATE_LOCK; break; + default: + match = FALSE; + } + + if (match) + pstate_pred.impulse = 0; +} + +struct { + float start_seq; + entity sight; + float sight_modelindex; +} SniperState; + +static void WP_InitSniper() { + entity sight = spawn(); + sight.solid = SOLID_NOT; + sight.drawmask = 0; + float index = getmodelindex("progs/sight.spr"); + setmodelindex(sight, index); + setsize(sight, [0,0,0], [0,0,0]); + + SniperState.sight = sight; +} + +float WP_SniperCharge() { + if (!WP_Enabled()) + return SBAR.SniperDam; + + if (!SniperState.start_seq) + return 0; + + float heat = 50 + (pstate_pred.seq - SniperState.start_seq) * 3; + return min(heat, PC_SNIPER_MAXDAM); +} + +static void WP_Sniper_UpdateSight() { + if (pstate_pred.playerclass != PC_SNIPER || !CVARF(fo_client_sniper_sight) || + (pstate_pred.tfstate & TFSTATE_AIMING == 0)) { + SniperState.sight.drawmask = 0; + return; + } + + SniperState.sight.drawmask = MASK_ENGINE; + vector org = PM_Org() + v_forward * 10; + org.z += PLAYER_MINS.z - 1 + (PLAYER_MAXS.z - PLAYER_MINS.z) * 0.7; + traceline(org, org + v_forward * 9192, MOVE_NORMAL, 0); + setorigin(SniperState.sight, trace_endpos); +} + +static float WP_Sniper_IsAttack() { + float aiming = pstate_pred.tfstate & TFSTATE_AIMING; + + // Just box these out up front + if (pstate_pred.client_time < pstate_pred.attack_finished || + !WP_GetAmmo(AMMO_SHELLS)) + return FALSE; + + if (pstate_pred.buttons_held & BUTTON0) { + // Start charging when possible/necessary. + if (!aiming && pmove_onground && vlen(PM_Vel()) <= 50) { + pstate_pred.tfstate |= TFSTATE_AIMING; + SniperState.start_seq = pstate_pred.seq; + } + return FALSE; + } else if (pstate_pred.buttons_up & BUTTON0) { + pstate_pred.tfstate &= ~TFSTATE_AIMING; + SniperState.start_seq = 0; + // Can fire iff already aiming and on ground + return (aiming && pmove_onground); + } else { + return FALSE; + } +} + +void W_ThrowGren(float is_throw); +void W_PrimeGren(float index); + +static void HandleButtonThrowgren(float index, float button) { + float b_down = pstate_pred.buttons_down & button; + float b_up = pstate_pred.buttons_up & button; + + if (IsHoldGrenades()) { + if (b_down) + W_PrimeGren(index); + else if (b_up) + W_ThrowGren(TRUE); + } else if (b_down) { + if (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED) + W_ThrowGren(TRUE); + else + W_PrimeGren(index); + } +} + +float WP_ReloadSlot(Slot slot); +void WP_ReloadNext(); +void WPP_Dump(); + +float WP_WeaponReady() { + const float not_ready_mask = TFSTATE_RELOADING | TFSTATE_NO_WEAPON; + if (pstate_pred.client_time < pstate_pred.attack_finished) + return FALSE; + if (pstate_pred.tfstate & not_ready_mask) + return FALSE; + return TRUE; +} + +static void WP_ChangeIfQueued() { + if (!IsSlotNull(pstate_pred.queue_slot)) { + WP_ChangeWeapon(pstate_pred.queue_slot); + pstate_pred.queue_slot = SlotNull; + } +} + + +DEFCVAR_FLOAT(fo_beta_local_grenade, 0); + +void WP_HandleGrenadeInputs() { + if (!CVARF(fo_beta_local_grenade)) + return; + + float clear_impulse = TRUE; + + switch (pstate_pred.impulse) { + case TF_GRENADE_1: W_PrimeGren(1); break; + case TF_GRENADE_2: W_PrimeGren(2); break; + case TF_GRENADE_T: W_ThrowGren(TRUE); break; + + case TF_GRENADE_PT_1: + if (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED) + W_ThrowGren(TRUE); + else + W_PrimeGren(1); + break; + case TF_GRENADE_PT_2: + if (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED) + W_ThrowGren(TRUE); + else + W_PrimeGren(2); + break; + + default: clear_impulse = FALSE; break; + } + + HandleButtonThrowgren(1, BUTTON5); + HandleButtonThrowgren(2, BUTTON6); + + if (clear_impulse) + pstate_pred.impulse = 0; +} + +float PM_Enabled(); +void PM_AddNudgeDash(float cseq); + +static void WP_Special() { + if (prematch) + return; + + if (pstate_pred.server_time < pstate_pred.special_next) + return; + + float cooldown = 0; + switch (WP_PlayerClass()) { + case PC_SCOUT: + cooldown = 1; + if (PM_Enabled() && IsEffectFrame()) { + PM_AddNudgeDash(pstate_pred.seq); + localsound("dash.wav", CHAN_BODY, 1); + } + break; + // Note: hwguy handled by WP_HandleHeavyInputs + } + + if (cooldown) + pstate_pred.special_next = pstate_pred.server_time + cooldown; +} + +void WP_Impulse() { + float local_impulse; + + WP_HandleGrenadeInputs(); + WP_HandleHeavyInputs(); + + local_impulse = pstate_pred.impulse; + pstate_pred.impulse = 0; + switch (local_impulse) { + case TF_DEBUG_CSQC: + WPP_Dump(); + break; + + case TF_SPECIAL_SKILL: + case TF_DASH: // This could use a clean-up server side + WP_Special(); + break; + + case TF_SLOT1: case TF_SLOT2: case TF_SLOT3: case TF_SLOT4: + case 1: case 2: case 3: case 4: case 5: case 6: case 7: { + Slot slot = FO_SlotByInput(pstate_pred.playerclass, local_impulse); + if (IsSameSlot(pstate_pred.current_slot, slot)) + pstate_pred.queue_slot = SlotNull; + else if (!IsSlotNull(slot)) + pstate_pred.queue_slot = slot; + break; + } + + case TF_WEAPLAST: + // Clearing queued counts as swap to whatever was pre-queued. + if (!IsSlotNull(pstate_pred.queue_slot)) + pstate_pred.queue_slot = SlotNull; + else + pstate_pred.queue_slot = pstate_pred.last_slot; + break; + case TF_WEAPNEXT: + pstate_pred.queue_slot = FO_FindPrevNextWeaponSlot( + pstate_pred.playerclass,CurrentSlot(), FALSE); + break; + case TF_WEAPPREV: + pstate_pred.queue_slot = FO_FindPrevNextWeaponSlot( + pstate_pred.playerclass, CurrentSlot(), TRUE); + break; + + case TF_GRENADE_T: + W_ThrowGren(TRUE); + break; + + case TF_DETPACK: + case TF_DETPACK_5: + case TF_DETPACK_20: + case TF_DETPACK_50: + filter_pproj_time = time + server_time_dt() + 3 * SERVER_FRAME_DT; + break; + + default: + pstate_pred.impulse = local_impulse; + break; + } + + if (input_buttons & BUTTON3) + WP_Special(); + + // Impulses below this line depend on ready weapon. + if (!WP_WeaponReady()) + return; + + WP_ChangeIfQueued(); + + if (!pstate_pred.impulse) // Handled above. + return; + + local_impulse = pstate_pred.impulse; + pstate_pred.impulse = 0; + switch (local_impulse) { + case TF_RELOAD: + WP_ReloadSlot(CurrentSlot()); + break; + case TF_RELOAD_NEXT: + WP_ReloadNext(); + break; + + default: + pstate_pred.impulse = local_impulse; + break; + } +} + +static float* WP_ClipFired(Slot slot) { + if (self_class() == PC_DEMOMAN && SlotIndex(slot) == 1) + slot = MakeSlot(1); // Special case: pipebomb shares with grenade launcher. + + return self_clip_fired(slot); +} + +float WP_CurrentClipFired() { + return *WP_ClipFired(CurrentSlot()); +} + +float WP_ReloadPercent() { + if (!WP_IsReloading()) + return 1; + + return min(1, (pstate_pred.client_time - pstate_pred.reload_started) / + (pstate_pred.reload_finished - pstate_pred.reload_started)); +} + +string WP_GetClip() { + Slot slot = CurrentSlot(); + FO_WeapInfo* wi = SlotWI(slot); + + if (!wi->needs_reload || IsSlotNull(slot)) + return ""; + + float capacity = wi->clip_size; + float still_loading = 0; + float fired = *WP_ClipFired(slot); + + if (WP_IsReloading()) + still_loading = FO_NumClipStillLoading(wi, pstate_pred.client_time, + pstate_pred.reload_finished); + + if (last_still_loading != still_loading) { + last_still_loading = still_loading; + + if (still_loading) + FO_ReloadSound(WP_CurrentWeapon()->weapon); + } + + float clip = capacity - fired - still_loading; + float rem = WP_GetAmmo(wi->ammo_type); + + // It's possible for the amount in clip to exceed remaining ammo (this + // occurs because we load before we drop for example). Render a clipped + // clip when this occurs, with a visual indicator. + if (clip > rem) + return sprintf("*%d*/%d", rem, wi->clip_size); + else + return sprintf("%d/%d", clip, wi->clip_size); +} + +float WP_CanReload(Slot slot, string* msg = __NULL__) { + FO_WeapInfo* wi = SlotWI(slot); + + if (!wi->needs_reload || WP_IsReloading() || prematch) + return FALSE; + + float clip_fired = *WP_ClipFired(slot); + float ammo_rem = WP_GetAmmo(wi->ammo_type); + return FO_CanReloadMsg(wi, ammo_rem, clip_fired, msg); +} + +float WP_ReloadSlot(Slot slot) { + FO_WeapInfo* wi = SlotWI(slot); + + string msg = ""; + if (!WP_CanReload(slot, &msg)) { + if (IsEffectFrame() && msg != "") + csqc_print(msg); + return FALSE; + } + + if (IsEffectFrame()) + csqc_print(strcat("Reloading ", FO_GetWeapName(wi->weapon), "...\n")); + + pstate_pred.tfstate |= TFSTATE_RELOADING; + float amt = min(*WP_ClipFired(slot), WP_GetAmmo(wi->ammo_type)); + (*WP_ClipFired(slot)) -= amt; + + pstate_pred.reload_started = pstate_pred.client_time; + pstate_pred.reload_finished = pstate_pred.client_time + + (amt / wi->clip_size) * wi->full_reload_time; + + return TRUE; +} + +void WP_CheckSpecialReady() { + if (!IsEffectFrame()) + return; + + if (pstate_pred.special_next > pstate_pred.server_time || + pstate_pred.special_next < pstate_pred.server_time - input_timelength) + return; + + switch (pstate_pred.playerclass) { + case PC_ENGINEER: + if (NewBalanceActive()) + printf("Impeller ready\n"); + break; + } +} + +void WP_CheckReloadFinished() { + if (WP_IsReloading() && pstate_pred.reload_finished && pstate_pred.client_time >= pstate_pred.reload_finished) { + pstate_pred.tfstate &= ~TFSTATE_RELOADING; + + if (IsEffectFrame()) + csqc_print("Finished reloading\n"); + } +} + +#define eprintf(...) if (IsEffectFrame()) printf(__VA_ARGS__) + +void WP_ReloadNext() { + Slot slot = CurrentSlot(); + + do { + slot = FO_FindPrevNextWeaponSlot(pstate_pred.playerclass, slot, FALSE); + + // We check this manually because we don't want to spam reload msgs. + if (WP_CanReload(slot)) { + if (WP_ReloadSlot(slot)) + return; + } + } while (!IsSameSlot(slot, CurrentSlot())); + + if (IsEffectFrame()) + csqc_print("All clips full\n"); +} + +float WP_ReloadIfNeeded(Slot slot) { + FO_WeapInfo* wi = SlotWI(slot); + + if (*WP_ClipFired(slot) >= wi->clip_size) + return WP_ReloadSlot(slot); + + return FALSE; +} + +float WP_ReloadCurrentIfNeeded() { return WP_ReloadIfNeeded(CurrentSlot()); } + +float WP_ConsumeAmmo(Slot slot) { + if (WP_ReloadIfNeeded(slot)) + return FALSE; + + FO_WeapInfo* wi = SlotWI(slot); + pstate_pred.ammo_used[wi->ammo_type] += wi->ammo_per_shot; + *WP_ClipFired(slot) += wi->ammo_per_shot; + + WP_ReloadIfNeeded(slot); + + return TRUE; +} + +float W_ConsumeAmmoIfPossible(float ammo_type, float amount) { + if (WP_GetAmmo(ammo_type) < amount) + return FALSE; + + pstate_pred.ammo_used[ammo_type] += amount; + return TRUE; +} + +var void() melee_anim = player_axeN; + +void WP_AnimateModel() { + if (pstate_pred.client_thinkindex == 0) { + player_run(); + return; + } + + switch (WP_CurrentWeapon()->weapon) { + case WEAP_ROCKET_LAUNCHER: + case WEAP_INCENDIARY: + case WEAP_GRENADE_LAUNCHER: + case WEAP_PIPE_LAUNCHER: + player_rocketN(); break; + + case WEAP_TRANQ: + case WEAP_RAILGUN: + case WEAP_SHOTGUN: + case WEAP_SUPER_SHOTGUN: + case WEAP_SNIPER_RIFLE: + player_shotN(); break; + + case WEAP_FLAMETHROWER: + player_flamethrowerN(); break; + + case WEAP_NAILGUN: + case WEAP_SUPER_NAILGUN: + player_nailN(); break; + + case WEAP_ASSAULT_CANNON: + player_assault_cannon(); + break; + + case WEAP_AXE: + case WEAP_KNIFE: + case WEAP_MEDIKIT: + melee_anim(); break; + case WEAP_SPANNER: + player_axeN(); break; + } +} + +void WPP_Dump() { + printf("cti p=%d s=%d wf=%d/%d\n", + pstate_pred.client_thinkindex, pstate_server.client_thinkindex, + pstate_pred.weaponframe, pstate_server.weaponframe); + printf("t=%4.3f (%4.3f) af=%4.3f (%4.3f) rf=%4.3f (%4.3f)\n", + pstate_pred.client_time, pstate_server.client_time, + pstate_pred.attack_finished, pstate_server.attack_finished, + pstate_pred.reload_finished, pstate_server.reload_finished); + + int slot_index = SlotIndex(pstate_pred.current_slot); + if (slot_index >= 0) { + printf("clip=%d/%d ammo_used=%d tfstate=%d/%d\n", + pstate_pred.clip_fired[slot_index], + pstate_server.clip_fired[slot_index], + pstate_pred.ammo_used[WP_CurrentWeapon()->ammo_type], + pstate_pred.tfstate, pstate_server.tfstate); + } +} + +void WP_Attack(); +void WP_ExplodeGren(); +DEFCVAR_FLOAT(cl_crossx, 0); + +void WP_Frame() { + if (!game_state.is_alive) { + pstate_pred.current_slot = SlotNull; + return; + } + + while (pstate_pred.client_nextthink && + pstate_pred.client_time >= pstate_pred.client_nextthink) { + float held_client_time = pstate_pred.client_time; + + pstate_pred.client_time = pstate_pred.client_nextthink; + pstate_pred.client_nextthink = 0; + WP_AnimateModel(); + pstate_pred.client_time = held_client_time; + } + + WP_CheckReloadFinished(); + WP_Impulse(); + WP_ExplodeGren(); + W_ThrowGren(FALSE); + + float is_attack = 0; + + if (pstate_pred.playerclass != PC_SNIPER) + is_attack = ((input_buttons & BUTTON0) && WP_WeaponReady()); + else + is_attack = WP_Sniper_IsAttack(); + + if (is_attack) + WP_Attack(); + + WP_Sniper_UpdateSight(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Projectiles +//////////////////////////////////////////////////////////////////////////////// + +entity predicted_projectiles; +.entity pred_next, pred_prev; + +.float starttime, endtime, p_time; +.float traileffectnum; +.float created_seq; + +struct trail_table_entry { + string eff_name[2]; + + // Automatically initialized below this line. + float trail[2]; +}; + +trail_table_entry trail_table[] = { + {{"TR_GRENADE", "TR_ROCKET" }}, + {{"TR_ROCKET", "TR_GRENADE" }}, + {{"TR_ALTROCKET" }}, + {{"TR_BLOOD" }}, + {{"TR_SLIGHTBLOOD" }}, + {{"TR_WIZSPIKE" }}, + {{"TR_KNIGHTSPIKE" }}, + {{"TR_VORESPIKE" }}, + {{"TE_RAILTRAIL"}}, +}; + +inline float get_phys_time(entity e) { + if (e.owner_entnum == pengine.player_entnum) { + // We want to align our own entities with remote player interp so that + // collisions feel correct. + return interp_time(); + } else { + // Entities from other players.. we have choice. For now, advance them + // up to wpp_adv_otherp_ms (bounded at ping, which is true position for + // hitting the client.. what they likely care about most... although + // this will offset explosions in the short term). + static float last_self_time; + float dt = min(inst_ping_ms, CVARF(wpp_adv_otherp_ms)) / 1000.0; + if (PM_Enabled() && (CVARF(fo_beta_nudge_explosion) & 2)) + dt = (clientcommandframe - servercommandframe - 1) * SERVER_FRAME_DT; + last_self_time = max(last_self_time, time + dt); + return last_self_time; + } +} + +void FO_Predict_Init() { + entity viewmodel = spawn(); + viewmodel.drawmask = MASK_PRED_ENT; + viewmodel.renderflags = RF_VIEWMODEL | RF_DEPTHHACK; + pengine.viewmodel = viewmodel; + + WP_InitSniper(); + + // Entity we'll attach locally generated sound to. + pred_sound_entity = spawn(); + + INIT_MOVING_AVG(avg_ping); + InitFppProjectiles(); + + for (float i = 0; i < trail_table.length; i++) { + for (float j = 0; j < 2; j++) { + trail_table_entry* entry = &trail_table[i]; + + string name = entry->eff_name[j]; + if (name == "") + name = entry->eff_name[0]; + + entry->trail[j] = particleeffectnum(name); + } + } +} + +void PP_Cleanup(entity proj) { + if (predicted_projectiles == proj) + predicted_projectiles = proj.pred_next; + + if (proj.pred_prev) + proj.pred_prev.pred_next = proj.pred_next; + + if (proj.pred_next) + proj.pred_next.pred_prev = proj.pred_prev; + + if (proj.removefunc) { + entity tmp = self; + self = proj; + self.removefunc(); + self = tmp; + } + remove(proj); +} + +DEFCVAR_FLOAT(r_pyrotrail, 0); +DEFCVAR_FLOAT(r_rockettrail, 0); +DEFCVAR_FLOAT(r_grenadetrail, 0); + +int FPP_FindTrail(entity e) { + int idx = -1; int is_g = 0; + + switch (e.fpp.index) { + case FPP_ROCKET: idx = CVARF(r_rockettrail) - 1; break; + case FPP_INCENDIARY: idx = CVARF(r_pyrotrail) - 1; break; + case FPP_GRENADE: idx = CVARF(r_grenadetrail) - 1; is_g = 1; break; + } + + if (idx >= 0 && idx < trail_table.length) + return trail_table[idx].trail[is_g]; + + return FPP_Get(e.fpp.index)->trailindex; +} + +DEFCVAR_FLOAT(cl_p2r, 0); +DEFCVAR_FLOAT(cl_r2g, 0); + +void FPP_Init(int fpp_type, entity proj) { + fo_projectile* desc = FPP_Get(fpp_type); + + proj.fpp.index = fpp_type; + + int render_type = fpp_type; + // If someone sets both p2r and r2g, we'll convert all the way. + if (render_type == FPP_INCENDIARY && CVARF(cl_p2r) == 1) + render_type = FPP_ROCKET; + if (render_type == FPP_ROCKET && CVARF(cl_r2g) == 1) + render_type = FPP_GRENADE; + + setmodelindex(proj, FPP_Get(render_type)->modelindex); + setsize(proj, [0,0,0], [0,0,0]); + + proj.traileffectnum = FPP_FindTrail(proj); + proj.movetype = desc->movetype; +} + +DEFCVAR_FLOAT(r_rocketlight, 1); +DEFCVAR_FLOAT(r_rocketlight, 1); +DEFCVAR_STRING(r_rocketlight_color, "2.0 1.0 0.25 200"); // Radius ignored. + +static float HasLight(int fpp_type) { + if (!CVARF(r_rocketlight)) + return FALSE; + return fpp_type == FPP_ROCKET || fpp_type == FPP_INCENDIARY; +} + +.float trail_started; +float PP_PredrawActive() { + if (self.voided) + return PREDRAW_NEXT; + + Phys_Advance(self, get_phys_time(self), 0); + Phys_Sim(self); + + // we need to space out the particles incase we're running at very high fps + if (time > self.p_time) { + if (self.p_time) { + traceline(self.oldorigin, self.origin, MOVE_NOMONSTERS, self); + if (trace_fraction < 1) { + self.oldorigin = trace_endpos; + return PREDRAW_NEXT; + } + } + trailparticles(self.traileffectnum, self, self.oldorigin, self.origin); + + self.trail_started = TRUE; + self.p_time = time + frametime; + self.oldorigin = self.origin; + } + + if (HasLight(self.fpp.index)) + dynamiclight_add(self.origin, 200, stov(CVARS(r_rocketlight_color))); + + return PREDRAW_AUTOADD; +} + +float PP_PredrawPredicted() { + if (time < self.starttime || time > self.endtime) { + if (time > self.endtime) + PP_Cleanup(self); + return PREDRAW_NEXT; + } + + return PP_PredrawActive(); +} + +static float PP_EPS = 0.05; + +enumflags { + CP_DROP +}; + +entity PP_CreateProjectile(int fpp_type, vector org, float cp_flags=0) { + PredProj_Sound(fpp_type); + + if (!PP_Enabled()) + return __NULL__; + + entity proj = spawn(); + proj.owner_entnum = player_localentnum; + proj.owner = pengine.player_ent; + proj.origin = org; + FPP_Init(fpp_type, proj); + + float ms_ahead = pstate_server.client_ping; + ProjectResult push_t = Forward_ProjectOffset(fpp_type, ms_ahead); + + float uncorrected_dt = max(ms_ahead - push_t.dynamic_ms, 0) / 1000.0; + if (cp_flags & CP_DROP) + uncorrected_dt = 0; + float static_dt = push_t.static_ms / 1000.0; + + // Note: We use true time here as created projectiles exist outside of pred. + proj.starttime = time + uncorrected_dt - input_timelength / 2; + proj.endtime = proj.starttime + pstate_server.client_ping / 1000.0 + PP_EPS; + + proj.predraw = PP_PredrawPredicted; + proj.drawmask = MASK_PRED_PROJECTILE; + + float proj_speed = FPP_Get(fpp_type)->speed; + if (!FPP_IsGrenade(fpp_type)) { + proj.velocity = v_forward * proj_speed; + } else if (cp_flags & CP_DROP) { + proj.velocity = '0 0 0'; + } else { + if (!input_angles_x) { + proj.velocity = v_forward * proj_speed; + proj.velocity_z = 200; + } else { + proj.velocity = (v_forward * proj_speed) + + (200 + shared_crandom(PRNG_WEAP) * 10) * v_up + + (shared_crandom(PRNG_WEAP) * 10 * v_right); + } + } + proj.angles = input_angles; proj.angles[0] *= -1; + + proj.s_origin = proj.origin; + proj.created_seq = pstate_pred.seq; + + // Trail starts at 0.05, we, split physics calcs longer than this. + float sdt2 = max(static_dt - 0.05, 0); + Phys_Init(proj, proj.starttime - sdt2, static_dt - sdt2, PHYSF_CONSUME_ALL); + + proj.s_time = proj.phys_time; // Set after static_dt processed. + + proj.oldorigin = proj.origin; + if (sdt2 > 0) + Phys_Advance(proj, sdt2, PHYSF_CONSUME_ALL); + + proj.pred_next = predicted_projectiles; + if (predicted_projectiles) + predicted_projectiles.pred_prev = proj; + predicted_projectiles = proj; + + return proj; +} + +static void FPP_ApplyGrenadeProperties(entity proj) { + if (!FPP_IsGrenade(proj.fpp.index)) + return; + + if (proj.fpp.gren_type < GREN_FIRST) { + printf("proj (%s) missing gren_type, tell newby\n", proj.model); + proj.fpp.gren_type = GREN_FIRST; + } + FO_GrenInfo* gdesc = FO_GrenDesc(proj.fpp.gren_type); + + setmodelindex(proj, gdesc->modelindex); + setsize(proj, [0,0,0], [0,0,0]); + proj.skin = gdesc->skin; + proj.avelocity = gdesc->avelocity; +} + +void W_FireGrenade(int gren_type) { + entity proj = PP_CreateProjectile(FPP_GRENADE, PM_Org()); + proj.fpp.gren_type = gren_type; + FPP_ApplyGrenadeProperties(proj); +} + +void W_FireAssaultCannon() { + if (!WP_ConsumeAmmo(CurrentSlot())) + return; + + if (!IsEffectFrame() || !PP_Enabled()) + return; + + WeapPred_FireAssCan(input_angles, 5, '0.04 0.04 0'); +} + +void (float ox) W_FireSpikes = { + FO_WeapInfo* wi = WP_CurrentWeapon(); + + if (!WP_CheckAmmo(wi)) { + W_ChangeToBestWeapon(); + return; + } + + WP_ConsumeAmmo(CurrentSlot()); + Attack_Finished(wi->attack_time); + + if (!IsEffectFrame()) + return; + + if (wi->weapon == WEAP_NAILGUN) + PP_CreateProjectile(FPP_NAIL, PM_Org() + ox * v_right + '0 0 16'); + else + PP_CreateProjectile(FPP_SUPER_NAIL, PM_Org() + '0 0 16'); + +}; + +static float QcPhysics() { return fo_config.qc_physics; } + +static float CanThrowGrenade(int gren_type) { + if (pstate_pred.client_time < pstate_pred.last_prime + 0.8) + return FALSE; // Not time to throw yet, THROWING set if necessary. + + float last_possible_throw = pstate_pred.last_prime + 3.7; + last_possible_throw -= FO_RewindGrenDt(gren_type); + + // We're conservative here, a missed predicted projectile throw into what's + // actually a held grenade is frustrating. Better to leave it up to the + // server when it's really tight. + if (pstate_pred.client_time > last_possible_throw - 1.5 * SERVER_FRAME_DT) + return FALSE; // Buckle up... probably. + + return TRUE; +} + +float WP_GrenCount(float index) { + return index == 1 ? pstate_pred.no_grenades_1 : pstate_pred.no_grenades_2; +} + +CsGrenTimer ParseGrenPrimed(float grentype, float explodes_at, + float timer_flags = 0); + +static void W_PrimeGren(float index) { + if (!game_state.is_alive || getstatf(STAT_NOFIRE)) + return; + + if (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED) + return; + + index -= 1; // We work with zero-based index here. + float* gc = index == 0 ? &pstate_pred.no_grenades_1 : + &pstate_pred.no_grenades_2; + if (*gc < 1) + return; // No grenades left + + *gc -= 1; + pstate_pred.tfstate |= index ? TFSTATE_GREN1_PRIMED : TFSTATE_GREN2_PRIMED; + + pstate_pred.primed_gren_type = FO_ClassGren(pstate_pred.playerclass, index)->id; + + static float held_gren_exp; + if (IsEffectFrame()) { + held_gren_exp = time + 3.8 + input_timelength / 2; + ParseGrenPrimed(pstate_pred.primed_gren_type, held_gren_exp, FL_GT_LOCAL); + } + pstate_pred.primed_gren_exp = held_gren_exp; +} + +static void WP_ExplodeGren() { + if (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED == 0) + return; + + float explode_at = pstate_server.primed_gren_exp; + float tfstate = pstate_pred.tfstate; + + if (!explode_at || explode_at > pstate_pred.server_time) + return; + + pstate_pred.primed_gren_exp = 0; + pstate_pred.tfstate &= ~TFSTATE_GREN_MASK_ALL; + + if (!IsEffectFrame()) + return; + + int type_index = (tfstate & TFSTATE_GREN1_PRIMED) ? 0 : 1; + int gren_type = FO_ClassGren(pstate_pred.playerclass, type_index)->id; + entity gren = PP_CreateProjectile(FPP_HANDGRENADE, PM_Org(), CP_DROP); + gren.fpp.gren_type = gren_type; + FPP_ApplyGrenadeProperties(gren); + + gren.starttime = explode_at; + gren.phys_time = explode_at; + gren.fpp.expires_at = explode_at + 0.1; + + Phys_Sim(gren, explode_at + 0.1); +} + +static void W_ThrowGren(float is_throw) { + if (!QcPhysics() || !RewindFlagEnabled(REWIND_GRENADES) || getstatf(STAT_NOFIRE)) + return; + + if (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED == 0) + return; + + if (is_throw) + pstate_pred.tfstate |= TFSTATE_GRENTHROWING; + else if (pstate_pred.tfstate & TFSTATE_GRENTHROWING == 0) + return; + + int type_index = (pstate_pred.tfstate & TFSTATE_GREN1_PRIMED) ? 0 : 1; + int gren_type = FO_ClassGren(pstate_pred.playerclass, type_index)->id; + if (!CanThrowGrenade(gren_type) && !IsClownMode(CLOWN_SPAM_GRENADES)) + return; + + if (IsEffectFrame()) { + entity gren = PP_CreateProjectile(FPP_HANDGRENADE, '0 0 0'); + gren.fpp.gren_type = gren_type; + FPP_ApplyGrenadeProperties(gren); + } + + pstate_pred.tfstate &= ~TFSTATE_GREN_MASK_ALL; +} + + +void FO_FireAssCanPellet(vector org, vector dir, float proj_speed, int index) { + entity proj = PP_CreateProjectile(FPP_ASSAULT_CANNON, org); + + if (proj == __NULL__) + return; + + proj.s_origin = org; + setorigin(proj, org); + proj.frame = hwguy_random() * 15; + proj.skin = 16 + hwguy_random() * 7; + proj.velocity = dir * proj_speed; + proj.angles = vectoangles(dir); + proj.fpp.ammo_index = WP_GetAmmo(AMMO_SHELLS) * 100 + index; +} + +float NB_ImpellerCoolDown(FO_WeapInfo* wi) { + if (!NewBalanceActive() || wi->weapon != WEAP_IMPELLER) + return FALSE; + + return pstate_pred.server_time < pstate_pred.special_next; +} + +void WP_Attack() { + if (getstatf(STAT_NOFIRE)) + return; + + FO_WeapInfo* wi = WP_CurrentWeapon(); + + if (wi->predict_type == NO_PREDICT) + return; + + if (!WP_CheckAmmo(wi)) + return; + + if (NB_ImpellerCoolDown(wi)) + return; + + // Whether firing occurs here, or is embedded in the frame animation code + // (because continuous fire). + int in_anim = wi->weapon == WEAP_NAILGUN || + wi->weapon == WEAP_SUPER_NAILGUN || + wi->weapon == WEAP_ASSAULT_CANNON; + + if (!wi->fire_in_anim && !WP_ConsumeAmmo(CurrentSlot())) + return; + + // OK. We're ready to pew. + + // Must be set prior to animation code, which might internally modify. + pstate_pred.client_thinkindex = 1; + + if (IsEffectFrame() && !wi->fire_in_anim && (time > filter_pproj_time)) { + float send_event = FALSE; + switch (wi->weapon) { + case WEAP_ROCKET_LAUNCHER: + PP_CreateProjectile(FPP_ROCKET, PM_Org() + v_forward * 8 + '0 0 16'); + send_event = TRUE; + break; + case WEAP_INCENDIARY: + PP_CreateProjectile(FPP_INCENDIARY, PM_Org() + v_forward * 8 + '0 0 16'); + send_event = TRUE; + break; + case WEAP_TRANQ: + PP_CreateProjectile(FPP_TRANQ, PM_Org() + v_forward * 8 + '0 0 16'); + break; + case WEAP_RAILGUN: + PP_CreateProjectile(FPP_RAILGUN, PM_Org() + '0 0 16'); + break; + + case WEAP_GRENADE_LAUNCHER: + send_event = TRUE; + case WEAP_PIPE_LAUNCHER: + if (QcPhysics()) + W_FireGrenade((wi->weapon == WEAP_GRENADE_LAUNCHER || prematch) ? + GREN_RED : GREN_PIPE); + else + Pred_Sound(SND_GREN); + break; + + case WEAP_AXE: + case WEAP_KNIFE: + case WEAP_MEDIKIT: + melee_anim = shared_prng(PRNG_WEAP) < 0.5 ? player_axeN : player_axebN; + // Fall through for sound. + case WEAP_SPANNER: + Pred_Sound(SND_AXE); break; + + case WEAP_SHOTGUN: Pred_Sound(SND_SG); break; + case WEAP_SUPER_SHOTGUN: Pred_Sound(SND_SSG); break; + case WEAP_SNIPER_RIFLE: Pred_Sound(SND_SNIPER_RIFLE); break; + } + + if (send_event && RewindFlagEnabled(REWIND_SENDEVENT)) { + // It's slightly confusing that we pass server and not pred here, + // but this includes the ping adjustment that would occur at pred. + // Tested both ways and this seems to be more stable. + sendevent("Attack", "ifvv", + wi->weapon, pstate_server.client_time, + PM_Org(), input_angles); + } + } + + if (!wi->fire_in_anim) + Attack_Finished(wi->attack_time); + + // Start the AC state machine when necessary. + if (wi->weapon == WEAP_ASSAULT_CANNON && + (pstate_pred.tfstate & TFSTATE_AC_MASK) == 0) { + if (W_ConsumeAmmoIfPossible(AMMO_CELLS, PC_HVYWEAP_CELL_FIRE)) + pstate_pred.tfstate |= TFSTATE_AC_SPINUP; + else + pstate_pred.client_thinkindex = 0; // Just kidding, no cells. + } +#if 0 + // If our latency is higher than forward projection, synchronize animation + // with when it will actually start/finish. The projectile internally + // synchronizes also. + // + // ** Not currently doing this because playing animation in advance is better + // for understanding attack_finished as a player. ** + float offset = + // max(PP_PredictStartOffset() - 2*SERVER_FRAME_MS, 0); + if (offset > 0) { + if ((CVARF(wpp_debug) & 2) && pengine.is_effectframe) + printf("held for %0.3f\n", offset); + + pstate_pred.client_nextthink = animate_s_time; + return; + } +#endif + + WP_AnimateModel(); +} + + +void PredProjectile_DebugMatch(entity cprj, entity sprj) { + static float S = 0, N = 0; + + S += vlen(cprj.origin - sprj.origin); N++; + if (N > 1000) { + S /= 1000; + N /= 1000; + } + + string sgn = + vlen(cprj.origin - PM_Org()) > vlen(sprj.origin - PM_Org()) ? "+" : "-"; + + string s = sprintf(" p_diff: %s%-2.1f [%0.3f] ", sgn, + vlen(cprj.origin - sprj.origin), + vlen(cprj.origin - sprj.origin) / vlen(cprj.velocity)); + s = strcat(s, sprintf("t=%0.3f p_t=[c:%0.3f s:%0.3f d=%0.3f]", + time, cprj.phys_time, sprj.phys_time, + sprj.phys_time - cprj.phys_time)); + + s = strcat(s, sprintf(" [c=%d s=%d] err=%0.3f al=%d\n", + vlen(cprj.origin - PM_Org()), vlen(sprj.origin - PM_Org()), + S/N, self.antilag_ms)); + print(s); +} + +float PredProjectile_MatchProjectile() { + entity proj, match = __NULL__; + float best = 64; + + for(proj = predicted_projectiles; proj != __NULL__; proj = proj.pred_next) { + if (proj == self) + error("???"); + + if (proj.fpp.index != self.fpp.index || proj.fpp.aux != self.fpp.aux) + continue; + + if (time > proj.endtime - 0.01) + break; // Projectile list is time ordered + + Phys_Advance(proj, get_phys_time(self), PHYSF_CONSUME_ALL); + + float d = vlen(proj.origin - self.origin); + if (!best || d < best) { + best = d; + match = proj; + } + } + + if (match) { + self.oldorigin = match.oldorigin; // Mate up trails. + self.p_time = match.p_time; + self.trail_started = match.trail_started; + + if (FPP_IsGrenade(self.fpp.index)) + self.angles = match.angles; + + if (CVARF(wpp_debug) & 1) + PredProjectile_DebugMatch(match, self); + + PP_Cleanup(match); + return TRUE; + } + + return FALSE; +} + +// Called on `self`. +void InitProjectileEnt(float sendflags) { + if (self.fpp.index == FPP_NONE) { + printf("ERROR: Missing FPP flags=%d\n", sendflags); + self.fpp.index = FPP_RAILGUN; // Something people will notice. + } + + self.created_seq = servercommandframe; + + self.drawmask = MASK_ENGINE; + self.predraw = PP_PredrawActive; + if (sendflags & FOPP_ANGLES == 0) + self.angles = vectoangles(self.velocity); + + FPP_Init(self.fpp.index, self); + FPP_ApplyGrenadeProperties(self); + + if (self.owner_entnum == pengine.player_entnum) + self.owner = pengine.player_ent; + + float ptime = get_phys_time(self); + if (ptime > self.phys_time /* from server */) + Phys_Advance(self, ptime, PHYSF_CONSUME_ALL); + else + setorigin(self, self.origin); + + float simto = max(self.forward_knock, self.fpp.expires_at); + if (PM_Enabled()) + Phys_Sim(self, simto); + + + if (self.created_at < pstate_server.server_time - 0.05) { + // Retransmit. Patch up trail, but no predicted / sound / etc. + self.oldorigin = self.s_origin; + return; + } + + // We still check this with projectile prediction as there could be in + // flight projectiles on the transition. This is effectively a nop in the + // off case since the projectile list will just be empty. + float has_predicted = FALSE; + if (PP_Enabled() && self.owner_entnum == pengine.player_entnum) { + has_predicted = PredProjectile_MatchProjectile(); + + // Either missed prediction, or not active. We don't want to condition + // on effect frames here as this isn't predicted; stronger: if we missed + // the sound we probably missed an effect frame! + if (!has_predicted) + RawPred_Sound(FPP_Get(self.fpp.index)->snd, CHAN_WEAPON); + } + + if (!has_predicted) { + // Back-track trail for entities without a local prediction. We use + // s_origin here as that's what we know antilag_ms to be relative to. + float trail_ms = (self.antilag_ms - 50) / 1000.0; + self.oldorigin = self.s_origin - trail_ms * self.velocity; + } +} + +void WP_UpdateViewModel() { + entity viewmodel = pengine.viewmodel; + float pmodelindex, pframe; + + FO_WeapInfo* wi = WP_CurrentWeapon(); + + if (!WP_Enabled() || wi->predict_type == NO_PREDICT) { + // Fall back to engine. + viewmodel.modelindex = 0; + pengine.view_mask |= MASK_VIEWMODEL; + return; + } else { + pengine.view_mask &= ~MASK_VIEWMODEL; + } + + static float no_weap_mask = TFSTATE_NO_WEAPON | TFSTATE_FLASHED; + if (pstate_pred.tfstate & no_weap_mask || CVARF(r_drawviewmodel) == 0) { + viewmodel.modelindex = 0; + return; + } + + float alph; + if (pstate_pred.tfstate & TFSTATE_RELOADING) { + alph = CVARF(fo_reloadalpha); + } else { + alph = CVARF(r_drawviewmodel); + float ready = WP_WeaponReady() && !NB_ImpellerCoolDown(wi); + if (!ready && CVARF(fo_nofirealpha) != -1) + alph = CVARF(fo_nofirealpha); + } + + if (alph > 0) { + viewmodel.alpha = alph; + } else { + viewmodel.modelindex = 0; + return; + } + + pframe = pstate_pred.weaponframe; + if (CVARF(fo_hue_weaponswap) && !IsSlotNull(pstate_pred.queue_slot)) { + wi = SlotWI(pstate_pred.queue_slot); + pframe = 0; + } + + pmodelindex = (wi->models)->modelindex; + + if (viewmodel.modelindex != pmodelindex) { + viewmodel.frame = pframe; + viewmodel.modelindex = pmodelindex; + viewmodel.lerpfrac = 0; + } else if (viewmodel.frame != pframe) { + viewmodel.frame2 = viewmodel.frame; + viewmodel.frame = pframe; + viewmodel.lerpfrac = 1; + } + + viewmodel.lerpfrac = max(0, viewmodel.lerpfrac - frametime * 10); +} + +struct { + float cussed; + float end_time; + vector c_view; + vector c_forward; + + float last_a; +} cuss_state; + +float ClownConcPeriod(); +void StopGrenTimers(); + +DEFCVAR_FLOAT(fo_grentimer_debug, 0); + +static void WP_GrenadeTimers() { + static float last_gen; + static float last_server_exp; + + // New life or player, reset and resync. + if (game_state.spawn_gen != last_gen) { + last_gen = game_state.spawn_gen; + StopGrenTimers(); + last_server_exp = 0; + } + + float server_exp = pstate_server.primed_gren_exp; + CsGrenTimer srv = CsGrenTimer::Match(server_exp, 25*MSEC); + + if (server_exp != last_server_exp) { + last_server_exp = server_exp; + + if (srv == world) { + srv = ParseGrenPrimed(pstate_server.primed_gren_type, server_exp); + } else { + if (CVARF(fo_grentimer_debug) & 2) + printf("gt update local: delta=%0.3f\n", server_exp - srv.raw_expiry()); + + // Previous local + if (!srv.test_flag(FL_GT_LOCAL)) + printf("Warning: local grenade mismatch! %0.3f %0.3f\n", server_exp, srv.raw_expiry()); + srv.clear_flag(FL_GT_LOCAL); + // Sound sync below not yet working well. + srv.adj_sync(server_exp - srv.raw_expiry(), + CVARF(fo_beta_local_grenade) == 2); + } + } + + if (srv != world && (pstate_pred.tfstate & TFSTATE_GREN_MASK_PRIMED == 0)) + srv.set_flag(FL_GT_THROWN); +} + +static void PredictConc() { + if (IsClownMode(CLOWN_CONC)) { + if (cuss_state.end_time < time) + cuss_state.end_time = time + ClownConcPeriod(); + + pstate_pred.conc_amp = 100; + pstate_pred.tfstate |= TFSTATE_CONC; + return; + } + + if (!fo_config.fo_concuss) + return; // Not enabled. + + if (pstate_server.tfstate & TFSTATE_CONC == 0) + return; + + static float last_conc_finished; + if (pstate_server.conc_finished != last_conc_finished) { + // New conc, use a single fixed local finish time. + last_conc_finished = pstate_server.conc_finished; + cuss_state.end_time = pstate_server.conc_finished - server_time_dt(); + cuss_state.last_a = 0; + } + + if (time > cuss_state.end_time) + pstate_pred.tfstate &= ~TFSTATE_CONC; +} + +void PM_SyncTo(float seq); +void PM_TestJump(); + +static void WP_UpdatePredict() { + PredictConc(); // Looks like this is missing time/prediction interp + getinputstate(servercommandframe); // Setup first read for old_buttons. + + float old_input_buttons; + int pframe = servercommandframe + 1; + int eframe = clientcommandframe - 1; + int effect_frame = clientcommandframe - 1; + + for(; pframe <= eframe; pframe++) { + old_input_buttons = input_buttons; + if (!getinputstate(pframe) || input_timelength <= 0) + break; + makevectors(input_angles); + + pstate_pred.seq = pframe; + pstate_pred.client_time += input_timelength; + pstate_pred.server_time += input_timelength; + + pstate_pred.buttons_held = input_buttons; + pstate_pred.buttons_up = old_input_buttons & (~input_buttons); + pstate_pred.buttons_down = (~old_input_buttons) & input_buttons; + + if (pframe == effect_frame && pframe > pengine.last_effectframe) { + pengine.is_effectframe = TRUE; + pengine.last_effectframe = pframe; + PM_SyncTo(pframe); + Conc_Update(&pstate_pred.conc_state, world, pstate_pred.server_time); + } else { + pengine.is_effectframe = FALSE; + } + + if (input_impulse) + pstate_pred.impulse = input_impulse; + + WP_Frame(); + } + + WP_UpdateViewModel(); +} + +DEFCVAR_FLOAT(fo_beta_noskip_predict, 0); +float WP_ClientThink() { + static float lsf, lcf; + if (lsf == servercommandframe && lcf == clientcommandframe && + !CVARF(fo_beta_noskip_predict)) + return PREDRAW_NEXT; + + pstate_pred = pstate_server; + + if (WP_Enabled()) + WP_UpdatePredict(); + + WP_GrenadeTimers(); + return PREDRAW_NEXT; +} + +entity WP_pmove_ent(); + +void InitWeapPredEnt(entity pe) { + if (pengine.pweap_ent != world) { + remove(pengine.pweap_ent); + pengine.pweap_ent = world; + } + + pe.predraw = WP_ClientThink; + pe.drawmask = MASK_PRED_ENT; + pengine.pweap_ent = pe; + + wp_ready = TRUE; +} + +void PM_Refresh(); + +void WP_ServerFrame() { + if (game_state.is_player != prev_game_state.is_player) + WPP_UpdateEnable(TRUE); + + if (game_state.spawn_gen != prev_game_state.spawn_gen) + PM_Refresh(); +} diff --git a/defs.h b/defs.h deleted file mode 100644 index 856c492ea..000000000 --- a/defs.h +++ /dev/null @@ -1,1238 +0,0 @@ -//============================================================================ -// VARS NOT REFERENCED BY C CODE -//============================================================================ - -#ifndef VER -#define VER "unknown" -#endif - -#ifndef REV -#define REV "unk" -#endif - -// -// constants -// - -#define FALSE 0 -#define TRUE 1 - -// edict.flags -#define FL_FLY 1 -#define FL_SWIM 2 -#define FL_CLIENT 8 // set for all client edicts -#define FL_INWATER 16 // for enter / leave water splash -#define FL_MONSTER 32 -#define FL_GODMODE 64 // player cheat -#define FL_NOTARGET 128 // player cheat -#define FL_ITEM 256 // extra wide size for bonus items -#define FL_ONGROUND 512 // standing on something -#define FL_PARTIALGROUND 1024 // not all corners are valid -#define FL_WATERJUMP 2048 // player jumping out of water -#define FL_JUMPRELEASED 4096 // for jump debouncing - -// edict.movetype values -#define MOVETYPE_NONE 0 // never moves -//#define MOVETYPE_ANGLENOCLIP 1 -//#define MOVETYPE_ANGLECLIP 2 -#define MOVETYPE_WALK 3 // players only -#define MOVETYPE_STEP 4 // discrete, not real time unless fall -#define MOVETYPE_FLY 5 -#define MOVETYPE_TOSS 6 // gravity -#define MOVETYPE_PUSH 7 // no clip to world, push and crush -#define MOVETYPE_NOCLIP 8 -#define MOVETYPE_FLYMISSILE 9 // fly with extra size against monsters -#define MOVETYPE_BOUNCE 10 -#define MOVETYPE_BOUNCEMISSILE 11 // bounce with extra size - -// edict.solid values -#define SOLID_NOT 0 // no interaction with other objects -#define SOLID_TRIGGER 1 // touch on edge, but not blocking -#define SOLID_BBOX 2 // touch on edge, block -#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground -#define SOLID_BSP 4 // bsp clip, touch on edge, block - -// range values -#define RANGE_MELEE 0 -#define RANGE_NEAR 1 -#define RANGE_MID 2 -#define RANGE_FAR 3 - -// deadflag values -#define DEAD_NO 0 -#define DEAD_DYING 1 -#define DEAD_DEAD 2 -#define DEAD_RESPAWNABLE 3 - -// takedamage values -#define DAMAGE_NO 0 -#define DAMAGE_YES 1 -#define DAMAGE_AIM 2 - -// items -#define IT_AXE 4096 -#define IT_SHOTGUN 1 -#define IT_SUPER_SHOTGUN 2 -#define IT_NAILGUN 4 -#define IT_SUPER_NAILGUN 8 -#define IT_GRENADE_LAUNCHER 16 -#define IT_ROCKET_LAUNCHER 32 -#define IT_LIGHTNING 64 -#define IT_EXTRA_WEAPON 128 - -#define IT_SHELLS 256 -#define IT_NAILS 512 -#define IT_ROCKETS 1024 -#define IT_CELLS 2048 - -#define IT_ARMOR1 8192 -#define IT_ARMOR2 16384 -#define IT_ARMOR3 32768 -#define IT_SUPERHEALTH 65536 - -#define IT_KEY1 131072 -#define IT_KEY2 262144 - -#define IT_INVISIBILITY 524288 -#define IT_INVULNERABILITY 1048576 -#define IT_SUIT 2097152 -#define IT_QUAD 4194304 -#define IT_HOOK 8388608 - -// point content values -#define CONTENT_EMPTY -1 -#define CONTENT_SOLID -2 -#define CONTENT_WATER -3 -#define CONTENT_SLIME -4 -#define CONTENT_LAVA -5 -#define CONTENT_SKY -6 - -#define STATE_TOP 0 -#define STATE_BOTTOM 1 -#define STATE_UP 2 -#define STATE_DOWN 3 - -#define VEC_ORIGIN '0 0 0' -#define VEC_HULL_MIN '-16 -16 -24' -#define VEC_HULL_MAX '16 16 32' - -#define VEC_HULL2_MIN '-32 -32 -24' -#define VEC_HULL2_MAX '32 32 64' - -// protocol bytes -#define SVC_TEMPENTITY 23 -#define SVC_KILLEDMONSTER 27 -#define SVC_FOUNDSECRET 28 -#define SVC_INTERMISSION 30 -#define SVC_FINALE 31 -#define SVC_CDTRACK 32 -#define SVC_SELLSCREEN 33 - -#define SVC_SMALLKICK 34 -#define SVC_BIGKICK 35 -#define SVC_UPDATEPING 36 -#define SVC_UPDATETIME 37 -#define SVC_MUZZLEFLASH 39 -#define SVC_UPDATEUSERINFO 40 -#define SVC_PLAYERINFO 42 -#define SVC_PACKETENTITIES 47 -#define SVC_DELTAPACKETENTITIES 48 -#define SVC_SETINFO 51 -#define SVC_UPDATEPL 53 - -#define TE_SPIKE 0 -#define TE_SUPERSPIKE 1 -#define TE_GUNSHOT 2 -#define TE_EXPLOSION 3 -#define TE_TAREXPLOSION 4 -#define TE_LIGHTNING1 5 -#define TE_LIGHTNING2 6 -#define TE_WIZSPIKE 7 -#define TE_KNIGHTSPIKE 8 -#define TE_LIGHTNING3 9 -#define TE_LAVASPLASH 10 -#define TE_TELEPORT 11 -#define TE_BLOOD 12 -#define TE_LIGHTNINGBLOOD 13 - -// sound channels -// channel 0 never willingly overrides -// other channels (1-7) allways override a playing sound on that channel -#define CHAN_AUTO 0 -#define CHAN_WEAPON 1 -#define CHAN_VOICE 2 -#define CHAN_ITEM 3 -#define CHAN_BODY 4 -#define CHAN_NO_PHS_ADD 8 - -#define ATTN_NONE 0 -#define ATTN_NORM 1 -#define ATTN_IDLE 2 -#define ATTN_STATIC 3 - -// update types - -#define UPDATE_GENERAL 0 -#define UPDATE_STATIC 1 -#define UPDATE_BINARY 2 -#define UPDATE_TEMP 3 - -// entity effects - -#define EF_BRIGHTFIELD 1 -#define EF_MUZZLEFLASH 2 -#define EF_BRIGHTLIGHT 4 -#define EF_DIMLIGHT 8 -#define EF_FLAG1 16 -#define EF_FLAG2 32 -// GLQuakeWorld Stuff -#define EF_BLUE 64 // Blue Globe effect for Quad -#define EF_RED 128 // Red Globe effect for Pentagram - -// messages -#define MSG_BROADCAST 0 // unreliable to all -#define MSG_ONE 1 // reliable to one (msg_entity) -#define MSG_ALL 2 // reliable to all -#define MSG_INIT 3 // write to the init string -#define MSG_MULTICAST 4 - -// message levels -#define PRINT_LOW 0 // pickup messages -#define PRINT_MEDIUM 1 // death messages -#define PRINT_HIGH 2 // critical messages -#define PRINT_CHAT 3 // also goes to chat console - -// multicast sets -#define MULTICAST_ALL 0 // every client -#define MULTICAST_PHS 1 // within hearing -#define MULTICAST_PVS 2 // within sight -#define MULTICAST_ALL_R 3 // every client, reliable -#define MULTICAST_PHS_R 4 // within hearing, reliable -#define MULTICAST_PVS_R 5 // within sight, reliable - -// attack_state -#define AS_STRAIGHT 1 -#define AS_SLIDING 2 -#define AS_MELEE 3 -#define AS_MISSILE 4 - -//=========================================================================== -// TEAMFORTRESS Defs -//=========================================================================== - -// TeamFortress State Flags -#define TFSTATE_GRENPRIMED 1 // Whether the player has a primed grenade -#define TFSTATE_RELOADING 2 // Whether the player is reloading -#define TFSTATE_ALTKILL 4 // TRUE if killed with a weapon not in self.weapon: NOT USED ANYMORE -#define TFSTATE_RANDOMPC 8 // Whether Playerclass is random, new one each respawn -#define TFSTATE_INFECTED 16 // set when player is infected by the bioweapon -#define TFSTATE_INVINCIBLE 32 // Player has permanent Invincibility (Usually by GoalItem) -#define TFSTATE_INVISIBLE 64 // Player has permanent Invisibility (Usually by GoalItem) -#define TFSTATE_QUAD 128 // Player has permanent Quad Damage (Usually by GoalItem) -#define TFSTATE_RADSUIT 256 // Player has permanent Radsuit (Usually by GoalItem) -#define TFSTATE_BURNING 512 // Is on fire -#define TFSTATE_GRENTHROWING 1024 // is throwing a grenade -#define TFSTATE_AIMING 2048 // is using the laser sight -#define TFSTATE_LOCK 4096 // this state will stop hwguy from shooting assault cannon -#define TFSTATE_RESPAWN_READY 8192 // is waiting for respawn, and has pressed fire -#define TFSTATE_HALLUCINATING 16384 // set when player is hallucinating -#define TFSTATE_TRANQUILISED 32768 // set when player is tranquilised -#define TFSTATE_CANT_MOVE 65536 // set when player is setting a detpack - -// Defines used by TF_T_Damage (see combat.qc) -#define TF_TD_IGNOREARMOUR 1 // Bypasses the armour of the target -#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) -#define TF_TD_NOTSELF 4 // Doesn't damage self - -#define TF_TD_OTHER 0 // Ignore armorclass -#define TF_TD_SHOT 1 // Bullet damage -#define TF_TD_NAIL 2 // Nail damage -#define TF_TD_EXPLOSION 4 // Explosion damage -#define TF_TD_ELECTRICITY 8 // Electric damage -#define TF_TD_FIRE 16 // Fire damage -#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc - -// Classic Fortress stuff -#define CF_MAPVOTE_FORCESHOW 10 // Seconds to force the mapvote menu to be open -#define CF_MAPVOTE_FINISH 5 // Seconds before timelimit to close all voting -#define CF_MAPVOTE_DURATION 180 // Seconds to show map vote menu -#define CF_MAPVOTE_DURATION_DECIDER 90 // Seconds to show map decider menu - -/*======================================================*/ -/* Toggleable Game Settings */ -/*======================================================*/ - -// Some of the toggleflags aren't used anymore, but the bits are still -// there to provide compatability with old maps -#define TFLAG_CLASS_PERSIST 1 // Persistent Classes Bit -#define TFLAG_CHEATCHECK 2 // Cheatchecking Bit -#define TFLAG_RESPAWNDELAY 4 // RespawnDelay bit -#define TFLAG_UN 8 // NOT USED ANYMORE -#define TFLAG_UN2 16 // NOT USED ANYMORE -#define TFLAG_UN3 32 // NOT USED ANYMORE -#define TFLAG_AUTOTEAM 64 // sets whether players are automatically placed in teams -#define TFLAG_TEAMFRAGS 128 // Individual Frags, or Frags = TeamScore -#define TFLAG_FIRSTENTRY 256 // Used to determine the first time toggleflags is set - // in a map. Cannot be toggled by players. -// unused 512 -#define TFLAG_GRAPPLE 1024 // Grapple on/off -#define TFLAG_FULLTEAMSCORE 2048 -#define TFLAG_FLAGEMU 4096 -#define TFLAG_WARSTANDARD 8192 - -#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn -#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn -#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn - -#define TEAMPLAY_NORMAL 1 -#define TEAMPLAY_HALFDIRECT 2 -#define TEAMPLAY_NODIRECT 4 -#define TEAMPLAY_HALFEXPLOSIVE 8 -#define TEAMPLAY_NOEXPLOSIVE 16 -#define TEAMPLAY_LESSPLAYERSHELP 32 -#define TEAMPLAY_LESSSCOREHELP 64 - -// FortressMap stuff -#define TEAM1_CIVILIANS 1 -#define TEAM2_CIVILIANS 2 -#define TEAM3_CIVILIANS 4 -#define TEAM4_CIVILIANS 8 - -// Defines for the playerclass -#define PC_UNDEFINED 0 - -#define PC_SCOUT 1 -#define PC_SNIPER 2 -#define PC_SOLDIER 3 -#define PC_DEMOMAN 4 -#define PC_MEDIC 5 -#define PC_HVYWEAP 6 -#define PC_PYRO 7 -#define PC_SPY 8 -#define PC_ENGINEER 9 - -// Insert new class definitions here - -// PC_RANDOM _MUST_ be the third last class -#define PC_RANDOM 10 // Random playerclass -#define PC_CIVILIAN 11 // Civilians are a special class. They cannot - // be chosen by players, only enforced by maps -#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops - // through the playerclass. - -/*======================================================*/ -/* Impulse Defines */ -/*======================================================*/ -#define TF_SLOT1 1 // Changes weapon to slot 1 (primary weapon) -#define TF_SLOT2 2 // Changes weapon to slot 2 (secondary weapon) -#define TF_SLOT3 3 // Changes weapon to slot 3 (tertiary weapon) -#define TF_SLOT4 4 // Changes weapon to slot 4 (melee weapon) -#define TF_CLASSMENU 5 // Brings up class menu -// unused 6 -// unused 7 -// unused 8 -// unused 9 -// unused 10 -#define TF_WEAPNEXT 11 // Selects the next weapon slot -#define TF_WEAPPREV 12 // Selects the previous weapon slot -#define TF_WEAPLAST 13 // Selects the last used weapon slot -#define TF_GRENADE_1 14 // Prime grenade type 1 -#define TF_GRENADE_2 15 // Prime grenade type 2 -#define TF_GRENADE_T 16 // Throw primed grenade -#define TF_GRENADE_PT_1 17 // Prime and throw grenade type 1 (two clicks) -#define TF_GRENADE_PT_2 18 // Prime and throw grenade type 2 (two clicks) -#define TF_GRENADE_SWITCH 19 // Switch grenade mode 1/2 -#define TF_QUICKSLOT1 20 // Fire weapon slot 1 and then switch back to current weapon -#define TF_QUICKSLOT2 21 // Fire weapon slot 2 and then switch back to current weapon -#define TF_QUICKSLOT3 22 // Fire weapon slot 3 and then switch back to current weapon -#define TF_QUICKSLOT4 23 // Fire weapon slot 4 and then switch back to current weapon -#define TF_QUICKSTOP 24 // Used to tell server that quick firing has stopped -#define TF_RELOAD_SLOT1 25 // Reload weapon slot 1 -#define TF_RELOAD_SLOT2 26 // Reload weapon slot 2 -#define TF_RELOAD_SLOT3 27 // Reload weapon slot 3 -#define TF_RELOAD 28 // Reload current weapon -#define TF_RELOAD_NEXT 29 // Reload next weapon with a non-full clip -#define TF_SPECIAL_SKILL 30 // Class special -#define TF_DROPFLAG 31 // Drop flag -#define TF_DROPKEY 32 // Drop key -#define TF_DISCARD 33 // Discard useless ammo -#define TF_DROP_AMMO 34 // Drop an ammo box on the ground -#define TF_MEDIC_HELPME 35 // Alert players around you that you are in need of medical attention -#define TF_INVENTORY 36 // Displays current inventory -#define FLAG_INFO 37 // Displays current flag location -#define TF_ID 38 // Identify player/object in front of player -#define TF_ID_TEAM 39 // Identify team player/object in front of player -#define TF_ID_ENEMY 40 // Identify enemy player/object in front of player -#define TF_DASH 41 // Scout: Initialize a forward bunnyhop -#define TF_SCAN 42 // Scout: Toggle Scanner on/off -#define TF_SCAN_ENEMY 43 // Scout: Toggle scanning of enemies -#define TF_SCAN_FRIENDLY 44 // Scout: Toggle scanning of friendlies -#define TF_SCAN_SOUND 45 // Scout: Toggle scanner sound -#define TF_ZOOMTOGGLE 46 // Sniper: Toggle zoom mode on/off -#define TF_ZOOMIN 47 // Sniper: Zoom in (while zoom mode is on) -#define TF_ZOOMOUT 48 // Sniper: Zoom out (while zoom mode is on) -#define TF_DEMOMAN_DETPACK 49 // Demoman: Bring up detpack menu -#define TF_DETPACK 50 // Demoman: Detpack Pre-Impulse -#define TF_DETPACK_STOP 51 // Demoman: Impulse to stop setting detpack -#define TF_DETPACK_5 52 // Demoman: Detpack set to 5 seconds -#define TF_DETPACK_20 53 // Demoman: Detpack set to 20 seconds -#define TF_DETPACK_50 54 // Demoman: Detpack set to 50 seconds -#define TF_PB_DETONATE 55 // Demoman: Detonate Pipebombs -#define TF_MEDIC_AURA_TOGGLE 56 // Medic: Toggle Healing Aura on/off -#define TF_LOCKON 57 // HWGuy: Turn Assault Cannon fire on -#define TF_LOCKOFF 58 // HWGuy: Turn Assault Cannon fire off -#define TF_SPY_DIE 59 // Spy: Feign death -#define TF_SPY_SILENT_DIE 60 // Spy: Silent feign death -#define TF_SPY_SPY 61 // Spy: Bring up disguise menu -#define TF_DISGUISE_ENEMY 62 // Spy: Disguise as enemy team -#define TF_DISGUISE_LAST 63 // Spy: Use last disguise -#define TF_DISGUISE_RESET 64 // Spy: Reset disguise -#define TF_DISGUISE_SCOUT 65 // Spy: Disguise as Scout -#define TF_DISGUISE_SNIPER 66 // Spy: Disguise as Sniper -#define TF_DISGUISE_SOLDIER 67 // Spy: Disguise as Soldier -#define TF_DISGUISE_DEMOMAN 68 // Spy: Disguise as Demoman -#define TF_DISGUISE_MEDIC 69 // Spy: Disguise as Medic -#define TF_DISGUISE_HWGUY 70 // Spy: Disguise as HWGuy -#define TF_DISGUISE_PYRO 71 // Spy: Disguise as Pyro -#define TF_DISGUISE_ENGINEER 72 // Spy: Disguise as Engineer -#define TF_DISGUISE_BLUE 73 // Spy: Disguise as blue team -#define TF_DISGUISE_RED 74 // Spy: Disguise as red team -#define TF_DISGUISE_YELLOW 75 // Spy: Disguise as yellow team -#define TF_DISGUISE_GREEN 76 // Spy: Disguise as green team -#define TF_ENGINEER_BUILD 77 // Engineer: Bring up build menu for Engineer -#define TF_ENGINEER_DETDISP 78 // Engineer: Detonate dispenser for Engineer -#define TF_ENGINEER_DETSENTRY 79 // Engineer: Detonate sentry gun for Engineer -// unused 80 -// unused 81 -// unused 82 -// unused 83 -// unused 84 -// unused 85 -// unused 86 -// unused 87 -// unused 88 -// unused 89 -// unused 90 -// unused 91 -// unused 92 -// unused 93 -// unused 94 -// unused 95 -// unused 96 -// unused 97 -// unused 98 -// unused 99 -#define TF_CHANGETEAM 100 // Bring up team selection menu -#define TF_TEAM_1 101 // Join team 1 -#define TF_TEAM_2 102 // Join team 2 -#define TF_TEAM_3 103 // Join team 3 -#define TF_TEAM_4 104 // Join team 4 -#define TF_DISPLAYLOCATION 105 // Displays current location and angles (for developers) -#define TF_SHOWTF 106 // Displays server settings and mod version -#define TF_SHOWLEGALCLASSES 107 // Show what classes are allowed by current map -#define TF_SHOW_IDS 108 // Show ids of connected players -#define TF_ALIAS_CHECK 109 // Check if client has gotten all the aliases -#define TF_CHANGECLASS 110 // Bring up class selection menu -#define TF_CHANGEPC_SCOUT 111 // Change class to Scout -#define TF_CHANGEPC_SNIPER 112 // Change class to Sniper -#define TF_CHANGEPC_SOLDIER 113 // Change class to Soldier -#define TF_CHANGEPC_DEMOMAN 114 // Change class to Demoman -#define TF_CHANGEPC_MEDIC 115 // Change class to Medic -#define TF_CHANGEPC_HVYWEAP 116 // Change class to HWGuy -#define TF_CHANGEPC_PYRO 117 // Change class to Pyro -#define TF_CHANGEPC_SPY 118 // Change class to Spy -#define TF_CHANGEPC_ENGINEER 119 // Change class to Engineer -#define TF_CHANGEPC_RANDOM 120 // Change class to RandomPC -#define TF_HELP_MAP 121 // Displays current map objectives -#define TF_CLASSHELP 122 // Class help alias -#define TF_TEAM_CLASSES 123 // Display team classes -#define TF_TEAM_LIST 124 // Display the players in each team -#define TF_TEAM_SCORES 125 // Display team scores -#define TF_STATUS_QUERY 126 // Displays current team balance and equilization ratios -#define TF_NEXTTIP 127 // Shows the next general/class tip -// unused 128 -// unused 129 -#define TF_TOGGLEVOTE 130 // Toggle vote menu on/off -#define TF_VOTENEXT 131 // Vote to start voting for next map -#define TF_VOTETRICK 132 // Vote to start voting for a trick map -#define TF_VOTERACE 133 // Vote to start voting for a race map -#define TF_FORCENEXT 134 // Vote to force a change to voted map -// unused 135 -// unused 136 -// unused 137 -// unused 138 -// unused 139 -// unused 140 -// unused 141 -// unused 142 -// unused 143 -// unused 144 -// unused 145 -// unused 146 -// unused 147 -// unused 148 -// unused 149 -// unused 150 -// unused 151 -// unused 152 -// unused 153 -// unused 154 -// unused 155 -// unused 156 -// unused 157 -// unused 158 -// unused 159 -// unused 160 -// unused 161 -// unused 162 -// unused 163 -// unused 164 -// unused 165 -// unused 166 -// unused 167 -// unused 168 -// unused 169 -// unused 170 -// unused 171 -// unused 172 -// unused 173 -// unused 174 -// unused 175 -// unused 176 -// unused 177 -// unused 178 -// unused 179 -// unused 180 -// unused 181 -// unused 182 -// unused 183 -// unused 184 -// unused 185 -// unused 186 -// unused 187 -// unused 188 -// unused 189 -// unused 190 -// unused 191 -// unused 192 -// unused 193 -// unused 194 -// unused 195 -// unused 196 -// unused 197 -// unused 198 -// unused 199 -// unused 200 -// unused 201 -// unused 202 -// unused 203 -// unused 204 -// unused 205 -// unused 206 -// unused 207 -// unused 208 -// unused 209 -// unused 210 -// unused 211 -// unused 212 -// unused 213 -// unused 214 -// unused 215 -// unused 216 -// unused 217 -// unused 218 -// unused 219 -// unused 220 -// unused 221 -// unused 222 -// unused 223 -// unused 224 -// unused 225 -// unused 226 -// unused 227 -// unused 228 -// unused 229 -// unused 230 -// unused 231 -// unused 232 -// unused 233 -// unused 234 -// unused 235 -// unused 236 -// unused 237 -// unused 238 -// unused 239 -// unused 240 -// unused 241 -// unused 242 -// unused 243 -// unused 244 -// unused 245 -// unused 246 -// unused 247 -// unused 248 -// unused 249 -// unused 250 -// unused 251 -// unused 252 -// unused 253 -// unused 254 -// unused 255 - -/*======================================================*/ -/* Colors */ -/*======================================================*/ -#define WHITE 1 -#define BROWN 2 -#define BLUE 3 -#define GREEN 4 -#define RED 5 -#define TAN 6 -#define PINK 7 -#define ORANGE 8 -#define PURPLE 9 -#define DARKPURPLE 10 -#define GREY 11 -#define DARKGREEN 12 -#define YELLOW 13 -#define DARKBLUE 14 - -/*======================================================*/ -/* Defines for the ENGINEER's Building ability */ -/*======================================================*/ -// Ammo costs -#define AMMO_COST_SHELLS 3 // Metal needed to make 1 shell -#define AMMO_COST_NAILS 2 -#define AMMO_COST_ROCKETS 5 -#define AMMO_COST_CELLS 5 - -// Building types -#define BUILD_DISPENSER 1 -#define BUILD_SENTRYGUN 2 - -// Building metal costs -#define BUILD_COST_DISPENSER 100 -#define BUILD_COST_SENTRYGUN 130 - -// Building times -#define BUILD_TIME_DISPENSER 2 // 2 seconds to build -#define BUILD_TIME_SENTRYGUN 5 // 5 seconds to build - -// Building health levels -#define BUILD_HEALTH_DISPENSER 150 -#define BUILD_HEALTH_SENTRYGUN 150 - -// Dispenser's maximum carrying capability -#define BUILD_DISPENSER_MAX_SHELLS 400 -#define BUILD_DISPENSER_MAX_NAILS 600 -#define BUILD_DISPENSER_MAX_ROCKETS 300 -#define BUILD_DISPENSER_MAX_CELLS 400 -#define BUILD_DISPENSER_MAX_ARMOR 500 - -/*======================================================*/ -/* Ammo quantities for dropping */ -/*======================================================*/ -#define DROP_SHELLS 20 -#define DROP_NAILS 20 -#define DROP_ROCKETS 10 -#define DROP_CELLS 10 -#define DROP_ARMOR 40 - -/*======================================================*/ -/* Team Defines */ -/*======================================================*/ -#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. - // A new global to hold new team colors is needed, and more flags - // in the spawnpoint spawnflags may need to be used. - // Basically, don't change this unless you know what you're doing :) - -/*======================================================*/ -/* New Weapon Defines */ -/*======================================================*/ - -#define WEAP_HOOK 1 -// unused 2 -#define WEAP_MEDIKIT 4 -#define WEAP_SPANNER 8 -#define WEAP_AXE 16 -#define WEAP_SNIPER_RIFLE 32 -#define WEAP_AUTO_RIFLE 64 -#define WEAP_SHOTGUN 128 -#define WEAP_SUPER_SHOTGUN 256 -#define WEAP_NAILGUN 512 -#define WEAP_SUPER_NAILGUN 1024 -#define WEAP_GRENADE_LAUNCHER 2048 -#define WEAP_FLAMETHROWER 4096 -#define WEAP_ROCKET_LAUNCHER 8192 -#define WEAP_INCENDIARY 16384 -#define WEAP_ASSAULT_CANNON 32768 -#define WEAP_LIGHTNING 65536 -#define WEAP_DETPACK 131072 -#define WEAP_TRANQ 262144 -#define WEAP_LASER 524288 -// still room for 12 more weapons -// but we can remove some by giving the weapons -// a weapon mode (like the rifle) - -/*======================================================*/ -/* New Weapon Related Defines */ -/*======================================================*/ -// shots per reload -#define RE_SHOTGUN 8 -#define RE_SUPER_SHOTGUN 16 // 8 shots -#define RE_GRENADE_LAUNCHER 6 -#define RE_ROCKET_LAUNCHER 4 - -// reload times -#define RE_SHOTGUN_TIME 2 -#define RE_SUPER_SHOTGUN_TIME 3 -#define RE_GRENADE_LAUNCHER_TIME 4 -#define RE_ROCKET_LAUNCHER_TIME 5 - -// Maximum velocity you can move and fire the Sniper Rifle -#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 - -// Medikit -#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit -#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense - -// Spanner -#define WEAP_SPANNER_REPAIR 10 - -// Detpack -#define WEAP_DETPACK_DISARMTIME 3 // Time it takes to disarm a Detpack -#define WEAP_DETPACK_SETTIME 4 // Time it takes to set a Detpack -#define WEAP_DETPACK_SIZE 1500 -#define WEAP_DETPACK_BITS_NO 12 // Bits that detpack explodes into - -// Tranquiliser Gun -#define TRANQ_TIME 15 - -// Grenades -#define GR_PRIMETIME 3 -#define GR_TYPE_NONE 0 -#define GR_TYPE_NORMAL 1 -#define GR_TYPE_CONCUSSION 2 -#define GR_TYPE_NAIL 3 -#define GR_TYPE_MIRV 4 -#define GR_TYPE_NAPALM 5 -#define GR_TYPE_FLARE 6 -#define GR_TYPE_GAS 7 -#define GR_TYPE_EMP 8 -#define GR_TYPE_FLASH 9 -#define GR_TYPE_CALTROP 10 - -// Defines for WeaponMode -#define GL_NORMAL 0 -#define GL_PIPEBOMB 1 - -// Defines for Concussion Grenade -#define GR_CONCUSS_TIME 5 -#define GR_CONCUSS_DEC 20 - -// Defines for the Gas Grenade -#define GR_HALLU_TIME 0.5 -#define GR_HALLU_DEC 2.5 - -/*======================================================*/ -/* New Items */ -/*======================================================*/ -#define NIT_SCANNER 1 - -#define NIT_SILVER_DOOR_OPENED IT_KEY1 /* 131072 */ -#define NIT_GOLD_DOOR_OPENED IT_KEY2 /* 262144 */ - -/*======================================================*/ -/* New Item Flags */ -/*======================================================*/ -#define NIT_SCANNER_ENEMY 1 // Detect enemies -#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) -#define NIT_SCANNER_MOVEMENT 4 // Motion detection. Only report moving entities. - -/*======================================================*/ -/* New Item Related Defines */ -/*======================================================*/ -#define NIT_SCANNER_POWER 100 // The amount of power spent on a scan with the scanner - // is multiplied by this to get the scanrange. -#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan -#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected - // by scanners that only detect movement - -/*======================================================*/ -/* Variables used for New Weapons and Reloading */ -/*======================================================*/ - -// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. -#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% -#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% -#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% -#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% -#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% - -/*======================================================================*/ -/* TEAMFORTRESS CLASS DETAILS */ -/*======================================================================*/ -// Class Details for SCOUT -#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. -#define PC_SCOUT_MAXHEALTH 75 // Maximum Health Level -#define PC_SCOUT_MAXSPEED 450 // Maximum movement speed -#define PC_SCOUT_MAXSTRAFESPEED 450 // Maximum strafing movement speed -#define PC_SCOUT_MAXARMOR 50 // Maximum Armor Level, of any armor class -#define PC_SCOUT_INITARMOR 25 // Armor level when respawned -#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption -#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned -#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class -#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned -#define PC_SCOUT_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_NAILGUN -#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry -#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry -#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry -#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry -#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned -#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned -#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned -#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned -#define PC_SCOUT_GRENADE_TYPE_1 GR_TYPE_FLASH // <- 1st Type of Grenade this class has -#define PC_SCOUT_GRENADE_TYPE_2 GR_TYPE_CONCUSSION // <- 2nd Type of Grenade this class has -#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned -#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned -#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has - -#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range -#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector - -// Class Details for SNIPER -#define PC_SNIPER_SKIN 5 -#define PC_SNIPER_MAXHEALTH 90 -#define PC_SNIPER_MAXSPEED 300 -#define PC_SNIPER_MAXSTRAFESPEED 300 -#define PC_SNIPER_MAXARMOR 50 -#define PC_SNIPER_INITARMOR 0 -#define PC_SNIPER_MAXARMORTYPE 0.3 -#define PC_SNIPER_INITARMORTYPE 0.3 -#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL -#define PC_SNIPER_INITARMORCLASS 0 -#define PC_SNIPER_WEAPONS WEAP_SNIPER_RIFLE | WEAP_AUTO_RIFLE | WEAP_AXE | WEAP_NAILGUN -#define PC_SNIPER_MAXAMMO_SHOT 75 -#define PC_SNIPER_MAXAMMO_NAIL 100 -#define PC_SNIPER_MAXAMMO_CELL 50 -#define PC_SNIPER_MAXAMMO_ROCKET 25 -#define PC_SNIPER_INITAMMO_SHOT 60 -#define PC_SNIPER_INITAMMO_NAIL 50 -#define PC_SNIPER_INITAMMO_CELL 0 -#define PC_SNIPER_INITAMMO_ROCKET 0 -#define PC_SNIPER_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_SNIPER_GRENADE_TYPE_2 GR_TYPE_FLARE -#define PC_SNIPER_GRENADE_INIT_1 2 -#define PC_SNIPER_GRENADE_INIT_2 3 -#define PC_SNIPER_TF_ITEMS 0 - -// Class Details for SOLDIER -#define PC_SOLDIER_SKIN 6 -#define PC_SOLDIER_MAXHEALTH 100 -#define PC_SOLDIER_MAXSPEED 240 -#define PC_SOLDIER_MAXSTRAFESPEED 240 -#define PC_SOLDIER_MAXARMOR 200 -#define PC_SOLDIER_INITARMOR 100 -#define PC_SOLDIER_MAXARMORTYPE 0.8 -#define PC_SOLDIER_INITARMORTYPE 0.8 -#define PC_SOLDIER_ARMORCLASSES 31 // ALL -#define PC_SOLDIER_INITARMORCLASS 0 -#define PC_SOLDIER_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_ROCKET_LAUNCHER -#define PC_SOLDIER_MAXAMMO_SHOT 100 -#define PC_SOLDIER_MAXAMMO_NAIL 100 -#define PC_SOLDIER_MAXAMMO_CELL 50 -#define PC_SOLDIER_MAXAMMO_ROCKET 50 -#define PC_SOLDIER_INITAMMO_SHOT 50 -#define PC_SOLDIER_INITAMMO_NAIL 0 -#define PC_SOLDIER_INITAMMO_CELL 0 -#define PC_SOLDIER_INITAMMO_ROCKET 10 -#define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL -#define PC_SOLDIER_GRENADE_INIT_1 4 -#define PC_SOLDIER_GRENADE_INIT_2 1 -#define PC_SOLDIER_TF_ITEMS 0 - -// Class Details for DEMOLITION MAN -#define PC_DEMOMAN_SKIN 1 -#define PC_DEMOMAN_MAXHEALTH 90 -#define PC_DEMOMAN_MAXSPEED 280 -#define PC_DEMOMAN_MAXSTRAFESPEED 280 -#define PC_DEMOMAN_MAXARMOR 120 -#define PC_DEMOMAN_INITARMOR 50 -#define PC_DEMOMAN_MAXARMORTYPE 0.6 -#define PC_DEMOMAN_INITARMORTYPE 0.6 -#define PC_DEMOMAN_ARMORCLASSES 31 // ALL -#define PC_DEMOMAN_INITARMORCLASS 0 //4 // AT_SAVEEXPLOSION -#define PC_DEMOMAN_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_GRENADE_LAUNCHER | WEAP_DETPACK -#define PC_DEMOMAN_MAXAMMO_SHOT 75 -#define PC_DEMOMAN_MAXAMMO_NAIL 50 -#define PC_DEMOMAN_MAXAMMO_CELL 50 -#define PC_DEMOMAN_MAXAMMO_ROCKET 50 -#define PC_DEMOMAN_MAXAMMO_DETPACK 1 -#define PC_DEMOMAN_INITAMMO_SHOT 30 -#define PC_DEMOMAN_INITAMMO_NAIL 0 -#define PC_DEMOMAN_INITAMMO_CELL 0 -#define PC_DEMOMAN_INITAMMO_ROCKET 20 -#define PC_DEMOMAN_INITAMMO_DETPACK 1 -#define PC_DEMOMAN_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_DEMOMAN_GRENADE_TYPE_2 GR_TYPE_MIRV -#define PC_DEMOMAN_GRENADE_INIT_1 4 -#define PC_DEMOMAN_GRENADE_INIT_2 4 -#define PC_DEMOMAN_TF_ITEMS 0 - -// Class Details for COMBAT MEDIC -#define PC_MEDIC_SKIN 3 -#define PC_MEDIC_MAXHEALTH 90 -#define PC_MEDIC_MAXSPEED 320 -#define PC_MEDIC_MAXSTRAFESPEED 320 -#define PC_MEDIC_MAXARMOR 100 -#define PC_MEDIC_INITARMOR 50 -#define PC_MEDIC_MAXARMORTYPE 0.6 -#define PC_MEDIC_INITARMORTYPE 0.3 -#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION -#define PC_MEDIC_INITARMORCLASS 0 -#define PC_MEDIC_WEAPONS WEAP_MEDIKIT | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_SUPER_NAILGUN -#define PC_MEDIC_MAXAMMO_SHOT 75 -#define PC_MEDIC_MAXAMMO_NAIL 150 -#define PC_MEDIC_MAXAMMO_CELL 100 -#define PC_MEDIC_MAXAMMO_ROCKET 25 -#define PC_MEDIC_MAXAMMO_MEDIKIT 100 -#define PC_MEDIC_INITAMMO_SHOT 50 -#define PC_MEDIC_INITAMMO_NAIL 50 -#define PC_MEDIC_INITAMMO_CELL 0 -#define PC_MEDIC_INITAMMO_ROCKET 0 -#define PC_MEDIC_INITAMMO_MEDIKIT 50 -#define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION -#define PC_MEDIC_GRENADE_INIT_1 3 -#define PC_MEDIC_GRENADE_INIT_2 2 -#define PC_MEDIC_TF_ITEMS 0 -#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. -#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. -#define PC_MEDIC_AURA_HEAL_TIME 1 // Number of seconds between each aura heal. -#define PC_MEDIC_AURA_HEAL_AMOUNT 5 // Amount of health given per aura heal. -#define PC_MEDIC_AURA_RANGE 120 // The aura's range -#define PC_MEDIC_CELL_REGEN_TIME 1 // Number of seconds between each cell regen. -#define PC_MEDIC_CELL_REGEN_AMOUNT 10 // Amount of cells regenerated each cell regen. -#define PC_MEDIC_CELL_REGEN_CD 5 // Seconds to cooldown cell regeneration after healing with medikit. -#define PC_MEDIC_SAVEME_GRACE 5 // Seconds after which /saveme gives grace period to medikit (no cell regeneration cooldown) - -// Class Details for HVYWEAP -#define PC_HVYWEAP_SKIN 2 -#define PC_HVYWEAP_MAXHEALTH 100 -#define PC_HVYWEAP_MAXSPEED 230 -#define PC_HVYWEAP_MAXSTRAFESPEED 230 -#define PC_HVYWEAP_MAXARMOR 300 -#define PC_HVYWEAP_INITARMOR 150 -#define PC_HVYWEAP_MAXARMORTYPE 0.8 -#define PC_HVYWEAP_INITARMORTYPE 0.8 -#define PC_HVYWEAP_ARMORCLASSES 31 // ALL -#define PC_HVYWEAP_INITARMORCLASS 0 -#define PC_HVYWEAP_WEAPONS WEAP_ASSAULT_CANNON | WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN -#define PC_HVYWEAP_MAXAMMO_SHOT 200 -#define PC_HVYWEAP_MAXAMMO_NAIL 200 -#define PC_HVYWEAP_MAXAMMO_CELL 50 -#define PC_HVYWEAP_MAXAMMO_ROCKET 25 -#define PC_HVYWEAP_INITAMMO_SHOT 200 -#define PC_HVYWEAP_INITAMMO_NAIL 0 -#define PC_HVYWEAP_INITAMMO_CELL 30 -#define PC_HVYWEAP_INITAMMO_ROCKET 0 -#define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV -#define PC_HVYWEAP_GRENADE_INIT_1 4 -#define PC_HVYWEAP_GRENADE_INIT_2 1 -#define PC_HVYWEAP_TF_ITEMS 0 - - -// Class Details for PYRO -#define PC_PYRO_SKIN 21 -#define PC_PYRO_MAXHEALTH 100 -#define PC_PYRO_MAXSPEED 300 -#define PC_PYRO_MAXSTRAFESPEED 300 -#define PC_PYRO_MAXARMOR 150 -#define PC_PYRO_INITARMOR 50 -#define PC_PYRO_MAXARMORTYPE 0.6 -#define PC_PYRO_INITARMORTYPE 0.6 -#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION -#define PC_PYRO_INITARMORCLASS 16 // AT_SAVEFIRE -#define PC_PYRO_WEAPONS WEAP_INCENDIARY | WEAP_FLAMETHROWER | WEAP_AXE | WEAP_SHOTGUN -#define PC_PYRO_MAXAMMO_SHOT 40 -#define PC_PYRO_MAXAMMO_NAIL 50 -#define PC_PYRO_MAXAMMO_CELL 200 -#define PC_PYRO_MAXAMMO_ROCKET 60 -#define PC_PYRO_INITAMMO_SHOT 20 -#define PC_PYRO_INITAMMO_NAIL 0 -#define PC_PYRO_INITAMMO_CELL 120 -#define PC_PYRO_INITAMMO_ROCKET 15 -#define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM -#define PC_PYRO_GRENADE_INIT_1 1 -#define PC_PYRO_GRENADE_INIT_2 4 -#define PC_PYRO_TF_ITEMS 0 - -// Class Details for SPY -#define PC_SPY_SKIN 22 -#define PC_SPY_MAXHEALTH 90 -#define PC_SPY_MAXSPEED 300 -#define PC_SPY_MAXSTRAFESPEED 300 -#define PC_SPY_MAXARMOR 100 -#define PC_SPY_INITARMOR 25 -#define PC_SPY_MAXARMORTYPE 0.6 -#define PC_SPY_INITARMORTYPE 0.6 -#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION -#define PC_SPY_INITARMORCLASS 0 -#define PC_SPY_WEAPONS WEAP_AXE | WEAP_TRANQ | WEAP_SUPER_SHOTGUN | WEAP_NAILGUN -#define PC_SPY_MAXAMMO_SHOT 40 -#define PC_SPY_MAXAMMO_NAIL 100 -#define PC_SPY_MAXAMMO_CELL 30 -#define PC_SPY_MAXAMMO_ROCKET 15 -#define PC_SPY_INITAMMO_SHOT 40 -#define PC_SPY_INITAMMO_NAIL 50 -#define PC_SPY_INITAMMO_CELL 10 -#define PC_SPY_INITAMMO_ROCKET 0 -#define PC_SPY_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_SPY_GRENADE_TYPE_2 GR_TYPE_GAS -#define PC_SPY_GRENADE_INIT_1 2 -#define PC_SPY_GRENADE_INIT_2 2 -#define PC_SPY_TF_ITEMS 0 -#define PC_SPY_CELL_REGEN_TIME 5 -#define PC_SPY_CELL_REGEN_AMOUNT 1 -#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible -#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover - -// Class Details for ENGINEER -#define PC_ENGINEER_SKIN 22 // Not used anymore -#define PC_ENGINEER_MAXHEALTH 80 -#define PC_ENGINEER_MAXSPEED 300 -#define PC_ENGINEER_MAXSTRAFESPEED 300 -#define PC_ENGINEER_MAXARMOR 50 -#define PC_ENGINEER_INITARMOR 25 -#define PC_ENGINEER_MAXARMORTYPE 0.6 -#define PC_ENGINEER_INITARMORTYPE 0.3 -#define PC_ENGINEER_ARMORCLASSES 31 // ALL -#define PC_ENGINEER_INITARMORCLASS 0 -#define PC_ENGINEER_WEAPONS WEAP_SPANNER | WEAP_LASER | WEAP_SUPER_SHOTGUN -#define PC_ENGINEER_MAXAMMO_SHOT 50 -#define PC_ENGINEER_MAXAMMO_NAIL 50 -#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal -#define PC_ENGINEER_MAXAMMO_ROCKET 30 -#define PC_ENGINEER_INITAMMO_SHOT 20 -#define PC_ENGINEER_INITAMMO_NAIL 25 -#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal -#define PC_ENGINEER_INITAMMO_ROCKET 0 -#define PC_ENGINEER_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_ENGINEER_GRENADE_TYPE_2 GR_TYPE_EMP -#define PC_ENGINEER_GRENADE_INIT_1 2 -#define PC_ENGINEER_GRENADE_INIT_2 2 -#define PC_ENGINEER_TF_ITEMS 0 - -// Class Details for CIVILIAN -#define PC_CIVILIAN_SKIN 22 -#define PC_CIVILIAN_MAXHEALTH 50 -#define PC_CIVILIAN_MAXSPEED 240 -#define PC_CIVILIAN_MAXSTRAFESPEED 240 -#define PC_CIVILIAN_MAXARMOR 0 -#define PC_CIVILIAN_INITARMOR 0 -#define PC_CIVILIAN_MAXARMORTYPE 0 -#define PC_CIVILIAN_INITARMORTYPE 0 -#define PC_CIVILIAN_ARMORCLASSES 0 -#define PC_CIVILIAN_INITARMORCLASS 0 -#define PC_CIVILIAN_WEAPONS WEAP_AXE -#define PC_CIVILIAN_MAXAMMO_SHOT 0 -#define PC_CIVILIAN_MAXAMMO_NAIL 0 -#define PC_CIVILIAN_MAXAMMO_CELL 0 -#define PC_CIVILIAN_MAXAMMO_ROCKET 0 -#define PC_CIVILIAN_INITAMMO_SHOT 0 -#define PC_CIVILIAN_INITAMMO_NAIL 0 -#define PC_CIVILIAN_INITAMMO_CELL 0 -#define PC_CIVILIAN_INITAMMO_ROCKET 0 -#define PC_CIVILIAN_GRENADE_TYPE_1 0 -#define PC_CIVILIAN_GRENADE_TYPE_2 0 -#define PC_CIVILIAN_GRENADE_INIT_1 0 -#define PC_CIVILIAN_GRENADE_INIT_2 0 -#define PC_CIVILIAN_TF_ITEMS 0 - - -/*======================================================================*/ -/* TEAMFORTRESS GOALS */ -/*======================================================================*/ -// For all these defines, see the tfortmap.txt that came with the zip -// for complete descriptions. -// Defines for Goal Activation types : goal_activation (in goals) -#define TFGA_TOUCH 1 // Activated when touched -#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion -#define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met -#define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner - -// Defines for Goal Effects types : goal_effect -#define TFGE_AP 1 // AP is affected. Default. -#define TFGE_AP_TEAM 2 // All of the AP's team. -#define TFGE_NOT_AP_TEAM 4 // All except AP's team. -#define TFGE_NOT_AP 8 // All except AP. -#define TFGE_WALL 16 // If set, walls stop the Radius effects -#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected -#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects - -// Defines for Goal Result types : goal_result -#define TFGR_SINGLE 1 // Goal can only be activated once -#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses -#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level -#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results -#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy -#define TFGR_FORCE_RESPAWN 32 - -// Defines for Goal Group Result types : goal_group -// None! -// But I'm leaving this variable in there, since it's fairly likely -// that some will show up sometime. - -// Defines for Goal Item types, : goal_activation (in items) -#define TFGI_GLOW 1 // Players carrying this GoalItem will glow -#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed -#define TFGI_DROP 4 // Players dying with this item will drop it -#define TFGI_RETURN_DROP 8 // Return if a player with it dies -#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation -#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE -#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details -#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped -#define TFGI_KEEP 256 // Players keep this item even when they die -#define TFGI_ITEMGLOWS 512 // Item glows when on the ground -#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed - -// Defines for TeamSpawnpoints : goal_activation (in team spawns) -#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times -#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times - -// Defines for TeamSpawnpoints : goal_effects (in teamspawns) -#define TFSP_REMOVESELF 1 // Remove itself after being spawned on - -// Defines for Goal States -#define TFGS_ACTIVE 1 -#define TFGS_INACTIVE 2 -#define TFGS_REMOVED 3 -#define TFGS_DELAYED 4 - -// Legal Playerclass Handling -#define TF_ILL_SCOUT 1 -#define TF_ILL_SNIPER 2 -#define TF_ILL_SOLDIER 4 -#define TF_ILL_DEMOMAN 8 -#define TF_ILL_MEDIC 16 -#define TF_ILL_HVYWEP 32 -#define TF_ILL_PYRO 64 -#define TF_ILL_RANDOMPC 128 -#define TF_ILL_SPY 256 -#define TF_ILL_ENGINEER 512 - -/*======================================================================*/ -/* Flamethrower */ -/*======================================================================*/ - -#define FLAME_PLYRMAXTIME 45 // lifetime in 0.1 sec of a flame on a player -#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) -#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade -#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player -#define FLAME_NUMLIGHTS 1 // maximum number of light flame -#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' -#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode - -/*======================================================*/ -/* CTF Support defines */ -/*======================================================*/ -#define CTF_FLAG1 1 -#define CTF_FLAG2 2 -#define CTF_DROPOFF1 3 -#define CTF_DROPOFF2 4 -#define CTF_SCORE1 5 -#define CTF_SCORE2 6 - -/*======================================================*/ -/* Death Message defines */ -/*======================================================*/ - -#define DMSG_SHOTGUN 1 -#define DMSG_SSHOTGUN 2 -#define DMSG_NAILGUN 3 -#define DMSG_SNAILGUN 4 -#define DMSG_GRENADEL 5 -#define DMSG_ROCKETL 6 -#define DMSG_LIGHTNING 7 -#define DMSG_GREN_HAND 8 -#define DMSG_GREN_NAIL 9 -#define DMSG_GREN_MIRV 10 -#define DMSG_GREN_PIPE 11 -#define DMSG_DETPACK 12 -#define DMSG_BIOWEAPON 13 -#define DMSG_BIOWEAPON_ATT 14 -#define DMSG_FLAME 15 -#define DMSG_DETPACK_DIS 16 -#define DMSG_AXE 17 -#define DMSG_SNIPERRIFLE 18 -#define DMSG_AUTORIFLE 19 -#define DMSG_ASSAULTCANNON 20 -#define DMSG_HOOK 21 -#define DMSG_BACKSTAB 22 -#define DMSG_MEDIKIT 23 -#define DMSG_GREN_GAS 24 -#define DMSG_TRANQ 25 -#define DMSG_LASERBOLT 26 -#define DMSG_SENTRYGUN_BULLET 27 -#define DMSG_SNIPERLEGSHOT 28 -#define DMSG_SNIPERHEADSHOT 29 -#define DMSG_GREN_EMP 30 -#define DMSG_GREN_EMP_AMMO 31 -#define DMSG_SPANNER 32 -#define DMSG_INCENDIARY 33 -#define DMSG_SENTRYGUN_ROCKET 34 -#define DMSG_GREN_FLASH 35 -#define DMSG_TRIGGER 36 - -/*======================================================*/ -/* Menus */ -/*======================================================*/ - -#define MENU_DEFAULT 1 -#define MENU_TEAM 2 -#define MENU_CLASS 3 -#define MENU_DROP 4 -#define MENU_INTRO 5 -#define MENU_DEMOMAN 6 -#define MENU_DEMOMAN_CANCEL 7 -#define MENU_REPEATHELP 8 -#define MENU_SPY_SKIN_1 9 -#define MENU_SPY_SKIN_2 10 -#define MENU_SPY_SKIN_3 11 -#define MENU_SPY 12 -#define MENU_SPY_SKIN 13 -#define MENU_SPY_COLOR 14 -#define MENU_ENGINEER 15 -#define MENU_ENGINEER_FIX_DISPENSER 16 -#define MENU_ENGINEER_FIX_SENTRYGUN 17 -#define MENU_SCOUT 18 -#define MENU_DISPENSER 19 - -#define MENU_REFRESH_RATE 25 - -/*======================================================*/ -/* Misc defines */ -/*======================================================*/ - -#define MAX_WORLD_FLAMES 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. -#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams - // - this is the most you should have on a net server -#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams - // - this is the most you should have on a net server -#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into -#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) - -#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. - // 60 is a mild setting, and won't make too much difference - // increasing it _decreases_ the amount of help the losing team gets - // Minimum setting is 1, which would really help the losing team - -#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds - -#define RESPAWN_DELAY_TIME 5 // this is the respawn delay, if the RESPAWN_DELAY option is - // turned on with temp1. QuakeWorld servers can use - // serverinfo respawn_delay to set their own time. diff --git a/docs/hud_statusbar.txt b/docs/hud_statusbar.txt new file mode 100644 index 000000000..016a2a512 --- /dev/null +++ b/docs/hud_statusbar.txt @@ -0,0 +1,37 @@ +The status bar shows TF-specific info, and it works in different ways depending on the client: +For fte, it will detect that csqc is active and send messages directly to the csqc part of the client to display individual HUD icons and statuses. +For ezquake (and basically everything else), it will use a centerprint hack to permanently display the status bar. + +It can be enabled using the client setting + setinfo sb 40 +(or any positive integer). The integer is relevant for the non-fte version, as it dictates how many empty "lines" to skip before drawing it, thus allowing basic control of its veritcal position. + +A specific sub-component can be enabled using the command + setinfo sbflaginfo 1|2 +Setting it to 0 will turn it off, setting it to 1 will enable it, but only 6 seconds after spawn (after the class tips finish displaying) and a setting of 2 will make it permanent. + +The sb flaginfo will display the current status of any flags/item_tfgoals that have been tagged by the mapper. The logic is the same as the stock "flaginfo" command, which is marking up to 4 goalitems in the info_tfdetect +entity using the fields display_item_status1 - display_item_status4 using the items' goal_no numbers. This is further documented in the tf reference docs. +This does limit the number of trackable items to 4 however. +The flaginfo functionality is extended by having it also display who it's currenly carried by, or, if dropped, the location (based on the loc file) and time to return. +Note that currently, the item names are hard-coded to be "blue/red/yellow/green flag", to cover most cases, where mappers didn't necessarily name them nicely. + +The sb flaginfo component also displays the "button" countdowns (such as lasers/security being down), if available, and there is room. +This functions in a similar way to the flag info, but, while technically unlimited, +the combined number of all sbar items (flags and buttons) is 10 for fte and 6 for ezquake. +For ezquake, the buttons will be distinguished by name, and will not appear until they are actually triggered and have a countdown active. +For fte, the buttons are distinguished by team colours and will display a different icon to the flag, but otherwise function similarly. + +For a map to allow these to be tracked, the following needs to be set for each button: +- It must be set on an info_tfgoal entity +- It must have the field "track_goal" be set to 1 +- It must have a "goal_no" field set +- It must have the field "delay_time" set to the countdown time +- It must be triggered at the start of the countdown either via goal_no or targetname. It doesn't have to be part of the main trigger chain however. +- For ezquake, it must have a "netname" field set to distinguish the goals. +- For FTE, it should have the "owned_by" field set which will determine the icon colour. +- Optionally, it can have the field "team_str_moved" set to set a custom string to be displayed while the countdown is active. If not set, the default is "Offline". + +For FTE, the icons for the above are in the same location as the normal hud elements: +ie fortress/textures/wad/ +They are named "flag_" for the flag icons and "off_icon_glow_" for the button icons. diff --git a/docs/tfentref.txt b/docs/tfentref.txt new file mode 100644 index 000000000..a0c1c39af --- /dev/null +++ b/docs/tfentref.txt @@ -0,0 +1,1296 @@ +---------------------------------------------------------------------------- +The New TeamFortress Entity Reference Sheet TF Version 2.6 +---------------------------------------------------------------------------- + +This is a reference sheet for people who're trying to read/debug the +entities on their TF map. It's simply a quick description of every variable +for all the TF entities. It's simply a quick-reference. For full details, +see the tfortmap.txt file. + +Print this out, and keep it by you whenever you're reading through someone +else's .map file to see how they did something :) Also get Raistlan's EntEd +which supports TeamFortress Entities at http://www.lockandload.com/ented/ + +Robin (walker@netspace.net.au) | Network (network@lockandload.com) +---------------------------------------------------------------------------- + +GLOSSARY +-------- +AP : Activating Player. The player who activated a Goal/Trigger, or picked + up the GoalItem. + +APA : Any Player Affected. Any player who is affected by the current + Goal/Trigger/Item's activation. + + +INDEX +-------- + I. : DETECTION ENTITY VARIABLES (info_tfdetect) + II. : TEAM SPAWNPOINT VARIABLES (info_player_teamspawn) +III. : GOAL VARIABLES (info_tfgoal) + IV. : GOALITEM VARIABLES (item_tfgoal) + V. : TIMER GOAL VARIABLES (info_tfgoal_timer) + VI. : OTHER QUAKE ENTITY VARIABLES +VII. : VARIABLE ABBREVIATION LIST + + +============================================================================ +DETECTION ENTITY VARIABLES (info_tfdetect) +============================================================================ + +broadcast The TF version string. e.g. "TeamFortress v2.6" + +impulse Initial Toggleflags status. Bitfields: (Unspecified = OFF) + 1 : ClasSkin Off/On + 2 : ClassPersistence Off/On + 4 : CheatChecking Off/On + 8 : FortressMap Off/On + 16 : RespawnDelay See tfortmap.txt + 32 : RespawnDelay See tfortmap.txt + 64 : AutoTeam Off/On + 128 : Individual Frags Off/On + +message Commands that are localcmd'd into the server when the map + first starts. e.g. "sv_gravity 300\nsv_friction 0.1\n" + Be sure to end all commands with '\n'. + +ammo_shells Max number of lives for each player in Team 1. +ammo_nails Max number of lives for each player in Team 2. +ammo_rockets Max number of lives for each player in Team 3. +ammo_cells Max number of lives for each player in Team 4. + (0 = Unlimited number of lives) + +ammo_medikit Maximum number of players allowed in Team 1. +ammo_detpack Maximum number of players allowed in Team 2. +maxammo_medikit Maximum number of players allowed in Team 3. +maxammo_detpack Maximum number of players allowed in Team 4. + (0 = Unlimited number of players) + +maxammo_shells Illegal Classes for Team 1. +maxammo_nails Illegal Classes for Team 2. +maxammo_rockets Illegal Classes for Team 3. +maxammo_cells Illegal Classes for Team 4. + -1 : Civilian Only. (Cannot Specify Others) + 1 : No Scout + 2 : No Sniper + 4 : No Soldier + 8 : No Demolitions Man + 16 : No Combat Medic + 32 : No Heavy Weapons Guy + 64 : No Pyro + 128 : No Random PlayerClass + 256 : No Spy + 512 : No Engineer + +hook_out If 1, the grappling hook cannot be used on this map. + Else, this does not need to be specified. + +display_item_status1 On FlagInfo command, display this GoalItem status +display_item_status2 On FlagInfo command, display this GoalItem status +display_item_status3 On FlagInfo command, display this GoalItem status +display_item_status4 On FlagInfo command, display this GoalItem status + +team_str_home String displayed to the owners of an Item + in an Item Status if the Item is at it's origin. + If not the owner of the Item, non_team_str_home + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is in it's base" + +team_str_moved String displayed to the owners of an Item in an + Item Status if the Item is not at it's origin. + If not the owner of the Item, non_team_str_moved + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is lying around" + +team_str_carried String displayed to the owners of an Item in an Item + Status if the Item is being carried by a player. The + player's name is appended to the String. If not the + owner of the Item, non_team_str_carried will be used. + Refer to the "owned_by" variable for GoalItems. + e.g. "Your flag is being carried by" < players's_name> + +non_team_str_home String displayed to everyone except the owners of an + Item in an Item Status if the Item is at it's origin. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is in it's base" + +non_team_str_moved String displayed to everyone except the owners of an + Item in an Item Status if the Item is not at it's origin. + If they are owner of the Item, team_str_moved will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is lying around" + +non_team_str_carried String displayed to everyone except the owners of an + Item in an Item Status if the Item is being carried by a + player. The player's name is appended to the String. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is being carried by" < player's_name> + +team_broadcast String that replaces the Team Menu. + (Line breaks should be used: \n) + +non_team_broadcast String that is displayed in the Map Help command. + (Line breaks could be used: \n) + +noise1 String that replaces the Class Menu for Team 1. +noise2 String that replaces the Class Menu for Team 2. +noise3 String that replaces the Class Menu for Team 3. +noise4 String that replaces the Class Menu for Team 4. + (Line breaks should be used: \n) + + +============================================================================ +TEAM SPAWNPOINT VARIABLES (info_player_teamspawn) +============================================================================ + +netname The name of the spawn point. (A reference) + +goal_no Unique ID number of this spawn. + +group_no ID Number of the spawn group this spawn belongs to. + +team_no Only players on this team spawn here. + +items GoalItem ID is given to the first player who + spawn here. + +axhitme Goal removes this GoalItem from APA that has it. + +message A String displayed to the first player who + spawns here. + +goal_activation Bitfields: + 1 : Give the GoalItem in "items" to every player who + spawns here instead of just the first player. + 2 : Display the "message" to every player who spawns + here instead of just the first player. + +goal_effects Bitfields: + 1 : The spawnpoint removes itself after the first + player has spawned here. + +activate_goal_no Activate this Goal. + +items_allowed AP needs this GoalItem to meet this Goal's criteria. + +has_item_from_group AP must have GoalItem from group "group_no". + +playerclass AP must be this playerclass to meet this Goal's criteria. + You can not add these up, you can only specify one player + class. Bitfields: + 1 : Only allow a Scout to activate + 2 : Only allow a Sniper to activate + 3 : Only allow a Soldier to activate + 4 : Only allow a Demolition Man to activate + 5 : Only allow a Medic to activate + 6 : Only allow a Heavy Weapons Guy to activate + 7 : Only allow a Pyro to activate + 8 : Only allow a Spy to activate + 9 : Only allow an Engineer to activate + +if_goal_is_active This Goal must be in ACTIVE state. +if_goal_is_inactive This Goal must be in INACTIVE state. +if_goal_is_removed This Goal must be in REMOVED state. +if_group_is_active All Goals in this Group must be in ACTIVE state. +if_group_is_inactive All Goals in this Group must be in INACTIVE state. +if_group_is_removed All Goals in this Group must be in REMOVED state. +if_item_has_moved This GoalItem must not have moved from it's origin, + and must not be being carried. +if_item_hasnt_moved This GoalItem must have moved from it's origin, + or be being carried. + +target Activate any Goal/Quake Trigger with this targetname + +killtarget Remove any Goal/Quake Trigger with this targetname + +goal_state The initial state of this entity. The default + (nothing specified) is Inactive the same as if + you specified a value of 2. Bitfields: + 1 : Active (Already On) + 2 : Inactive (Ready to be used) + 3 : Removed (Unmanipulatable until restored) + +ex_skill_min This entity is removed when the map starts if the game's "skill" + variable is >= to this value. To specify a skill of 0, use + -1 instead. + +ex_skill_max This entity is removed when the map starts if the game's "skill" + variable is <= to this value. To specify a skill of 0, use + -1 instead. + +count Specified score given to the AP's team. + +increase_team1 Specified score given to team 1 +increase_team2 Specified score given to team 2 +increase_team3 Specified score given to team 3 +increase_team4 Specified score given to team 4 + +noise WAV file played when this Goal is activated. + +lives Added to APA's lives. + +health Added to APA's health. + +armortype The APA's armortype is set to the Goals "armortype". + Armortype Bitfields: + 0.3 : Green + 0.6 : Yellow + 0.8 : Red + +armorvalue APA's armorvalue is set to this value. (0-250) + +armorclass APA's armorclass is set to this type(s). + 1 : Shell Resistant + 2 : Nail Resistant + 4 : Explosion Resistant + 8 : Electricity Resistant + 16 : Fire Resistant + +frags Added to APA's frags. + +ammo_shells Added this number of shells to the APA's ammo supply. +ammo_nails Added this number of nails to the APA's ammo supply. +ammo_rockets Added this number of rockets to the APA's ammo supply. +ammo_cells Added this number of cells to the APA's ammo supply. +ammo_detpack Added this number of det packs to the APA's supply. +ammo_medikit Added this number of medikits to the APA's supply. + +no_grenades_1 Added to APA's number of type 1 TF grenades. +no_grenades_2 Added to APA's number of type 2 TF grenades. + +invincible_finished Number of seconds of invincibility APA gets. +invisible_finished Number of seconds of invisibility APA gets. +super_damage_finished Number of seconds of quad APA gets. +radsuit_finished Number of seconds of radsuit APA gets. + +activate_goal_no Activate this Goal. +inactivate_goal_no Inactivate this Goal. +remove_goal_no Remove this Goal. +restore_goal_no Restore this Goal. + +activate_group_no Activate all Goals in this GoalGroup. +inactivate_group_no Inactivate all Goals in this GoalGroup. +remove_group_no Remove all Goals in this GoalGroup. +restore_group_no Restore all Goals in this GoalGroup. + +remove_item_group Removes a GoalItems APA has from GoalGroup + +all_active If all Goals in this GoalGroup are ACTIVE, activate + the Goal in the "last_impulse" variable. + +last_impulse If all Goals in the "all_active" GoalGroup are ACTIVE, + activate this Goal. + +remove_spawnpoint Remove the spawnpoint with this "goal_no". + +restore_spawnpoint Restore the spawnpoint with this "goal_no", if it's + in the REMOVED state. + +remove_spawngroup Remove all spawnpoints with this "group_no". + +restore_spawngroup Restore all spawnpoints with this "group_no", if + they're in the REMOVED state. + + +============================================================================ +GOAL VARIABLES (info_tfgoal) +============================================================================ + +netname The name of the Goal. (A reference) + +goal_no Unique ID number of this goal. + +group_no ID Number of the goal group this goal belongs to. + +owned_by The Team that own this entity. + +goal_state The initial state of this entity. The default + (nothing specified) is Inactive the same as if + you specified a value of 2. Bitfields: + 1 : Active (Already On) + 2 : Inactive (Ready to be used) + 3 : Removed (Unmanipulatable until restored) + +mdl The mdl used by this Goal. (Unspecified = Invisible Goal.) + +skin The number of the skin to be used in the specified model. + +goal_activation Determines how the Goal is activated. Bitfields: + 1 : Activated when touched by a player. + 2 : Activated when touched by a detpack explosion. + 4 : Only activated if the AP fails Criteria. + 2048 : If this bit is set, the Goal drops to the + ground when it first spawns. + +team_no AP must be of this team to meet this Goal's criteria. + +items_allowed AP needs this GoalItem to meet this Goal's criteria. + +has_item_from_group AP must have GoalItem from group "group_no". + +playerclass AP must be this playerclass to meet this Goal's criteria. + You can not add these up, you can only specify one player + class. Bitfields: + 1 : Only allow a Scout to activate + 2 : Only allow a Sniper to activate + 3 : Only allow a Soldier to activate + 4 : Only allow a Demolition Man to activate + 5 : Only allow a Medic to activate + 6 : Only allow a Heavy Weapons Guy to activate + 7 : Only allow a Pyro to activate + 8 : Only allow a Spy to activate + 9 : Only allow an Engineer to activate + +spawnflags 1 : By default, when a GoalItem has a .mdl set, it will glow based on team_no. + This spawnflag will stop that glow. (TFGI_NOGLOW) + 2 : Allow this goal to work even in clan battle/quadmode prematch (TFGI_CB_IGNORE) +if_goal_is_active This Goal must be in ACTIVE state. +if_goal_is_inactive This Goal must be in INACTIVE state. +if_goal_is_removed This Goal must be in REMOVED state. +if_group_is_active All Goals in this Group must be in ACTIVE state. +if_group_is_inactive All Goals in this Group must be in INACTIVE state. +if_group_is_removed All Goals in this Group must be in REMOVED state. +if_item_has_moved This GoalItem must not have moved from it's origin, + and must not be being carried. +if_item_hasnt_moved This GoalItem must have moved from it's origin, + or be being carried. + +else_goal If the AP fails the Criteria of this entity, then + attempt to activate the Goal with this ID. + +goal_min The minimum bounding box size of a Goal can be set using + this. The default is: "goal_min" "-16 -16 -24" + +goal_max The maximum bounding box size of a Goal can be set using + this. The default is: "goal_max" "16 16 32" + +------------------------------------------- +When this Goal is successfully activated +up, the following variables may be executed +------------------------------------------- + +return_item_no Return this GoalItem, if it's not being carried. + +broadcast Message centerprinted to everyone except the AP. + e.g. "Clean-up on isle 4!" +broadcast_high Message printed high to everyone. + e.g. "Clean-up on isle 4!" + +message Message centerprinted to AP. + e.g. "You activated this goal!" + +team_broadcast Message centerprinted to players on the AP's team, + except the AP. e.g. "Your team has the enemy flag!" + +non_team_broadcast Message centerprinted to players not on the AP's team. + If there is a "owners_team_broadcast" specified, this + message isn't centerprinted to the owners of the entity. + e.g. "An enemy flag was taken!" + +owners_team_broadcast Gets centerprinted to all the members of the + team that own the goal/item. + e.g. "Your flag was taken!" + +netname_broadcast Broadcast to all players, prepended to the AP's name. + A line break should be used at the end. + e.g. " activated this goal!\n" + +netname_team_broadcast Broadcast to all players on the AP's team, except + the AP, prepended by the AP's name. A line break should + be used at the end. + e.g. " activated your team's goal!\n" + +netname_non_team_broadcast + Broadcast to all players on the AP's team, prepended by the + AP's name. If there is a "netname_owners_team_broadcast" + specified, this message isn't broadcast to the owners of + the entity. A line break should be used at the end. + e.g. " took an enemy flag!\n" + +netname_owners_team_broadcast + Broadcast to all members of the team that own this entity + (see "owned_by" above), prepended by the AP's name. + e.g. " took your flag!\n" + +deathtype Death message appended to the AP's name and broadcasted, if the + AP is killed by the Goal. A line break should be used at the end. + e.g. " was killed by this goal!\n" + +target Activate any Goal/Quake Trigger with this targetname + +killtarget Remove any Goal/Quake Trigger with this targetname + +ex_skill_min This entity is removed when the map starts if the game's "skill" + variable is >= to this value. To specify a skill of 0, use + -1 instead. + +ex_skill_max This entity is removed when the map starts if the game's "skill" + variable is <= to this value. To specify a skill of 0, use + -1 instead. + +goal_effects Determines what players are affected. If nothing is specified + the goal will not effect anybody. Bitfields: + 1 : AP is affected. + 2 : Everyone on the AP's team is affected. + 4 : Everyone not on the AP's team is affected. + 8 : Everyone except the AP is affected. + 16 : Radius effect (see "t_length") does not go + through walls. + 32 : If APA is not in the same environment as the + Goal, don't affect him. Environments are air, + water, slime, lava. e.g. If a Goal is above + some water, and does a radius effect with + "t_length", and a player in the water is within + the radius, he won't be affected if this bit + is set. + 64 : If this bit is set, then instead of just applying + this Goal's results to the group of players + specified by the other "goal_effects" variable, + this Goal checks it's criteria for each player + in the group and then applies it's results + invididually to any of them that pass. + +maxammo_shells All members of this team are affected. + +maxammo_nails All members not of this team are affected. + +t_length Everyone within this radius is affected. To specify whether + the radius should go through walls or not see bit 16 + in "goal_effects" + + +goal_result Determines what results are applied to APA. Bitfields: + 1 : Goal is removed after activation. + 2 : Goals activated by this one apply their + results to the AP. + 4 : Display scores and end the level. + 8 : Activated goals will not apply their results to + the APA + 16 : Disable/Stop the spy's undercover mask. + 32 : This forces APA to simply respawn. The player + doesn't die... just respawns. + +count Specified score given to the AP's team. + +increase_team1 Specified score given to team 1 +increase_team2 Specified score given to team 2 +increase_team3 Specified score given to team 3 +increase_team4 Specified score given to team 4 + +noise WAV file played when this Goal is activated. + +lives Added to APA's lives. + +health Added to APA's health. + +armortype The APA's armortype is set to the Goals "armortype". + Armortype Bitfields: + 0.3 : Green + 0.6 : Yellow + 0.8 : Red + +armorvalue APA's armorvalue is set to this value. (0-250) + +armorclass APA's armorclass is set to this type(s). + 1 : Shell Resistant + 2 : Nail Resistant + 4 : Explosion Resistant + 8 : Electricity Resistant + 16 : Fire Resistant + +frags Added to APA's frags. + +ammo_shells Added this number of shells to the APA's ammo supply. +ammo_nails Added this number of nails to the APA's ammo supply. +ammo_rockets Added this number of rockets to the APA's ammo supply. +ammo_cells Added this number of cells to the APA's ammo supply. +ammo_detpack Added this number of det packs to the APA's supply. +ammo_medikit Added this number of medikits to the APA's supply. + +no_grenades_1 Added to APA's number of type 1 TF grenades. +no_grenades_2 Added to APA's number of type 2 TF grenades. + +invincible_finished Number of seconds of invincibility APA gets. +invisible_finished Number of seconds of invisibility APA gets. +super_damage_finished Number of seconds of quad APA gets. +radsuit_finished Number of seconds of radsuit APA gets. + +items Goal gives this GoalItem to APA. +axhitme Goal removes this GoalItem from APA that has it. + +delay_time If the criteria is meant for activation, the goal waits + this amount of time before activating, in seconds. + +wait Goal stays ACTIVE for this amount of time, in seconds. + +activate_goal_no Activate this Goal. +inactivate_goal_no Inactivate this Goal. +remove_goal_no Remove this Goal. +restore_goal_no Restore this Goal. + +activate_group_no Activate all Goals in this GoalGroup. +inactivate_group_no Inactivate all Goals in this GoalGroup. +remove_group_no Remove all Goals in this GoalGroup. +restore_group_no Restore all Goals in this GoalGroup. + +remove_item_group Removes a GoalItems APA has from GoalGroup + +all_active If all Goals in this GoalGroup are ACTIVE, activate + the Goal in the "last_impulse" variable. + +last_impulse If all Goals in the "all_active" GoalGroup are ACTIVE, + activate this Goal. + +remove_spawnpoint Remove the spawnpoint with this "goal_no". + +restore_spawnpoint Restore the spawnpoint with this "goal_no", if it's + in the REMOVED state. + +remove_spawngroup Remove all spawnpoints with this "group_no". + +restore_spawngroup Restore all spawnpoints with this "group_no", if + they're in the REMOVED state. + +display_item_status1 Display this GoalItem Status when this goal is activated +display_item_status2 Display this GoalItem Status when this goal is activated +display_item_status3 Display this GoalItem Status when this goal is activated +display_item_status4 Display this GoalItem Status when this goal is activated + +team_str_home String displayed to the owners of an Item + in an Item Status if the Item is at it's origin. + If not the owner of the Item, non_team_str_home + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is in it's base" + +team_str_moved String displayed to the owners of an Item in an + Item Status if the Item is not at it's origin. + If not the owner of the Item, non_team_str_moved + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is lying around" + +team_str_carried String displayed to the owners of an Item in an Item + Status if the Item is being carried by a player. The + player's name is appended to the String. If not the + owner of the Item, non_team_str_carried will be used. + Refer to the "owned_by" variable for GoalItems. + e.g. "Your flag is being carried by" < players's_name> + +non_team_str_home String displayed to everyone except the owners of an + Item in an Item Status if the Item is at it's origin. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is in it's base" + +non_team_str_moved String displayed to everyone except the owners of an + Item in an Item Status if the Item is not at it's origin. + If they are owner of the Item, team_str_moved will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is lying around" + +non_team_str_carried String displayed to everyone except the owners of an + Item in an Item Status if the Item is being carried by a + player. The player's name is appended to the String. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is being carried by" < player's_name> + + +============================================================================ +GOALITEM VARIABLES (item_tfgoal) +============================================================================ + +netname The name of the GoalItem. (A reference) + +goal_no Unique ID number of this GoalItem. + +group_no ID Number of the goal group this GoalItem belongs to. + +owned_by The Team that own this entity. + +goal_state The initial state of this item. The default + (nothing specified) is Inactive the same as if + you specified a value of 2. Bitfields: + 1 : Active (Already On) + 2 : Inactive (Ready to be used) + 3 : Removed (Unmanipulatable until restored) + +mdl The mdl used by this GoalItem. (Unspecified = Invisible) + +skin The number of the skin to be used in the specified model. + +goal_activation Bitfields: + 1 : Carrying Player glows. + 2 : Carrying Player moves at half speed. + 4 : Item is dropped when a player with it dies. + 8 : Item is returned when dropped. + 16 : Item is returned when removed from players by a Goal + 32 : Item is returned due to "pausetime" (see below) + 64 : Only activated (picked-up) if AP fails Criteria. + 128 : Enable "pausetime" removing. + 256 : Players keep this item when they die. + 512 : If this Item isn't being carried, it glows dimly. + 1024 : Don't remove the results of this Item when it's + removed from a player. + 2048 : If this bit is set, the GoalItem drops to the + ground when it first spawns. + 8192 : If this bit is set, the GoalItem is Solid while not + being carried by a player. This means it blocks + bullets, grenades, and those that do not pass it's + criteria. Others will simply pick it up. + +playerclass AP must be this playerclass to meet this GoalItem's criteria. + You can not add these up, you can only specify one player + class. Bitfields: + 1 : Only allow a Scout to activate + 2 : Only allow a Sniper to activate + 3 : Only allow a Soldier to activate + 4 : Only allow a Demolition Man to activate + 5 : Only allow a Medic to activate + 6 : Only allow a Heavy Weapons Guy to activate + 7 : Only allow a Pyro to activate + 8 : Only allow a Spy to activate + 9 : Only allow an Engineer to activate + +if_goal_is_active This Goal must be in ACTIVE state. +if_goal_is_inactive This Goal must be in INACTIVE state. +if_goal_is_removed This Goal must be in REMOVED state. +if_group_is_active All Goals in this Group must be in ACTIVE state. +if_group_is_inactive All Goals in this Group must be in INACTIVE state. +if_group_is_removed All Goals in this Group must be in REMOVED state. +if_item_has_moved This GoalItem must not have moved from it's origin, + and must not be being carried. +if_item_hasnt_moved This GoalItem must have moved from it's origin, + or be being carried. + +else_goal If the AP fails the Criteria of this entity, then + attempt to activate the Goal with this ID. + +has_item_from_group AP must have GoalItem from group "group_no" + +goal_min The minimum bounding box size of a GoalItem can be set + using this. The default is: "goal_min" "-16 -16 -24" + +goal_max The maximum bounding box size of a GoalItem can be set + using this. The default is: "goal_max" "16 16 32" + +------------------------------------------- +When this GoalItem is successfully picked +up, the following variables may be executed +------------------------------------------- + +return_item_no Return this GoalItem, if it's not being carried. + +target Activate any Goal/Quake Trigger with this targetname + +killtarget Remove any Goal/Quake Trigger with this targetname + +ex_skill_min This entity is removed when the map starts if the game's "skill" + variable is >= to this value. To specify a skill of 0, use + -1 instead. + +ex_skill_max This entity is removed when the map starts if the game's "skill" + variable is <= to this value. To specify a skill of 0, use + -1 instead. + +goal_effects Determines what players are affected. If nothing is specified + the goal will not effect anybody. Bitfields: + 1 : AP is affected. + 2 : Everyone on the AP's team is affected. + 4 : Everyone not on the AP's team is affected. + 8 : Everyone except the AP is affected. + 16 : Radius effect (see "t_length") does not go + through walls. + 32 : If APA is not in the same environment as the + Goal, don't affect him. Environments are air, + water, slime, lava. e.g. If a Goal is above + some water, and does a radius effect with + "t_length", and a player in the water is within + the radius, he won't be affected if this bit + is set. + 64 : If this bit is set, then instead of just applying + this Goal's results to the group of players + specified by the other "goal_effects" variable, + this Goal checks it's criteria for each player + in the group and then applies it's results + invididually to any of them that pass. + +spawnflags 1 : By default, when a GoalItem has a .mdl set, it will glow based on team_no. + This spawnflag will stop that glow. + +maxammo_shells All members of this team are affected. + +maxammo_nails All members not of this team are affected. + +t_length Everyone within this radius is affected. To specify whether + the radius should go through walls or not see bit 16 + in "goal_effects" + +impulse When the GoalItem is Returned, activate this Goal. + +pausetime Item is removed if not touched for this time after being + dropped by a dying player. Also, see bit 32 in + "goal_activation" above. N.B. This will not be used if + bit 128 in "goal_activation" is not enabled. + + +maxammo_shells All members of this team are affected. + +maxammo_nails All members not of this team are affected. + +goal_result Determines what results are applied to APA. Bitfields: + 1 : Goal is removed after activation. + 2 : Goals activated by this one apply their + results to the AP. + 4 : Display scores and end the level. + 8 : Activated goals will not apply their results to + the APA + 16 : Disable/Stop the spy's undercover mask. + 32 : This forces APA to simply respawn. The player + doesn't die... just respawns. + 4096 : If this bit is set, any player carrying this + item can drop it using the "dropitems" command. + +count Specified score given to the AP's team. + +increase_team1 Specified score given to team 1 +increase_team2 Specified score given to team 2 +increase_team3 Specified score given to team 3 +increase_team4 Specified score given to team 4 + +noise WAV file played when this Goal is activated. + +lives Added to APA's lives. + +health Added to APA's health. + +armortype The APA's armortype is set to the Goals "armortype". + Armortype Bitfields: + 0.3 : Green + 0.6 : Yellow + 0.8 : Red + +armorvalue APA's armorvalue is set to this value. (0-250) + +armorclass APA's armorclass is set to this type(s). + 1 : Shell Resistant + 2 : Nail Resistant + 4 : Explosion Resistant + 8 : Electricity Resistant + 16 : Fire Resistant + +frags Added to APA's frags. + +ammo_shells Added this number of shells to the APA's ammo supply. +ammo_nails Added this number of nails to the APA's ammo supply. +ammo_rockets Added this number of rockets to the APA's ammo supply. +ammo_cells Added this number of cells to the APA's ammo supply. +ammo_detpack Added this number of det packs to the APA's supply. +ammo_medikit Added this number of medikits to the APA's supply. + +no_grenades_1 Added to APA's number of type 1 TF grenades. +no_grenades_2 Added to APA's number of type 2 TF grenades. + +invincible_finished Number of seconds of invincibility APA gets. +invisible_finished Number of seconds of invisibility APA gets. +super_damage_finished Number of seconds of quad APA gets. +radsuit_finished Number of seconds of radsuit APA gets. + +delay_time If the criteria is meant for activation, the goal waits + this amount of time before activating, in seconds. + +items Goal gives this GoalItem to APA. +axhitme Goal removes this GoalItem from APA that has it. + +distance If all GoalItems in this GoalItem Group are being carried + by players, activate the Goal in the "pain_finished" variable. + +pain_finished If all GoalItems in the GoalItem Group specified in the + "distance" variable are being carried by players, activate + this Goal. + +speed If all GoalItems in this GoalItem Group are being + carried by one player, activate the Goal in the + "attack_finished" variable. + +attack_finished If all GoalItems in the GoalItem Group specified in the "speed" + variable are being carried by one player, activate this Goal. + +noise3 String centerprinted to the all the members of the team + that own this GoalItem whenever it returns. + e.g. "Your flag has been returned to base\n" + +noise4 String centerprinted to the everyone except all the members + of the team that own this GoalItem whenever it returns. + e.g. "An enemy flag has been returned to base\n" + +team_drop Centerprinted to all members of the team that own this Item, + whenever this Item is dropped by a player. + e.g. "Your flag is lying around!\n" + +non_team_drop Centerprinted to all members not in the team that own this + Item, whenever this Item is dropped by a player. + e.g. "An enemy flag is lying around!\n" + +netname_team_drop Broadcast to all members of the team that own this item, + prepended by the name of the player who dropped the item. + A line break should be used at the end. + e.g. " lost your flag!\n" + +netname_non_team_drop Broadcast to all members not in the team that own this item, + prepended by the name of the player who dropped the item. + A line break should be used at the end. + e.g. " lost the enemy flag!\n" + +broadcast Message centerprinted to everyone except the AP. + e.g. "Clean-up on isle 4!" +broadcast_high Message printed high to everyone. + e.g. "Clean-up on isle 4!" + +message Message centerprinted to AP. + e.g. "You activated this goal!" + +team_broadcast Message centerprinted to players on the AP's team, + except the AP. e.g. "Your team has the enemy flag!" + +non_team_broadcast Message centerprinted to players not on the AP's team. + If there is a "owners_team_broadcast" specified, this + message isn't centerprinted to the owners of the entity. + e.g. "An enemy flag was taken!" + +owners_team_broadcast Gets centerprinted to all the members of the + team that own the goal/item. + e.g. "Your flag was taken!" + +netname_broadcast Broadcast to all players, prepended to the AP's name. + A line break should be used at the end. + e.g. " activated this goal!\n" + +netname_team_broadcast Broadcast to all players on the AP's team, except + the AP, prepended by the AP's name. A line break should + be used at the end. + e.g. " activated your team's goal!\n" + +netname_non_team_broadcast + Broadcast to all players on the AP's team, prepended by the + AP's name. If there is a "netname_owners_team_broadcast" + specified, this message isn't broadcast to the owners of + the entity. A line break should be used at the end. + e.g. " took an enemy flag!\n" + +netname_owners_team_broadcast + Broadcast to all members of the team that own this entity + (see "owned_by" above), prepended by the AP's name. + e.g. " took your flag!\n" + +deathtype Death message appended to the AP's name and broadcasted, if the + AP is killed by the Goal. A line break should be used at the end. + e.g. " was killed by this goal!\n" + +activate_goal_no Activate this Goal. +inactivate_goal_no Inactivate this Goal. +remove_goal_no Remove this Goal. +restore_goal_no Restore this Goal. + +activate_group_no Activate all Goals in this GoalGroup. +inactivate_group_no Inactivate all Goals in this GoalGroup. +remove_group_no Remove all Goals in this GoalGroup. +restore_group_no Restore all Goals in this GoalGroup. + +remove_item_group Removes a GoalItems APA has from GoalGroup + +all_active If all Goals in this GoalGroup are ACTIVE, activate + the Goal in the "last_impulse" variable. + +last_impulse If all Goals in the "all_active" GoalGroup are ACTIVE, + activate this Goal. + +remove_spawnpoint Remove the spawnpoint with this "goal_no". + +restore_spawnpoint Restore the spawnpoint with this "goal_no", if it's + in the REMOVED state. + +remove_spawngroup Remove all spawnpoints with this "group_no". + +restore_spawngroup Restore all spawnpoints with this "group_no", if + they're in the REMOVED state. + +display_item_status1 Display this GoalItem Status when this goal is activated +display_item_status2 Display this GoalItem Status when this goal is activated +display_item_status3 Display this GoalItem Status when this goal is activated +display_item_status4 Display this GoalItem Status when this goal is activated + +team_str_home String displayed to the owners of an Item + in an Item Status if the Item is at it's origin. + If not the owner of the Item, non_team_str_home + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is in it's base" + +team_str_moved String displayed to the owners of an Item in an + Item Status if the Item is not at it's origin. + If not the owner of the Item, non_team_str_moved + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is lying around" + +team_str_carried String displayed to the owners of an Item in an Item + Status if the Item is being carried by a player. The + player's name is appended to the String. If not the + owner of the Item, non_team_str_carried will be used. + Refer to the "owned_by" variable for GoalItems. + e.g. "Your flag is being carried by" < players's_name> + +non_team_str_home String displayed to everyone except the owners of an + Item in an Item Status if the Item is at it's origin. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is in it's base" + +non_team_str_moved String displayed to everyone except the owners of an + Item in an Item Status if the Item is not at it's origin. + If they are owner of the Item, team_str_moved will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is lying around" + +non_team_str_carried String displayed to everyone except the owners of an + Item in an Item Status if the Item is being carried by a + player. The player's name is appended to the String. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is being carried by" < player's_name> + + +============================================================================ +TIMER GOAL VARIABLES (info_tfgoal_timer) +============================================================================ + +goal_effects Determines what players are affected. If nothing is specified + the goal will not effect anybody. Bitfields: + 1 : Illegal for Timer Goals + 2 : Illegal for Timer Goals + 4 : Illegal for Timer Goals + 8 : Illegal for Timer Goals + 16 : Radius effect (see "t_length") does not go + through walls. + 32 : If APA is not in the same environment as the + Goal, don't affect him. Environments are air, + water, slime, lava. e.g. If a Goal is above + some water, and does a radius effect with + "t_length", and a player in the water is within + the radius, he won't be affected if this bit + is set. + 64 : If this bit is set, then instead of just applying + this Goal's results to the group of players + specified by the other "goal_effects" variable, + this Goal checks it's criteria for each player + in the group and then applies it's results + invididually to any of them that pass. + +search_time Period between Timer activations. + +netname The name of the Timer Goal. (A reference) + +goal_no Unique ID number of this Timer Goal. + +group_no ID Number of the goal group this Timer Goal belongs to. + +goal_state The initial state of this entity. The default + (nothing specified) is Inactive the same as if + you specified a value of 2. Bitfields: + 1 : Active (Already On) + 2 : Inactive (Ready to be used) + 3 : Removed (Unmanipulatable until restored) + +items_allowed AP needs this GoalItem to be effected by the results. + +playerclass AP must be this playerclass to meet this Goal's criteria. + You can not add these up, you can only specify one player + class. Bitfields: + 1 : Only allow a Scout to activate + 2 : Only allow a Sniper to activate + 3 : Only allow a Soldier to activate + 4 : Only allow a Demolition Man to activate + 5 : Only allow a Medic to activate + 6 : Only allow a Heavy Weapons Guy to activate + 7 : Only allow a Pyro to activate + 8 : Only allow a Spy to activate + 9 : Only allow an Engineer to activate + +if_goal_is_active This Goal must be in ACTIVE state. +if_goal_is_inactive This Goal must be in INACTIVE state. +if_goal_is_removed This Goal must be in REMOVED state. +if_group_is_active All Goals in this Group must be in ACTIVE state. +if_group_is_inactive All Goals in this Group must be in INACTIVE state. +if_group_is_removed All Goals in this Group must be in REMOVED state. + +if_item_has_moved This GoalItem must not have moved from it's origin, + and must not be being carried. + +if_item_hasnt_moved This GoalItem must have moved from it's origin, + or be being carried. + +has_item_from_group APA will be effected if they have GoalItem + from group "group_no" + +maxammo_shells All members of this team are checked for meeting the criteria. + +maxammo_nails All members not on this team are checked for meeting the + criteria. + +t_length Everyone within this radius is affected. To specify whether + the radius should go through walls or not see bit 16 + in "goal_effects" + +------------------------------------------- +When this Goal is successfully activated +up, the following variables may be executed +------------------------------------------- + +return_item_no Return this GoalItem, if it's not being carried. + +deathtype Death message appended to the AP's name and broadcasted, if the + AP is killed by the Goal. A line break should be used at the end. + e.g. " was killed by this goal!\n" + +target Activate any Goal/Quake Trigger with this targetname + +killtarget Remove any Goal/Quake Trigger with this targetname + +ex_skill_min This entity is removed when the map starts if the game's "skill" + variable is >= to this value. To specify a skill of 0, use + -1 instead. + +ex_skill_max This entity is removed when the map starts if the game's "skill" + variable is <= to this value. To specify a skill of 0, use + -1 instead. + +goal_result Determines what results are applied to APA. Bitfields: + 1 : Goal is removed after activation. + 2 : Goals activated by this one apply their + results to APA. + 4 : Display scores and end the level. + 8 : Activated goals will not apply their results to + the APA + 16 : Disable/Stop the spy's undercover mask. + 32 : This forces APA to simply respawn. The player + doesn't die... just respawns. + 64 : If this bit is set, the results are applied to all the + players that do NOT fit the criteria. + +increase_team1 Specified score given to team 1 +increase_team2 Specified score given to team 2 +increase_team3 Specified score given to team 3 +increase_team4 Specified score given to team 4 + +noise WAV file played when this Goal is activated. + +lives Added to APA's lives. + +health Added to APA's health. + +armortype The APA's armortype is set to the Goals "armortype". + Armortype Bitfields: + 0.3 : Green + 0.6 : Yellow + 0.8 : Red + +armorvalue APA's armorvalue is set to this value. (0-250) + +armorclass APA's armorclass is set to this type(s). + 1 : Shell Resistant + 2 : Nail Resistant + 4 : Explosion Resistant + 8 : Electricity Resistant + 16 : Fire Resistant + +frags Added to APA's frags. + +ammo_shells Added this number of shells to the APA's ammo supply. +ammo_nails Added this number of nails to the APA's ammo supply. +ammo_rockets Added this number of rockets to the APA's ammo supply. +ammo_cells Added this number of cells to the APA's ammo supply. +ammo_detpack Added this number of det packs to the APA's supply. +ammo_medikit Added this number of medikits to the APA's supply. + +no_grenades_1 Added to APA's number of type 1 TF grenades. +no_grenades_2 Added to APA's number of type 2 TF grenades. + +invincible_finished Number of seconds of invincibility APA gets. +invisible_finished Number of seconds of invisibility APA gets. +super_damage_finished Number of seconds of quad APA gets. +radsuit_finished Number of seconds of radsuit APA gets. + +items Goal gives this GoalItem to APA. +axhitme Goal removes this GoalItem from APA that has it. + +delay_time If the criteria is meant for activation, the goal waits + this amount of time before activating, in seconds. + +wait Goal stays ACTIVE for this amount of time, in seconds. + +activate_goal_no Activate this Goal. +inactivate_goal_no Inactivate this Goal. +remove_goal_no Remove this Goal. +restore_goal_no Restore this Goal. + +activate_group_no Activate all Goals in this GoalGroup. +inactivate_group_no Inactivate all Goals in this GoalGroup. +remove_group_no Remove all Goals in this GoalGroup. +restore_group_no Restore all Goals in this GoalGroup. + +all_active If all Goals in this GoalGroup are ACTIVE, activate + the Goal in the "last_impulse" variable. + +last_impulse If all Goals in the "all_active" GoalGroup are ACTIVE, + activate this Goal. + +remove_item_group Removes a GoalItems APA has from GoalGroup + +remove_spawnpoint Remove the spawnpoint with this "goal_no". + +restore_spawnpoint Restore the spawnpoint with this "goal_no", if it's + in the REMOVED state. + +remove_spawngroup Remove all spawnpoints with this "group_no". + +restore_spawngroup Restore all spawnpoints with this "group_no", if + they're in the REMOVED state. + +display_item_status1 Display this GoalItem Status when this goal is activated +display_item_status2 Display this GoalItem Status when this goal is activated +display_item_status3 Display this GoalItem Status when this goal is activated +display_item_status4 Display this GoalItem Status when this goal is activated + +team_str_home String displayed to the owners of an Item + in an Item Status if the Item is at it's origin. + If not the owner of the Item, non_team_str_home + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is in it's base" + +team_str_moved String displayed to the owners of an Item in an + Item Status if the Item is not at it's origin. + If not the owner of the Item, non_team_str_moved + will be used. Refer to the "owned_by" variable + for GoalItems. + e.g. "Your flag is lying around" + +team_str_carried String displayed to the owners of an Item in an Item + Status if the Item is being carried by a player. The + player's name is appended to the String. If not the + owner of the Item, non_team_str_carried will be used. + Refer to the "owned_by" variable for GoalItems. + e.g. "Your flag is being carried by" < players's_name> + +non_team_str_home String displayed to everyone except the owners of an + Item in an Item Status if the Item is at it's origin. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is in it's base" + +non_team_str_moved String displayed to everyone except the owners of an + Item in an Item Status if the Item is not at it's origin. + If they are owner of the Item, team_str_moved will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is lying around" + +non_team_str_carried String displayed to everyone except the owners of an + Item in an Item Status if the Item is being carried by a + player. The player's name is appended to the String. + If they are owner of the Item, team_str_home will be + used. Refer to the "owned_by" variable for GoalItems. + e.g. "The enemy flag is being carried by" < player's_name> + +============================================================================ +OTHER QUAKE ENTITY VARIABLES +============================================================================ + + +func_button / func_door + This works the same as info_tfgoal except for obvious + variables such as "model". Additional Bitfields: + goal_activation 8 : only activated when hit by an engineer's spanner + forcecloseonblock 1 : when blocked by a player or item, keep attempting to close + +trigger_push + This can now be paired with a targetname of a point entity called target_position - this will create Q3 style jumppads + spawnflags: 2 : include TFItems (So flags can be affected) + 4 : exclude "other" (So only TFItems are affected) + 8 : "no noise" make sure no default noise is added + +func_wall + spawnflags: 1 : make wall non solid + 2 : WALL_HIDE_ON_USE +trigger_jumppad + Rename of trigger_push, can be used to allow for "Fortress One" only trigger_push entities that won't be misinterpreted by other fortress mods + + +OTHER ENTITIES Most Quake entities work the same as info_tfgoal except + for obvious variables such as "model", "goal_no", and + "goal_activation" fields. So you can limit func_doors to + certain team_nos and so on... + + +============================================================================ +VARIABLE ABBREVIATION LIST +============================================================================ + +Abbreviations can be used in replace of certain full named variables to save +entity space due to many Quake limits. Here is a complete list of all the +variables: + + + Classnames + "i_p_t" for "info_player_teamspawn" + "i_t_g" for "info_tfgoal" + "i_t_t" for "info_tfgoal_timer" + + Floats + "g_a" for "goal_activation" + "g_e" for "goal_effects" + + "h_i_g" for "has_item_from_group" + "r_i_g" for "remove_item_group" + + "a_s" for "ammo_shells" + "a_n" for "ammo_nails" + "a_r" for "ammo_rockets" + "a_c" for "ammo_cells" + + "rv_s_h" for "remove_spawngroup" + "rs_s_h" for "restore_spawngroup" + "rv_gr" for "remove_group_no" + "rs_gr" for "restore_group_no" + "rv_g" for "remove_goal_no" + "rs_g" for "restore_goal_no" + + Strings + "t_s_h" for "team_str_home" + "t_s_m" for "team_str_moved" + "t_s_c" for "team_str_carried" + "n_s_h" for "non_team_str_home" + "n_s_m" for "non_team_str_moved" + "n_s_c" for "non_team_str_carried" + + "b_b" for "broadcast" + "b_t" for "team_broadcast" + "b_n" for "non_team_broadcast" + "b_o" for "owners_team_broadcast" + "n_b" for "netname_broadcast" + "n_t" for "netname_team_broadcast" + "n_n" for "netname_non_team_broadcast" + "n_o" for "netname_owners_team_broadcast" + + "d_t" for "team_drop" + "d_n" for "non_team_drop" + "d_n_t" for "netname_team_drop" + "d_n_n" for "netname_non_team_drop" + +EOF + diff --git a/docs/tfortmap.txt b/docs/tfortmap.txt new file mode 100644 index 000000000..0e313ba28 --- /dev/null +++ b/docs/tfortmap.txt @@ -0,0 +1,1648 @@ +*==============================================================* +* TeamFortress v2.8 tfortmap.txt * +*==============================================================* +Introduction +------------ + +NOTE: If you're interested in seeing what was added since version 2.1, + I suggest you check out the "Summary of Changes" at the bottom + of the file. + +If you haven't read the readme.txt by now, please do. + +This file contains details about making Maps specifically +for TeamFortress. You don't need to know these details to +play the game, even on a TeamFortress Map. + +Beware! This is not for the faint-hearted! + +There are few maps on the WWW page that use these specs to +do interesting things. Don't forget you can simply view the +.bsp file to look at the entities inside the map, so if +you want to see some examples of Goals, etc, I suggest you +look at those maps. + +Oh, and some variables we use for the Goal entities are variables +used for completely different things for other entities. We did +this to preserve variable space. + +Also, this document has been re-hashed so many times that it's +almost certain that I missed updating a bitfield number or +something. If you see anything that looks wierd, or is +contradicted somewhere else in the document, please tell me. + +Finally, The TeamFortress Entity Editor is available on +the WWW page. It's a Windows program that creates goals, items, +etc for you. I suggest you read these specs, and skip over +the Technical Info. Then, just use the Entity Editor to make +your goals. +Reading the Technical Info is good, since it will give you a +better understanding of Goal entities and their uses, but don't +bother trying to remember it all. + +Robin. + +---------------------------------------------------------------------------- +TeamFortressMap Specification +---------------------------------------------------------------------------- +Apart from being a normal map, TeamFortress Maps +have the following changes: + + Auto Detection + Team SpawnPoints + Armor + + Goals + GoalItems + Timer Goals + +If you are only interested in the changes made to the map specs +since the last version, to update your maps etc, then check +out the section entitled "Summary of Changes" + +---------------------------------------------------------------------------- +Notes about using existing Entities +---------------------------------------------------------------------------- +In TeamFortress, we have an Observer mode, where players who haven't +chosen a class, or have run out of lives, are able to cycle through +all the Intermission points to watch the action. +Because of this, make sure you have a few Intermission points at +interesting viewing locations around the map. If you don't have some +of these, Observers won't have much fun. + +---------------------------------------------------------------------------- +Auto Detection +---------------------------------------------------------------------------- +TeamFortress maps will be automatically detected by the TeamFortress patch +if you put an entity in the map with a classname of "info_tfdetect". +This makes TeamFortress automatically turn on the FORTRESSMAP toggleflag, +and turn on teamplay. + +Also, once the TeamFortress map has been detected, the patch will look +for any spawnpoints dedicated to teams (see below). +If it finds any, it will look for the highest team number that is used. +Once found, it will limit anybody attempting to join a team to that +number. +At the moment, Maps are limited to a maximum of 4 teams. +E.g. If the highest team number used by a Team Spawnpoint is 3, then + the patch will only allow players to join teams 1, 2, and 3. + + +The detection entity can also do the following: + - specify a version string in the "broadcast" variable. + This will be compared with the version string for the TeamFortress + patch. If the don't match, a warning will be displayed to the + player that their patch and the map are incompatible. + The version string is in the format as follows: + TeamFortress v2.1 + The string is case sensitive, so make sure you've got it right. + Don't put a \n on the end of it. + + - Set the state of the toggleflags when the map starts up. The + value of the toggleflags should be in the "impulse" variable. + The bits for the toggleflags are: + + Bit 1 (1) : Off - ClasSkin , On - Multiskin + Bit 2 (2) : Off - ClassPersistence Off , On - ClassPersistence On + Bit 3 (4) : Off - CheatChecking Off , On - CheatChecking On + Bit 4 (8) : Off - FortressMap Off , On - FortressMap On + Bit 5 (16) : Off - RespawnDelay Off , On - RespawnDelay (See below) + Bit 6 (32) : Off - RespawnDelay Off , On - RespawnDelay (See below) + Bit 7 (64) : Off - AutoTeam Off , On - AutoTeam On + Bit 8 (128) : Off - Individual Frags , On - Frags = TeamScore + + N.B. FortressMap will be set On automatically by the the + Detection entity anyway, so just ignore that Bit. + + N.B. The RespawnDelay settings takes 2 bits. The value of both of + them determines the level of respawn delay, as follows: + Bit 5 Bit 6 Result + Off Off No Respawn delays + On Off 5 Second respawn delay + Off On 10 Second respawn delay + On On 20 Second respawn delay + + - Specify a string which is then "localcmd"ed. This allows you + to automatically set gravity, friction, etc. + The string should be stored in the "message" variable. + + - Put a limit on the number of lives each player has. When a player + runs out of lives, they are stuck in observer mode for the rest + of the level. + Lives for each player depend on the setting for the team they + belong to. The number of lives players of each team be should + be specified in the following variables: + Team 1 : "ammo_shells" + Team 2 : "ammo_nails" + Team 3 : "ammo_rockets" + Team 4 : "ammo_cells" + If the value of any team is 0, then the players of that team get + infinite lives. + + - Specify any playerclass that is _not_ allowed on this map + for each particular team. + The bits of the four variables are used for this. + The variables are as follows: + Team 1 : "maxammo_shells" + Team 2 : "maxammo_nails" + Team 3 : "maxammo_rockets" + Team 4 : "maxammo_cells" + Also, the "playerclass" variable can be used to restrict classes + for all teams. + The bits for all the variables are as follows: + Bit 1 (1) : No Scout + Bit 2 (2) : No Sniper + Bit 3 (4) : No Soldier + Bit 4 (8) : No Demolitions Man + Bit 5 (16) : No Combat Medic + Bit 6 (32) : No Heavy Weapons Guy + Bit 7 (64) : No Pyro + Bit 8 (128) : No Random PlayerClass + Bit 9 (256) : No Spy + Bit 10(512) : No Engineer + E.g. If the "maxammo_nails" variable is set to 3, then players + in Team 2 will be unable to play Scouts or Snipers. + + Finally, if you want the Team to be a special team for a fancy + map, such as the President in President-Quake, then you can + restrict the team to only play Civilian Class. Do this by setting + the team's variable to "-1". + +Notes: + When using the "message" variable to set do localcmd's, you + can issue more than one command by seperating them with \n + Make sure you end it with a \n too. + E.g. The following changes the gravity and the friction. + "message" "sv_gravity 200\nsv_friction .5\n" + +---------------------------------------------------------------------------- +Team Spawnpoints +---------------------------------------------------------------------------- +There is now a new entity for Team Spawnpoints. Team Spawnpoints are used +to make players only spawn in points designated for their team. Use it +to make players spawn inside their own base/fortress. +The entity is called "info_player_teamspawn", and the "team_no" variable +sets the team_no that owns this spawnpoint. + +Also, spawnpoints allow you to give any player the spawns from this +point a GoalItem. This is done using the following variables: + "items" : Contains the ID of the GoalItem to give + "message" : Message displayed to anyone who spawns on this + point. + +The "goal_activation" of the Spawnpoint determines how the above works: + + 1 : If set, then every player who spawns on this point + will be given a copy of the GoalItem. Otherwise, only + the first player to spawn will be given it. + 2 : If set, then every player who spawns on this point + will have the "message" displayed to them. Otherwise, only + the first player to spawn will be see it. + +Also, the "goal_effects" variable of the spawnpoint sets the re-use +behaviour of the spawnpoint. If it's 1, then the spawnpoint removes +itself after someone has spawned on it. + + +---------------------------------------------------------------------------- +Armor +---------------------------------------------------------------------------- +TeamFortress supports not only armor levels and absorption percentages the +same way Quake does: it also supports armor classes. E.g. Armor can be designed +to prevent particular attack types. +The Armor Types are as follows: + + Kevlar : Better protection against Bullets + Wooden ;) : Better protection against Nails + Blast : Better protection against Explosions + Shock : Better protection against Electricity + Ceramic : Better protection against Fire + +To use these armor types, just create an armor entity on the map as usual, +and set it's "armorclass" variable. The "armorclass" is used as bitfields, +as follows: + Bit 1 (1) : Kevlar + Bit 2 (2) : Wooden + Bit 3 (4) : Blast + Bit 4 (8) : Shock + Bit 5 (16) : Ceramic + +Any combination of armor is legal, but try not to have armor which +is too good. + +---------------------------------------------------------------------------- +Goal Summary / Terminology +---------------------------------------------------------------------------- +Goals are the really fun things you can do with TeamFortressMaps. +A goal is simply an entity you place in a map. It is then handled +in the game based on the flags you give it. They're essentially +more complex triggers. I've kept all the code separate from +the triggers tho, to allow us to toggle the use of them ingame. +The flags and variables set for a Goal allow us to create powerful +maps without changing the QuakeC code at all. +----------- +Goals +----------- +Goals are always in one of the following states: + +ACTIVE : The goal has been activated in some way. + It cannot be touched by a player. + Active goals return to the INACTIVE state based on their + result criteria, or when they're forced into INACTIVE state + by another goal. + +INACTIVE: The goal is not active. + It can be activated by players, and if the players meet the + activation criteria, the goal moves to the ACTIVE state. + +REMOVED : The goal is not active. + It cannot be touched by players. + Removed goals can still be activated by other goals, in which + case they move to the ACTIVE state. + Removed goals can be restored to the INACTIVE by being restored + by another goal. + +Terminology +----------- + +activated : Goal moves to the ACTIVE state +inactivate : If the Goal is in ACTIVE state, it moves to the INACTIVE state +removed : Goal moves to the REMOVED state +restored : If the Goal is in REMOVED state, it moves to the INACTIVE state + +returned : GoalItems can be returned to their starting point, based on + a number of settings. + +Goal Group : A group of goals that can be altered as one. + +Activating Player(AP): The player who either touched the Goal, or set the + detpack which touched the Goal. In the case of GoalItems, + the player that's attempting to pick up the GoalItem. + +Actions +------- +When goals go to ACTIVE state, they can perform various actions. +These include: + + - Increase the score of the team the AP belongs to. + - Display a message to all players + - Display a message just to the AP + + - Alter the details of one or more players, as follows: + - Give the player extra lives (if applicable) + - Alter a player's health/ammo/armor/powerups + - Give the player(s) a GoalItem + - Remove a GoalItem from the player(s) + + The player can be the AP, all the members of a Team the AP is/isn't in, + all the players on the map, everyone but the AP, all the players + within a specified distance from the goal, or everyone in/not in one + particular team. + + - Activate another goal, applying the bonuses of the goal to the AP + - Activate another goal, without applying the bonuses of the goal to the AP + - Inactivate another goal + - Remove another goal + - Restore another goal + + - Activate a goal if all the goals in a goal group are now ACTIVE + - Activate a group of goals + - Inactivate a group of goals + - Remove a group of goals + - Restore a group of goals + + - Display team's scores and End the Level + +Activation +---------- +Goals can be activated in one of the following ways: + + - Touched by a player + - Touched by a detpack explosion + - Activated by another Goal + - Activated by a Goal Group command from another Goal + - Activated by a Quake Trigger + +Additionally, the AP's details can be checked. +This allows you to have goals that are only activated if: + + - The AP is of a particular class + - The AP has a particular GoalItem + - The AP is of a particular team + +Goals can also be activated only if the AP details _don't_ +meet the criteria specified. + +Inactivation +------------ +Goals can be inactivated in one of the following ways: + + - Inactivated after being ACTIVE for a specified time + - Inactivated by another Goal + - Inactivated by a Goal Group command from another Goal + +Also, some goals are marked as Multiple Goals. They return to INACTIVE +state immediately after they become ACTIVE. + +Removal +------- +Goals can be removed in one of the following ways: + + - Removed after being activated + - Removed by another Goal + - Removed by a Goal Group command from another Goal + - Removed by a Quake Trigger + +Restoration +----------- +Goals can be restored in one of the following ways: + + - Restored by a GoalItem when it Returns (See below). + - Restored by another Goal + - Restored by a Goal Group command from another Goal + +---------------------------------------------------------------------------- +GoalItems +---------------------------------------------------------------------------- +GoalItems are items that players can carry around. +GoalItems can be placed directly into a map, where a player +can collect them as usual. +Also, on activation, Goals can give/remove GoalItems from the AP. +The details of players attempting to collect the GoalItems can +also be checked. +This allows you to have GoalItems that can only be picked up if: + + - The Player is of a particular class + - The Player has a particular GoalItem + - The Player is of a particular team + +GoalItems can also be set to only be picked up by players who +meet the AP criteria. + +GoalItems can be returned to their starting position in any of the +following circumstances: + + - When it's dropped by a dying player + - When it's removed from an AP by a Goal activation + - When it's untouched for a specified time + +GoalItems can affect players in the same way Goals can, except that +any modifications done by the GoalItem are permanent, until the +GoalItem is dropped. +E.g. if a GoalItem grants invisibility to any players, they stay +invisible until the GoalItem is dropped/removed. + +Also, when they're picked up by a player, GoalItems can perform the +following actions on Goals: + + - Activate another goal, applying the bonuses of the goal to the AP + - Activate another goal, without applying the bonuses of the goal to the AP + - Inactivate another goal + - Remove another goal + - Restore another goal + + - Activate a group of goals + - Inactivate a group of goals + - Remove a group of goals + - Restore a group of goals + +GoalItems can also belong to Groups. GoalItems groups are kept +separate from Goal Groups. Operations on either of them do not +affect the other type, even if the Group No is the same. +Using GoalItem groups, GoalItems can: + + - Activate a goal if all the Items in a GoalItem group are being + carried by players. + - Activate a goal if all the Items in a GoalItem group are being + carried by one particular player. + +---------------------------------------------------------------------------- +Timer Goals +---------------------------------------------------------------------------- +There is also a special type of goal called a Timer Goal. TG's +have a Timer Delay. TG's start INACTIVE, and then every Timer +Delay seconds they activate, do their results, and inactivate. +They can also be forced to activate by other Goals. They cannot +be activated by player or detpack touches. +N.B. When a TG goes ACTIVE, it does not have any AP, and hence + _cannot_ activate other goals, or apply modifications to + individual players. + +N.B. When a TG is forced to activate by another goal, they reset + their internal timer that decides when they are next going + to activate. + E.g. If a TG with a setting of 5 seconds is forced to activate, + it will reset it's timer, and still activate 5 seconds after + it was forced to activate. + +So, as a quick summary, TG's can have their state altered as +usual by other goals, and when they activate, they can: + + - Display a message to all players + + - Alter the details of one or more players, as follows: + - Give a player extra lives (if applicable) + - Alter a player's health/ammo/armor/powerups + - Give a player a GoalItem + - Remove a player from an AP + + The player can be all the members of a Team, all the players + on the map, or all the players within a specified distance from + the goal. + + - Inactivate another goal + - Remove another goal + - Restore another goal + + - Inactivate a group of goals + - Remove a group of goals + - Restore a group of goals + + - Display team's scores and End the Level + +---------------------------------------------------------------------------- +Standard Quake Triggers and TeamFortress Goals +---------------------------------------------------------------------------- +Quake triggers can be linked into TF Goals and vice versa. +When they are activated, a trigger can affect Goals +in the following ways: + + - Activate a goal, applying the bonuses of the goal to the AP + - Activate a goal, without applying the bonuses of the goal to the AP + - Inactivate another goal + - Remove another goal + - Restore another goal + + - Activate a group of goals + - Inactivate a group of goals + - Remove a group of goals + - Restore a group of goals + +When a Goal is activated, it can affect Quake Triggers +in the following ways: + + - Activate Quake Trigger(s) + - Remove Quake Trigger(s) + +Don't forget that Quake Triggers include Buttons, Doors, +Platforms, Health Boxes, Backpacks, Weapons, Armor, Keys, Sigils, +Monsters, Spikeshooters, Lights, Special Walls, and +Teleporters.. +The 'activation' of any of these things can affect goals +if you want it to. +Triggers Activate when: + + - Collected by a player + Health Boxes, Weapons, Armor, Keys, Sigils, Backpacks + - Touched by a player or hit by a weapon + Buttons, Doors, Platforms, Special Walls,Teleporters + - Killed + Monsters + +Also, Quake Triggers have been enhanced so that any Trigger +can be made to triggered only by a player if: + - The Player is of a particular class + - The Player has a particular GoalItem + - The Player is of a particular team + +This means that if the player does not match the criteria, +the following happens: + - Health Boxes, Weapons, Armor, Keys, Sigils, Backpacks + Player cannot pick them up. + - Buttons, Doors, Platforms, Special Walls, Teleporters + Player cannot activate/open/use them. + - Monsters + Player cannot hurt them. + +The result of 'Activation' of Triggers are as follows: + + - Health Boxes, Weapons, Armor, Keys, Sigils + Bad. These items should never be activated by + Goals or Triggers. They should only be used + to activate other Goals/Triggers. + + - Buttons + The button, if not already depressed, depressed + and sets off whatever it was designed to do. + + - Doors + If the door is a DOOR_TOGGLE type door, it reveres it's + current state. e.g. Opens if it's closed, closes if it's open. + It not, if it's closed, it opens. + + - Platforms + If it has not been triggered before, it will lower. Otherwise, + it does nothing. + + - Special Walls + Changes it's texture. + + - Teleporters + Doesn't do anything. + + - Monsters + Wakes the monster. + +These are a rough guide. You should look at the qc code which handles +trigger activation to see the details. Every Trigger that can be +activated by another trigger/goal has a .use function. +Look inside it to see how activations for that type of Trigger +are handled. + +---------------------------------------------------------------------------- +Order of Actions +---------------------------------------------------------------------------- +You don't really need to know the order in which the actions are applied, +and they're far too complex now to describe them in any useful way. The +only important thing of note is: + + The Group Goal alterations are applied _before_ the single goal + alterations. This is so you can do things like: + Inactivate an entire group, and then activate one goal in that group. + Remove an entire group, and then restore one goal in that group. + +---------------------------------------------------------------------------- +Goal Technical Info +---------------------------------------------------------------------------- +Goal behaviour is entirely defined by the variables of the Goal object +when it is spawned at the start of the level. These variables are set +in the map file. Just add up the flags that you want and set the +variable to that. +E.g. If you want a goal that can be activated by a Player touch(1) and + a detpack explosion(2), you would set the "goal_activation" variable + to 3. + +Don't worry! It's actually fairly easy :) +N.B. Bit descriptions marked with (*) are dangerous, and should + be handled with care. + +The variables of the Goals are handled as follows: + + classname : "info_tfgoal" + broadcast : Message broadcasted when this goal is activated, if any. + Don't forget to put the \n at the end of it. + message : Message displayed to the activating player, if any. + Don't forget to put the \n at the end of it. + deathtype : Message broadcasted if the goal kills the activating + player. It is appended to the name of the player. + E.g. deathtype = " gets killed by a goal!\n" displays + Bro gets killed by a goal! + Not needed if this goal doesn't do damage to a player. + Don't forget to put the \n at the end of it. + target : If set, any Quake Triggers with the same name as this + will be activated. + killtarget : If set, any Quake Triggers with the same name as this + will be removed. + count : Score the team of the player who activates this goal gets. + Use negative numbers for penalty goals. + goal_no : A unique number ID identifying this goal. Used to make + references to this goal in other goals. + group_no : The number of the goal group this goal belongs to. + netname : This isn't used in Quake, but in the TeamFortress + Entity Editor, it's used as the name for this goal. + noise : A .wav file played when the goal is activated. + Make sure it's a .wav file you've got. It's probably + best to use one of Quake's .wavs. + If unspecified, no sound is played. + mdl : The .mdl file used for this item. Make sure it's a .mdl + file you've got. It's probably best to use one of Quake's + .mdls. If unspecified, the Goal is invisible. + +All the following attributes are applied to the attributes of +every player specified by the "goal_effect"(see below). +(e.g. the self.health is added to the health of the player) + lives : Use negative values to remove lives from the player + health : Use negative values to hurt the player + armortype : The player's armortype is set to this value + armorvalue : The player's armorvalue is set to this value + Armortypes are: + 0.3 : Green + 0.6 : Yellow + 0.8 : Red + armorclass : The type of armor the player gets. (See Armor Types above) + frags : Use negative values to lower frag count + ammo_shells : Use negative values to remove ammo + ammo_nails : Use negative values to remove ammo + ammo_rockets : Use negative values to remove ammo + ammo_cells : Use negative values to remove ammo + ammo_medikit : Use negative values to remove ammo + ammo_detpack : Use negative values to remove ammo + +Note A: After applying all these values to the player, the playerclass + limitations of the player are applied. So if you set the health of + the player over that allowed, by his/her playerclass, it will then + be lowered to the max_health for that playerclass. + +Note B: "lives" allows you to give/remove lives from players. This is only + useful if the current map limits the number of lives each player + has. See the Auto Detection section above. + +TeamFortress Grenades can be given/removed to/from the player by setting +the following two variables. + no_grenades_1 : Use negative values to remove grenades + no_grenades_2 : Use negative values to remove grenades + +And finally, the following attributes are added to the global time +and applied to every player specified by the "goal_effect"(see below). +(e.g. if the goal's invincible_finished is 5, then the player will +get invincibility for 5 seconds after activating the goal.) + invincible_finished : Pentagram of Protection + invisible_finished : Ring of Shadows + super_damage_finished : Quad Damage + radsuit_finished : Environmental Suit + +--------------------------- +Goal Activation +--------------------------- +The "goal_activation" of the goals determine how the goal is activated. +The bitfields are as follows: + +Activation Details + 1 : Activated when touched by a player. + 2 : Activated when touched by a detpack explosion. + +AP Details + - If the "items_allowed" variable is non-zero, the AP must be + carrying a GoalItem with an ID of "items_allowed". + - If the "playerclass" variable is non-zero, the AP must be + be of the same class specified in "playerclass". + Classes are: + Scout : 1 + Sniper : 2 + Soldier : 3 + Demolitions Man : 4 + Medic : 5 + Heavy Weapons Guy : 6 + - If the "team_no" variable is non-zero, the AP must belong + to the same team as the number specified in "team_no". + + 4 : This goal is only activated if the AP details above are _not_ met. + + 2048: If this bit is set, the Goal/Item drops to the ground when it + first spawns. + +--------------------------- +Goal Effects +--------------------------- +The "goal_effects" of the goal defines which players are affected by +the activation of the goal. It defaults to 1, so that only the AP is +affected. +The player modifications of this Goal are applied to every player +that matches these criteria. GoalItems will also be given/removed +to/from every one of the players matching the criteria. +The bitfields are as follows: + +Basics + 1 : The AP is affected + 2 : Everyone on the AP's team is affected + 4 : Everyone not on the AP's team is affected + 8 : Everyone except the AP is affected + +Specific Groups + - If the "maxammo_shells" variable is non-zero, then all members of that + team are affected. + - If the "maxammo_nails" variable is non-zero, then all members of any + team except that one are affected. + +Radius + - If the "t_length" variable is non-zero, then everyone within + "t_length" distance of the goal is affected, unless the above + bitfields are in use. + If they are, then the bitfield check is applied only to those + caught in the radius. + E.g. if "t_length" is 50, and "goal_effects" is 4, then everyone + within 50 who isn't on the AP's team is affected. + + 16 : If set, the radius effect is obstructed by walls. + + 32 : If a player who fits one of the other "goal_effects" variables + is not in the same environment as the Goal, don't affect him. + Environments are air, water, slime, lava. + e.g. If a Goal is above some water, and does a radius effect + with "t_length", and a player in the water is within the + radius, he won't be affected if this bit is set. + +Note A: If the Goal gives out a GoalItem, then every player who matches + this criteria will get the GoalItem. This can result in lots of these + GoalItems, so be careful. + +--------------------------- +Goal Results +--------------------------- +The "goal_result" of the goals determine how the goal behaves when it is +activated, and the results of it's activation. It also determines what it +will do to any players that match it's "goal_effects" criteria. +The bitfields are as follows: + +Respawn behaviour + 1 : The goal is immediately removed after it is activated. + + - If the "wait" variable is non-zero, then the Goal stays ACTIVE for + the "wait" seconds, and then inactivates. + If "wait" is -1, the Goal stays active permanently. + +GoalItem behaviour + - If the "items" variable is non-zero, the Goal gives a GoalItem + to the player. The GoalItem it gives is the Item with a GoalNo + of "items". + - If the "axhitme" variable is non-zero, the Goal removes a GoalItem + from the player. The GoalItem it removes is the Item with a GoalNo + of "axhitme". + If the player does not have the GoalItem, nothing happens. + +Goal behaviour + 2 : If this is set, the goals activated by this one apply + their AP modifications. Otherwise, they don't. + + - If the "activate_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is activated. + - If the "inactivate_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is inactivated. + - If the "remove_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is removed. + - If the "restore_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is restored. + +Miscellaneous + 4 : Display teamscores, fire intermission, and end the level + + 8 : GoalItems given out by this Goal don't apply their results. + + 16 : If the player these results are being applied to is a Spy, + reset his/her skin and color. + +---- +Note A: If (1) is not set and their is no "wait" specified, then the + goal is a Multiple Goal. + After it activates, it will automatically inactivate. + This is not always a good idea. When a player runs over an entity, + the touch function will usually be run about 6 or 7 times, so the + player will activate the goal multiple times. If you want a player + to be able to activate it over and over again, make it respawn + every 2-3 seconds. + This is especially dangerous if the goal gives out an item on + activation, since you will end up with a _lot_ of items. + Of course, you may want this for a goal that is activated remotely + by another goal at certain times. + +Note B: When a goal is activated by another goal, _no_ checking is done + based on the "goal_activation" variable of the target goal. It is + simply activated. The AP who activated the original goal is used + as the activator for all the goals, but the stats of an activated + goal are only added to the player's if (2) is set on the goal + that activates it. + GoalItems are always given/removed. + +--------------------------- +Goal Groups +--------------------------- +Goal groups allow you to do operations of multiple goals in one go. +The "group_no" of a Goal defines the ID of goal group it is in. +Any goal group operation affects all goals with the specified ID. +A goal's "goal_group" specifies what operations, if any, it performs +on any goal groups. +Since I've cleaned up the goal group handling to get rid of bitflags +which aren't needed, there are no bits used in the "goal_group" +variable anymore :) +I've left it in anyway, since some may come along later. +Goal Group handling is as follows: + + - If the "all_active" variable is non-zero, then when this goal is activated, + a check is done. If all the goals in the group specified in the "all_active" + variable are ACTIVE, activate a goal with a goal number equal to the + value of the "last_impulse" variable. + + - If the "activate_group_no" variable is non-zero, activate all the + goals in that Group. + - If the "inactivate_group_no" variable is non-zero, inactivate all the + goals in that Group. + - If the "remove_group_no" variable is non-zero, remove all the + goals in that Group. + - If the "restore_group_no" variable is non-zero, restore all the + goals in that Group. + +---- +Note A: When a goal is activated by another goal, _no_ checking is done + based on the "goal_activation" variable of the target goal. It is + simply activated. The AP who activated the original goal is used + as the activator for all the group goals. + Bonuses of goals in groups are _never_ applied to the activating player. + GoalItems are always given/removed. + +---------------------------------------------------------------------------- +Timer Goal Technical Info +---------------------------------------------------------------------------- +Timer Goals are basically identical to Goals, except they are never activated +by players or detpack explosions. They automatically activate every +"search_time" seconds. They can also be activated by other goals and triggers. +The variables of Timer Goals are the same as Goals, except for: + + classname : "info_tfgoal_timer" + + search_time : The time in seconds that this goal activates. + e.g. a search_time of 2 makes this goal activate every + 2 seconds. + +As a result of their method of activation, Timer Goals can _never_ do +anything that requires an AP, and they can't activate any other Goals. +They _can_ do operations on groups of players, such as all the players +on the map, or all the players in a team, etc. +This is done using the "goal_effect" variable in much the same way +as Goals, as follows: +Basics + 1 : Illegal for Timer Goals. + 2 : Illegal for Timer Goals. + 4 : Illegal for Timer Goals. + 8 : Illegal for Timer Goals. + +Specific Groups + - If the "maxammo_shells" variable is non-zero, then all members of that + team are affected. + - If the "maxammo_nails" variable is non-zero, then all members of any + team except that one are affected. + +Radius + - If the "t_length" variable is non-zero, then everyone within + "t_length" distance of the goal is affected, unless the above + bitfields are in use. + If they are, then the bitfield check is applied only to those + caught in the radius. + E.g. if "t_length" is 50, and "goal_effects" is 4, then everyone + within 50 who isn't on the AP's team is affected. + + 16 : If set, the radius effect is obstructed by walls. + + 32 : If a player who fits one of the other "goal_effects" variables + is not in the same environment as the Goal, don't affect him. + Environments are air, water, slime, lava. + e.g. If a Goal is above some water, and does a radius effect + with "t_length", and a player in the water is within the + radius, he won't be affected if this bit is set. + + 64 : If this bit is set, then instead of just applying this Goal's + results to the group of players specified by the other + "goal_effects" variable, this Goal checks it's criteria for + each player in the group and then applies it's results invididually + to any of them that pass. + +Note A : The first 4 bits in the "goal_effects" are illegal for Timer Goals, + and should _never_ be set, because they involve an AP. + +---------------------------------------------------------------------------- +Goal Items Technical Info +---------------------------------------------------------------------------- +GoalItems do not have to be given to a player by a Goal activation. +They can be placed directly into a map. +The variables of the GoalItems are as follows: + + classname : "item_tfgoal" + netname : Name of this GoalItem. + broadcast : Message broadcasted when this goalitem is retrieved, if any. + Don't forget to put the \n at the end of it. + message : Message displayed to a player when they get the Item, if any. + Don't forget to put the \n at the end of it. + noise : A .wav file played when the item is collected. + Make sure it's a .wav file you've got. It's probably + best to use one of Quake's .wavs. + If unspecified, no sound is played. + mdl : The .mdl file used for this item. Make sure it's a .mdl + file you've got. It's probably best to use one of Quake's + .mdls. It defaults to the silver key. + goal_no : A unique number identifying this GoalItem. Used to make + references to this Item in goals. + goal_group : The number of the goalitem group this goal belongs to. + +GoalItems can also modify player details in the same Goals can, using +the "goal_effects" variable (see above). +The tricky bit is that any Player Modifications done by goals are added +when the player first picks the item up, and are _removed_ when the player +loses the item. +E.g. If the item has a health of 40, then the player gets 40 health + added to their health when he/she picks it up. Then, when they + drop it, he/she loses 40 health. +There are two exceptions to this: + + 1. Power-Ups. Items give the players a powerup for the entire time the + player has the item. The time specified in the _finished variable + is ignored: If it's non-zero, then all players the GoalItem specifies + get the power-up while item is still carried. + + 2. GoalItems. Items given/removed by another GoalItem when it's picked up + are not removed/given back when the original GoalItem is dropped/removed. + +With the "goal_effects" variable you can make Items affect more than +one player. This is done exactly as if it was just one person. +E.g. If the Item adds health to everyone, then as soon as any player +picks it up, the modifications are applied to everyone, and as soon as +it's dropped, the modifications are removed from everyone. + +Also, GoalItems can affect Goals in much the same way as other Goals, +as follows: + + - If the "activate_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is activated. + - If the "inactivate_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is inactivated. + - If the "remove_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is removed. + - If the "restore_goal_no" variable is non-zero, then the Goal with + a Goal_No equal to the variable is restored. + + - If the "activate_group_no" variable is non-zero, activate all the + goals in that Group. + - If the "inactivate_group_no" variable is non-zero, inactivate all the + goals in that Group. + - If the "remove_group_no" variable is non-zero, remove all the + goals in that Group. + - If the "restore_group_no" variable is non-zero, restore all the + goals in that Group. + +GoalItems can use the "goal_result" variable. The Bitfields are as follows: + + 2 : Goals activated by this Item don't apply bonuses to the AP. + + 16 : If the player these results are being applied to is a Spy, + reset his/her skin and color. + When a Spy is carrying a GoalItem with this bit set, he/she + will be unable to change skin and color. + +When Goals are activated by a GoalItem, the AP used to activate the Goals +is the Player who picked up the Item. AP Modifications are applied if (512) +is set it the "goal_activation" variable (see below). +GoalItems themselves have various bitfields to depict the GoalItem's +behaviour. The bitfields are stored in the GoalItem's "goal_activation" +variable, and are: + +Carrying Details + 1 : Any player carrying this item will glow. + 2 : Any player carrying this item will move at half-speed. +(*) 4 : When a player carrying this is killed, the item will be dropped + +Returning + 8 : Return the item if dropped by a dying player + 16 : Return the item if removed from a player by a goal activation. + 32 : Return the item if removed from the map by the action of the + (128) (see below). + + - If the "impulse" variable is non-zero, then when the GoalItem + is returned by one of the above, the goal with a Goal No equal to + the "impulse" is restored. + +Pickup Details + - If the "items_allowed" variable is non-zero, the Player must be + carrying a GoalItem with an ID matching the variable. If not, + he/she will not be able to get this item. + - If the "playerclass" variable is non-zero, the Player must be + be of the same class specified in the variable. If not, + he/she will not be able to get this item. + Classes are: + Scout : 1 + Sniper : 2 + Soldier : 3 + Demolitions Man : 4 + Medic : 5 + Heavy Weapons Guy : 6 + - If the "team_no" variable is non-zero, the Player must belong + to the same team as the number specified in the variable. + If not, he/she will not be able to get this item. + + 64 : This GoalItem can only be picked up by a player if they _don't_ + match the player details above. + +Miscellaneous + 128 : If this is set, then, after the goal is dropped by a player, + if it has not been touched for the time specified in the + "pausetime" variable, in seconds, it is removed. + + - If the "distance" variable is non-zero, then, If all GoalItems in the + goalitem group specified by "distance" are being carried by players, + activate a goal, the Goal No of which is specified in "pain_finished". + - If the "speed" variable is non-zero, then, If all GoalItems in the + goalitem group specified by "speed" are being carried by one player, + activate a goal, the Goal No of which is specified in "attack_finished". + + 256 : If a player carrying this Item dies, they keep it, even after + they respawn. + + 512 : If this Item is not being carried by a goal, it glows. + + 1024: When this item is removed from a player, don't remove the + effects this item gave the player. + + 2048: If this bit is set, the Goal/Item drops to the ground when it + first spawns. + + 4096: If this bit is set, any player carrying this item can drop + it using the "dropitems" command. + +---- +Note A: (4) is dangerous. If you have goals giving out this item multiple + times, make sure the item is getting removed somehow, or you could + end up with too many items on the map. + +Note B: The Pickup Details are only used when a player picks up this + item directly. If this item is given to an AP by a goal, these + are ignored. + +Note D: (128) allows you to make items that are untouched for a while + disappear. This should be set if you have Goals that give out + multiple copies of GoalItems that are dropped when players + carrying them are killed. Otherwise you could end up with too many + entities on the map :) + If you have a GoalItem that is only given out once and is dropped + when players with it die, you will probably want to set (128) + and (32), so that if it falls in lava or something, it'll be + returned. + If you don't specify a value, "pausetime" defaults to 2 minutes. + +---------------------------------------------------------------------------- +Quake Triggers that use TeamFortress Goals Technical Details +---------------------------------------------------------------------------- +For Triggers to use Goals, you need to use various variables. +Triggers can use the same AP criteria matching as Goals, as follows: + + - If the "items_allowed" variable is non-zero, the AP must be + carrying a GoalItem with an ID of "items_allowed". + - If the "playerclass" variable is non-zero, the AP must be + be of the same class specified in "playerclass". + Classes are: + Scout : 1 + Sniper : 2 + Soldier : 3 + Demolitions Man : 4 + Medic : 5 + Heavy Weapons Guy : 6 + - If the "team_no" variable is non-zero, the AP must belong + to the same team as the number specified in "team_no". + + +If any of the following variables is not set, or 0, then the effect of that +variable is simply not used. +The variables are: + + activate_goal_no : Activates a goal with a goal_no equal to this. + inactivate_goal_no : Inactivates a goal with a goal_no equal to this. + remove_goal_no : Removes a goal with a goal_no equal to this. + restore_goal_no : Restores a goal with a goal_no equal to this. + + activate_group_no : Activates a group of goals with a goal_group equal to this. + inactivate_group_no : Inactivates a group of goals with a goal_group equal to this. + remove_group_no : Removes a group of goals with a goal_group equal to this. + restore_group_no : Restores a group of goals with a goal_group equal to this. + + +Also: + + - If the "goal_result" variable of the Trigger is 0, then any Goals + activated by this Trigger _don't_ add bonuses to the AP. If it's 2, they do. + + - If the "all_active" variable is non-zero, then when this Trigger is activated, + a check is done. If all the goals in the group specified in the "all_active" + variable are ACTIVE, activate a goal with a goal number equal to the + value of the "last_impulse" variable. + +In all cases, the AP for the goals will be set to the "activator". If the +AP is not a player, then no player-related operations will be performed by +the Goals, or any Goals activated by those Goals. +This stuff is kind of dangerous, if the Trigger isn't activated by a player, +and the Goals your playing with alter other Goals. Just be careful :) + +---------------------------------------------------------------------------- +Summary of Changes +---------------------------------------------------------------------------- +This is a summary of changes in the map specs from TeamFortress v1.3x +to TeamFortress v2.x : + + - All entities, most especially GoalItems, can specify the team + they belong to by putting the team number in the "owned_by" variable. + + - The "goal_state" variable allows you to set the starting state + of a goal. The available states are: + Active 1 + Inactive (default) 2 + Removed 3 + + - Activation criteria for Goals/Items/Timers/Triggers can + now include the Checking of the state of other Goals + The following variables are used: + "if_goal_is_active" : If this is non-zero, the goal will only activate + if the goal with a "goal_no" matching + "if_goal_is_active" is active. + "if_goal_is_inactive" : As above, if the goal is inactive. + "if_goal_is_removed" : As above, if the goal is removed. + + "if_group_is_active" : As above, except that all goals with a "group_no" + matching "if_group_is_active" must be active. + "if_group_is_inactive": As above, if all the group is inactive. + "if_group_is_removed" : As above, if all the group is removed. + + - Upon activation, Goals can now centerprint more intelligently, + as follows: + "broadcast" : Instead of being bprinted, it now gets + centerprinted to everyone, except the AP. + You'll probably want to use "team_broadcast" + and "non_team_broadcast" instead. + "message" : Instead of sprinting to the AP, it now + centerprints. + "team_broadcast" : Gets centerprinted to all of the AP's team, + except the AP. + "non_team_broadcast" : Gets centerprinted to all non-AP-teammembers. + If there is a "owners_team_broadcast" specified, + it isn't centerprinted to members of the team + that own the goal/item. + "owners_team_broadcast" : Gets centerprinted to all the members of the + team that own the goal/item. + + - The detection entity can now supply the Team Menu string, which + effectively allows you to provide names for team. + The string should be put in the detection entity's "team_broadcast" var. + It gets centerprinted when player's are asked to join a team, + so it should be similar to the following format: + + "=== Choose your team ===\n\n1.. Team One \n\n\n\n\n7.. Bind my keys for me!\n\nFor full details on this patch:\nhttp://yallara.cs.rmit.edu.au/~cookj\n" + "=== Choose your team ===\n\n2.. Team One \n2.. Team Two \n\n\n\n\n7.. Bind my keys for me!\n\nFor full details on this patch:\nhttp://yallara.cs.rmit.edu.au/~cookj\n" + "=== Choose your team ===\n\n2.. Team One \n2.. Team Two \n3.. Team Three\n\n\n\n\n7.. Bind my keys for me!\n\nFor full details on this patch:\nhttp://yallara.cs.rmit.edu.au/~cookj\n" + "=== Choose your team ===\n\n3.. Team One \n2.. Team Two \n3.. Team Three\n4.. Team Four \n\n\n\n\n7.. Bind my keys for me!\n\nFor full details on this patch:\nhttp://yallara.cs.rmit.edu.au/~cookj\n" + + Other examples are: + + "=== Choose your team ===\n\n1.. Blue Team \n2.. Red Team \n\n\n\n7.. Bind keys" + "=== Choose your team ===\n\n1.. Attackers \n2.. Defenders \n\n\n\n7.. Bind keys" + + You need to have a team number and choice for every team on your map. + Don't forget that TF determines the number of teams on a map based upon + the Teamspawn points. If there are spawn points for 3 teams, then you + should have three teams in the team menu string. + + N.B. Unfortunately, qbsp prevents you from having large strings, so you + have to be curt. If you get a "Token too large" error when qbsping, + then you've got the string too long. + + - The detection entity can now specify a Map Help string in it's + "non_team_broadcast" variable. + Player's can do the Map Help impulse, and the Map Help string will + be printed. The string can be anything you like, but it should be + a quick description of the goal of the map, such as: + "Havoc: Get into the enemy's castle and destroy their computer room!\n" + + End the string with a \n. + N.B. Unfortunately, qbsp prevents you from having large strings, so you + have to be curt. If you get a "Token too large" error when qbsping, + then you've got the string too long. + + - All Entities can specify whether they exist for different skill + settings, as follows: + "ex_skill_min" : The entity only exists when the skill variable + is >= to this value. + "ex_skill_max" : The entity only exists when the skill variable + is <= to this value. + + N.B. The checking is done in the initial spawning, so if the skill + changes during the level, it will have no effect on the entities. + + You can use this to do so many things, such as making plats move + in different trains at different skill levels, more/less monsters + and ammo and different levels, more secrets at higher levels, etc. + + - Team Spawnpoints can set their "goal_effects" variable to 1, which + makes them remove themselves after someone has spawned on them. + Use this to make a team spawn inside their base initially, and then + spawn throughout the level for the rest of the game. + + - Items now have a ItemGlow flag, which makes the Item glow when it's + not being carried by a player. + The flag is Bit 10 (512) in the Goalitem's "goal_activation" variable. + + - The Detection entity can now restrict the classes available to + members of particular teams, in the following variables: + Team 1 : "maxammo_shells" + Team 2 : "maxammo_nails" + Team 3 : "maxammo_rockets" + Team 4 : "maxammo_cells" + + - The Detection entity can now specify a Class Selection string for + any team. The default string allows them to pick any class, but you + may want to refine it for teams that have a limited selection of + classes. + If you don't supply one, the default will be used. + The strings should be put in following Detection entity variables : + Team 1 String : "noise1" + Team 2 String : "noise2" + Team 3 String : "noise3" + Team 4 String : "noise4" + + The default string looks like this: + + "=== Choose your class ===\n\n1.. Scout \n2.. Sniper \n3.. Soldier \n4.. Demoman \n5.. Medic \n6.. Hvwep \n7.. Pyro \n8.. Spy \n9.. Engineer\n0.. Randompc\n" + + But if you restricted Team 1 to Scout, Demoman, and Medic, you might + want to set the "noise1" variable to: + + "=== Choose your class ===\n\n1.. Scout \n4.. Demoman \n5.. Medic \n" + + N.B. You _MUST_ not alter the numbering of the class. + e.g. Sniper must always be selected with 2, Demoman with 4, etc. + N.B. Unfortunately, qbsp prevents you from having large strings, so you + have to be curt. If you get a "Token too large" error when qbsping, + then you've got the string too long. + + - Any Goal can now specify an 'else' goal. This is a goal which attempts + to activate if this goal fails to activate because it's Criteria + wasn't met. The "goal_no" of the 'else' goal should be specified in + the Goal's "else_goal" variable. 'else' Goals still check their + criteria, the AP being the player who failed the first goal's criteria. + + - The Detection entity can now specify a maximum number of players + for each team. The default is 0, which is unlimited. + The player limits should be put in the following variables of + the Detection Entity : + Team 1 : "ammo_medikit" + Team 2 : "ammo_detpack" + Team 3 : "maxammo_medikit" + Team 4 : "maxammo_detpack" + + N.B. The total number of players _MUST_ be >= 16. This is the total + number of players for the _existing_ teams. + e.g. if you have TeamSpawnpoints for only 2 teams, then the + total number of players allowed in the two teams must be >= 16. + + - Any Goal can now display the status of upto 4 GoalItems when it's + activated. The Goal specifies 6 different strings, which are used + to do the displaying for all the items. + The item's "goal_no" are specified in the following variables: + "display_item_status1" : Item 1 + "display_item_status2" : Item 2 + "display_item_status3" : Item 2 + "display_item_status4" : Item 2 + + The strings are specified in the following variables: + "team_str_home" : Displayed when the item is at the point + it originally spawned at. + "team_str_moved" : Displayed if the item has been moved from + it's original spawning point. + "team_str_carried" : Displayed if the item is being carried. It + is prepended to the carrier's name, or " You" + if the Player is carrying the item. + e.g. "Your flag is being carried by" Bro. + "non_team_str_home" : Displayed when the item is at the point + it originally spawned at. + "non_team_str_moved" : Displayed if the item has been moved from + it's original spawning point. + "non_team_str_carried" : Displayed if the item is being carried. It + is prepended to the carrier's name, or " You" + if the Player is carrying the item. + e.g. "The enemy flag is being carried by" You. + + The "team..." strings are displayed to members of the team that the + GoalItem belongs too. The "non_team..." strings are displayed to anyone + not on the team the GoalItem belongs to. + If the Goalitem does not belong to a team, the "team..." strings + are displayed. + + Finally, if the Detection Entity has these variable set, then when + a player does Impulse 21 (FlagInfo in CTF), then the details of the + items specified by the Detection Entity will be displayed. + + - GoalItems can centerprint messages to players when they return to + their starting point, regardless of how they return. + Two messages should be supplied in the following variables: + "noise3" : Is centerprinted to all the members of the team + that own the item. + "noise4" : Is centerprinted to everyone except the members of + the team that own the item. + + - You can restrict teams to only the Civilian class, for special + purposes. Do it by setting their class restriction variable to "-1". + + - Any Goal can force a GoalItem to return, if it's not being carried, + by specifying an Item No in the "return_item_no" variable. + + - Goals activated by other Goals don't automatically activate like + they used to. Now they check their Criteria before activating. The AP + used is the AP of the first Goal. + N.B. The AP doesn't have to initiate the Criteria checking on + activated goals. E.g. The AP doesn't have to be touching + the Goal which was activated, even if the Goal's "goal_activation" + specifies that it's activated by touch. + + - GoalItems have a flag, which they can check, which tells them if they're + not at their starting position. + You can make any Goal/Item/whatever have part of it's Criteria + check to see if an Item is _not_ at it's origin by putting the + "goal_no" of the item in the "if_item_has_moved" variable. + You can make any Goal/Item/whatever have part of it's Criteria + check to see if an Item _is_ at it's origin by putting the + "goal_no" of the item in the "if_item_hasnt_moved" variable. + + - Goals that give out items can specify whether they want the Items + to apply their results. Usually, the goal that gives out the items + does all the results you want, so you can specify this. + Do it by setting Bit number 4 (8) in the Goal's "goal_result" variable. + + - The Detection Entity can specify that the grappling hook cannot + be used on a map, by setting the Detection Entity's "hook_out" + variable to 1. + + - Any Goal can remove/restore Teamspawnpoints. The variables to use + are: + "remove_spawnpoint" : Remove a spawnpoint with a matching "goal_no" + "restore_spawnpoint" : Restore the matching spawnpoint + "remove_spawngroup" : Remove all spawnpoints with a "group_no" + matching this number. + "restore_spawngoup" : Restore a group of spawnpoints. + + - GoalItems can make console items light up, such as the silver key icon, + when they're being carried. In the "items" variable, just specify a + bitfield. The bit's values are as follows: + + Silver Key : 131072 + Gold Key : 262144 + + Invisibility Icon : 524288 + Invulnerability Icon : 1048576 + Radsuit Icon : 2097152 + Quad Icon : 4194304 + + - GoalItems don't restore a goal when they return, now they activate it. + If you had any GoalItems using the "impulse" variable to restore a Goal + when the GoalItem returns, you'll need to redo the Goalwork. + + - Goals can increase the scores of any specific teams, using the following + variables: + + increase_team1 : Increase the score of team 1 by this value + increase_team2 : Increase the score of team 2 by this value + increase_team3 : Increase the score of team 3 by this value + increase_team4 : Increase the score of team 4 by this value + +------------------------- +Changes from 2.11 to 2.12 + + - Upon activation, Goals can now broadcast even more, + as follows: + + "netname_broadcast" : Gets bprinted to _all_ players, prepended + by the AP's name. + E.g. " got the RED flag" would be bprinted + as "Bro got the Red Flag" + + "netname_team_broadcast" : Gets bprinted to all of the AP's team, + except the AP, with the AP's name prepending + it. + + "netname_non_team_broadcast" : Gets bprinted to all non-AP-teammembers, + prepended by the AP's name. + If there is a "owners_team_broadcast" specified, + it isn't centerprinted to members of the team + that own the goal/item. + + "netname_owners_team_broadcast" : Gets bprinted to all the members of + the team that own the goal/item, prepended by + the AP's name. + +------------------------- +Changes from 2.12 to 2.13 + + - GoalItems can centerprint messages to players when they're dropped by + a dying player. + Two messages should be supplied in the following variables: + "team_drop" : Is centerprinted to all the members of the team + that own the item. + "non_team_drop" : Is centerprinted to everyone except the members of + the team that own the item. + Also, messages can be appended to the name of the player who dropped + the item, and then bprinted. + As follows: + "netname_team_drop" : Gets bprinted to all members of the team + who own the item, prepended by the name of + the player who dropped the item. + "netname_non_team_drop" : Gets bprinted to all players not of the team + who own the item, prepended by the name of + the player who dropped the item. + +------------------------- +Changes from 2.13 to 2.14 + + - A new Bit was added to the "goal_activation" of GoalItems. + As follows: + 1024: When this item is removed from a player, don't remove the + effects this item gave the player. + + E.g. If you've got a GoalItem giving a player 2 frags, when the + GoalItem is removed from the player, the 2 frags won't be + removed. Be Careful with this one. Check all the results you + bestow on the player before using it. + If necessary, have the GoalItem activate another goal which + bestows the effects you want on the player instead. + +------------------------- +Changes from 2.14 to 2.5 + + - A new Bit was added to the "goal_result" of Goals and GoalItems. + As follows: + + 16 : If the player these results are being applied to is a Spy, + reset his/her skin and color. + When a Spy is carrying a GoalItem with this bit set, he/she + will be unable to change skin and color. + + This enables you to have areas where spies cannot go undercover, etc. + It also means you can have the spy unable to change his/her skin/color + when carrying the flag. + To introduce a bit of backwards compatability, the spy cannot disguise + himself when he's dimly lit either, due to carrying a GoalItem with + the first bit of its "goal_activation" set. + Almost all the flags in existing TF maps have this bit set. + + - Goals can work with groups of items more now, using the following + variables: + "has_item_from_group" : More Criteria. Player must be carrying + at least 1 of the items in the group. + + "remove_item_group" : Result. Removes all items belonging to this + group from the Player. + + - A new Bit was added to the "goal_effects" of Goals and GoalItems. + As Follows: + + 32 : If a player who fits one of the other "goal_effects" variables + is not in the same environment as the Goal, don't affect him. + Environments are air, water, slime, lava. + e.g. If a Goal is above some water, and does a radius effect + with "t_length", and a player in the water is within the + radius, he won't be affected if this bit is set. + + - The Spy and Engineer were added to the list of classes the TF Detect + entity can ban. The list is now as follows: + Bit 1 (1) : No Scout + Bit 2 (2) : No Sniper + Bit 3 (4) : No Soldier + Bit 4 (8) : No Demolitions Man + Bit 5 (16) : No Combat Medic + Bit 6 (32) : No Heavy Weapons Guy + Bit 7 (64) : No Pyro + Bit 8 (128) : No Random PlayerClass + Bit 9 (256) : No Spy + Bit 10(512) : No Engineer + + - Since quake has a limit on the size of the entity data in a map, + abbreviations for some of the common entity fields were created. + The Abbreviations are as follows: + Classnames + "i_p_t" for "info_player_teamspawn" + "i_t_g" for "info_tfgoal" + "i_t_t" for "info_tfgoal_timer" + + Common Variables + "g_a" for "goal_activation" + "g_e" for "goal_effects" + + String Variables + "t_s_h" for "team_str_home" + "t_s_m" for "team_str_moved" + "t_s_c" for "team_str_carried" + "n_s_h" for "non_team_str_home" + "n_s_m" for "non_team_str_moved" + "n_s_c" for "non_team_str_carried" + + "b_b" for "broadcast" + "b_t" for "team_broadcast" + "b_n" for "non_team_broadcast" + "b_o" for "owners_team_broadcast" + "n_b" for "netname_broadcast" + "n_t" for "netname_team_broadcast" + "n_n" for "netname_non_team_broadcast" + "n_o" for "netname_owners_team_broadcast" + + "d_t" for "team_drop" + "d_n" for "non_team_drop" + "d_n_t" for "netname_team_drop" + "d_n_n" for "netname_non_team_drop" + + - Goals now affect players who are dead, allowing dead players + to get teamscores when their team captures flags, etc. + + - Goals can use the "delay_time" variable now to delay their activation + for a specific amount of time. + + - Team Spawnpoints can now use Criteria the same way Goals do. + Players can only spawn at spawnpoints that they match the Criteria of. + + - Team Spawnpoints can activate a goal when someone spawns on them, + using the "activate_goal_no" variable. + + - Way back in version 1.3 the "goal_effects" variable was introduced, + to allow goals to affect more than one player. In version 1.21 you + didn't have to specify a "goal_effects" variable... instead, any + goal simply affected it's AP. To allow for backwards compatability, + in all versions since 1.3, any Goal that did not specify a "goal_effects" + field automatically had it's "goal_effects" set to 1, which is affect + AP only. This has become a problem now, since it prevents map-makers + from having goals that have a "goal_effects" of 0. + From this version onwards, the "goal_effects" variable will be left + as it is in the .map file. So, if you have a map which contains any + Goals/Items that do _not_ have a "goal_effects" variable specified, + add it in and set it to 1. + + - Items given by Goals are not set to Active state anymore, so they + can be given out multiple times. + + - An extra bit was added to the "goal_effects" variable for Timer Goals. + The bit will be ignored by any goal/item other than Timers, and is as + follows: + 64 : If this bit is set, then instead of just applying this + Goal's results to the group of players specified by the + other "goal_effects" variable, this Goal checks it's + criteria for each player in the group and then applies it's + results invididually to any of them that pass. + + - An extra bit was added to the "goal_activation" variable of + func_button's and func_door's. + 8 : If this bit is set, then activate this button/door only + when it's hit by an engineer's spanner. + +------------------------- +Changes from 2.5 to 2.6 + + - The Map Debug was cleaned up a lot, most especially in the + Criteria Checking section. + + - A new bit (32) was added to "goal_result" for Goals/etc which forces + anyone affected by the goal to simply respawn. The player doesn't + die... just respawns. + Used in conjunction with TeamSpawns that have criteria and the + enable/disable TeamSpawns capabilities, this can be used to + move an entire team to specific points around the map. + + - An new bit (2048) was added to "goal_activation" for Goals and GoalItems. + 2048: If this bit is set, the Goal/Item drops to the ground when it + first spawns. + + - Another new bit (4096) was added to "goal_activation" for GoalItems. + 4096: If this bit is set, any player carrying this item can drop + it using the "dropitems" command. + + - Yet another bit (8192) was added to "goal_activation" for GoalItems. + 8192: If this bit is set, the GoalItem is Solid while not + being carried by a player. This means it blocks bullets, + grenades. + N.B. It will also block any players that don't pass it's + criteria. Players that do pass the criteria will + simply pick it up. + + - The size of a Goal or GoalItem can now be set using the "goal_min" + and "goal_max" variables to set the min and max bounding box. + If not set, they default to: + "goal_min" "-16 -16 -24" + "goal_max" "16 16 32" + + - More Abbreviations, as follows: + Floats + "h_i_g" for "has_item_from_group" + "r_i_g" for "remove_item_group" + + "a_s" for "ammo_shells" + "a_n" for "ammo_nails" + "a_r" for "ammo_rockets" + "a_c" for "ammo_cells" + + "rv_s_h" for "remove_spawngroup" + "rs_s_h" for "restore_spawngroup" + "rv_gr" for "remove_group_no" + "rs_gr" for "restore_group_no" + "rv_g" for "remove_goal_no" + "rs_g" for "restore_goal_no" + + - The "ex_skill_min" and "ex_skill_max" variables can now + use "-1" to mean 0. This is needed because specifying either + as 0 is the same as not specifying it at all. + E.g. An entity with: + "ex_skill_min" "-1" + "ex_skill_max" "-1" + Would only exist at skill 0. + An entity with: + "ex_skill_min" "-1" + "ex_skill_max" "2" + Would only exist at skill 0, 1, or 2. + + +---------------------------------------------------------------------------- +If you think of any more things goals could do when activated, +please don't hesitate to mail them to me. + +And as a last word, I _really_ suggest that you use the Entity Editor. +I use it myself. It's far too much of a pain mucking about getting all +the right variables set to the right things to bother with doing +it all manually. +Originally, it looked like it would be feasible to do it yourself, +but they've just become far too complex now. + +Have fun! + +Robin. (robin@teamfortress.com) +=---------------------------------------------------------------------------= +TEAMFORTRESS v2.8 20/5/98 +TeamFortress Software Pty. Ltd. +Company WWW: http://www.teamfortress.com/ +TF Web Site: http://www.planetquake.com/teamfortress + diff --git a/versions.txt b/docs/versions.txt similarity index 97% rename from versions.txt rename to docs/versions.txt index bb71f2b0f..c0c99cba5 100644 --- a/versions.txt +++ b/docs/versions.txt @@ -900,7 +900,7 @@ Map Code Enhancements: a particular group in it's criteria using "has_item_from_group". - Goals can use the "remove_item_group" to remove all the items in a particular group from any player affected by its activation. - - A new Bit was added to the "goal_results" variable for Goals/Items + - A new Bit was added to the "goal_result" variable for Goals/Items which allows them to remove a Spy's disguise. - A new Bit was added to the "goal_activation" of GoalItems. See the tfortmap.txt for details. diff --git a/generate_ctags.sh b/generate_ctags.sh new file mode 100755 index 000000000..c4424ab05 --- /dev/null +++ b/generate_ctags.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +rm *tags +fteqcc64 -ftags ./ssqc/progs.src +fteqcc64 -ftags ./csqc/csprogs.src +fteqcc64 -ftags ./menu/menu.src +cat *.tags|LC_COLLATE=C sort>tags +rm *.tags diff --git a/help.qc b/help.qc deleted file mode 100644 index 720f01730..000000000 --- a/help.qc +++ /dev/null @@ -1,163 +0,0 @@ -// CLASS HELP FOR CLASSIC FORTRESS -// =============================== -// Shows class bindings for each class. - -// functions by order of appearance -void () Help_Show; -void () Help_ShowScout; -void () Help_ShowSniper; -void () Help_ShowSoldier; -void () Help_ShowDemoman; -void () Help_ShowMedic; -void () Help_ShowHWGuy; -void () Help_ShowPyro; -void () Help_ShowSpy; -void () Help_ShowEngineer; - -// global variables -// - -// shows a list of key bindings and aliases for current class -// called from weapons.qc:ImpulseCommands() -void () Help_Show = { - if (self.playerclass == PC_SCOUT) - Help_ShowScout(); - if (self.playerclass == PC_SNIPER) - Help_ShowSniper(); - if (self.playerclass == PC_SOLDIER) - Help_ShowSoldier(); - if (self.playerclass == PC_DEMOMAN) - Help_ShowDemoman(); - if (self.playerclass == PC_MEDIC) - Help_ShowMedic(); - if (self.playerclass == PC_HVYWEAP) - Help_ShowHWGuy(); - if (self.playerclass == PC_PYRO) - Help_ShowPyro(); - if (self.playerclass == PC_SPY) - Help_ShowSpy(); - if (self.playerclass == PC_ENGINEER) - Help_ShowEngineer(); -}; - -void () Help_ShowScout = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Scout:\n"); - sprint(self, PRINT_HIGH, "± - Equip Nailgun\n"); - sprint(self, PRINT_HIGH, "² - Equip Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Axe\n"); - sprint(self, PRINT_HIGH, "å - Toggle Scanner on/off\n"); - sprint(self, PRINT_HIGH, "æ - Throw Caltrop Canisters\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Concussion Grenade\n"); - sprint(self, PRINT_HIGH, "\nClass aliases for Scout:\n"); - sprint(self, PRINT_HIGH, "áõôïóãáî - Toggle Scanner on/off\n"); - sprint(self, PRINT_HIGH, "óãáîóïõîä - Toggle Scanner sound on/off\n"); - sprint(self, PRINT_HIGH, "óãáîå - Toggle scanning of enemies on/off\n"); - sprint(self, PRINT_HIGH, "óãáîæ - Toggle scanning of friendlies on/off\n"); -}; - -void () Help_ShowSniper = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Sniper:\n"); - sprint(self, PRINT_HIGH, "± - Equip Sniper Rifle\n"); - sprint(self, PRINT_HIGH, "² - Equip Sniper Rifle on Full Auto\n"); - sprint(self, PRINT_HIGH, "³ - Equip Nailgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Axe\n"); - sprint(self, PRINT_HIGH, "å - Toggle zoom mode\n"); - sprint(self, PRINT_HIGH, "í÷èååìõð - Zoom in (while in zoom mode)\n"); - sprint(self, PRINT_HIGH, "í÷èååìäï÷î - Zoom out (while in zoom mode)\n"); - sprint(self, PRINT_HIGH, "æ - Throw Flare\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); - sprint(self, PRINT_HIGH, "\nClass aliases for Sniper:\n"); - sprint(self, PRINT_HIGH, "úïïíôïççìå - Toggle zoom mode\n"); - sprint(self, PRINT_HIGH, "úïïíéî  - Zoom in (for adjusting zoom while in zoom mode)\n"); - sprint(self, PRINT_HIGH, "úïïíïõô - Zoom out (for adjusting zoom while in zoom mode)\n"); - sprint(self, PRINT_HIGH, "\nSettings for Sniper:\n"); - sprint(self, PRINT_HIGH, "úæ ¼æïö¾ - The default zoom fov which zoomtoggle zooms to (default 30)\n"); - sprint(self, PRINT_HIGH, "úó ¼æïö¾ - The fov increments/decrements used by zoomin/zoomout (default 20)\n"); - sprint(self, PRINT_HIGH, "Usage: setinfo \n"); -}; - -void () Help_ShowSoldier = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Soldier:\n"); - sprint(self, PRINT_HIGH, "± - Equip Rocket Launcher\n"); - sprint(self, PRINT_HIGH, "² - Equip Super Shotgun\n"); - sprint(self, PRINT_HIGH, "³ - Equip Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Axe\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw Nail Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); -}; - -void () Help_ShowDemoman = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Demolitions Man:\n"); - sprint(self, PRINT_HIGH, "± - Equip Grenade Launcher\n"); - sprint(self, PRINT_HIGH, "² - Equip Pipebomb Launcher\n"); - sprint(self, PRINT_HIGH, "³ - Equip Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Axe\n"); - sprint(self, PRINT_HIGH, "µ - Detpack menu\n"); - sprint(self, PRINT_HIGH, "å - Detonate pipebombs\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw Mirv Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); - sprint(self, PRINT_HIGH, "\nClass aliases for Demolitions Man:\n"); - sprint(self, PRINT_HIGH, "äåôðéðå - Detonate pipebombs\n"); - sprint(self, PRINT_HIGH, "«äåôµ - Place detpack with 5 second timer\n"); - sprint(self, PRINT_HIGH, "«äåô²° - Place detpack with 20 second timer\n"); - sprint(self, PRINT_HIGH, "«äåôµ° - Place detpack with 50 second timer\n"); - sprint(self, PRINT_HIGH, "«äåô²µµ - Place detpack with 255 second timer\n"); -}; - -void () Help_ShowMedic = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Combat Medic:\n"); - sprint(self, PRINT_HIGH, "± - Equip Super Nailgun\n"); - sprint(self, PRINT_HIGH, "² - Equip Super Shotgun\n"); - sprint(self, PRINT_HIGH, "³ - Equip Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Medikit\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw Concussion Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); -}; - -void () Help_ShowHWGuy = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Heavy Weapons Guy:\n"); - sprint(self, PRINT_HIGH, "± - Equip Assault Cannon\n"); - sprint(self, PRINT_HIGH, "² - Equip Super Shotgun\n"); - sprint(self, PRINT_HIGH, "³ - Equip Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Axe\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw Mirv Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); -}; - -void () Help_ShowPyro = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Pyro:\n"); - sprint(self, PRINT_HIGH, "± - Equip Flamethrower\n"); - sprint(self, PRINT_HIGH, "² - Equip Incendiary Cannon\n"); - sprint(self, PRINT_HIGH, "³ - Equip Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Axe\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw Napalm Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); -}; - -void () Help_ShowSpy = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Spy:\n"); - sprint(self, PRINT_HIGH, "± - Equip Tranquiliser Gun\n"); - sprint(self, PRINT_HIGH, "² - Equip Super Shotgun\n"); - sprint(self, PRINT_HIGH, "³ - Equip Nailgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Knife\n"); - sprint(self, PRINT_HIGH, "µ - Disguise menu\n"); - sprint(self, PRINT_HIGH, "å - Silently feign death\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw Gas Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); - sprint(self, PRINT_HIGH, "\nClass aliases for Spy:\n"); - sprint(self, PRINT_HIGH, "æåéçî - Feign death\n"); - sprint(self, PRINT_HIGH, "óæåéçî - Silently feign death\n"); -}; - -void () Help_ShowEngineer = { - sprint(self, PRINT_HIGH, "\nDefault bindings for Engineer:\n"); - sprint(self, PRINT_HIGH, "± - Equip Railgun\n"); - sprint(self, PRINT_HIGH, "² - Equip Super Shotgun\n"); - sprint(self, PRINT_HIGH, "´ - Equip Spanner\n"); - sprint(self, PRINT_HIGH, "µ - Build/destroy menu\n"); - sprint(self, PRINT_HIGH, "æ - Prime/throw EMP Grenade\n"); - sprint(self, PRINT_HIGH, "íïõóå² - Prime/throw Hand Grenade\n"); - sprint(self, PRINT_HIGH, "\nClass aliases for Engineer:\n"); - sprint(self, PRINT_HIGH, "äåôäéóðåîóåò - Detonate Dispenser\n"); - sprint(self, PRINT_HIGH, "äåôóåîôòù - Detonate Sentry Gun\n"); -}; diff --git a/menu.qc b/menu.qc deleted file mode 100644 index e869fc138..000000000 --- a/menu.qc +++ /dev/null @@ -1,997 +0,0 @@ -//====================================================== -// This file handles all menu functions and displays. -//====================================================== - -void (entity pe_player, float pf_class) CF_Spy_ChangeSkin; -void (entity pe_player, float pf_team_no) CF_Spy_ChangeColor; -void (float issilent) CF_Spy_FeignDeath; -void () CF_Spy_Invisible; -void () CF_Spy_DisguiseStop; - -float (float pf_team_no, float pf_class) CF_GetClassRestriction; -float (float pf_team_no, float pf_class) CF_GetClassPlayers; -float (float pf_team_no, float pf_class) CF_ClassIsRestricted; - -void (entity spy) Spy_RemoveDisguise; - -void (entity eng, string bld) DestroyBuilding; - -void (float objtobuild) TeamFortress_Build; - -void () lvl1_sentry_stand; -void () lvl2_sentry_stand; -void () lvl3_sentry_stand; - -float (float tno) TeamFortress_TeamSet; -float (float tno) TeamFortress_TeamGetColor; -float () TeamFortress_TeamPutPlayerInTeam; -float (float tno) TeamFortress_TeamIsCivilian; -float (float tno) TeamFortress_TeamGetNoPlayers; -float () TeamFortress_GetNoPlayers; - -float (float pc) IsLegalClass; -void (float inp) TeamFortress_ChangeClass; -void (entity p) TeamFortress_SetSkin; - -void (float timer) TeamFortress_SetDetpack; -void () TeamFortress_DetpackStop; - -void (float type) TeamFortress_DropAmmo; -void (entity disp) Engineer_Dispenser_InsertAmmo; -void (entity disp) Engineer_Dispenser_InsertArmor; -void (entity disp) Engineer_Dispenser_Repair; -void (entity disp) Engineer_SentryGun_InsertAmmo; -void (entity disp) Engineer_SentryGun_Upgrade; -void (entity disp) Engineer_SentryGun_Repair; -void () Menu_Engineer_Cancel; -void () TeamFortress_EngineerBuildStop; - -void (entity targ, entity inflictor, entity attacker, float damage, - float T_flags, float T_AttackType) TF_T_Damage; - -void (entity pl) W_SetCurrentAmmo; - -void (entity p) bound_other_ammo; -float (float v) anglemod; - -void (float tno, entity ignore, string st) teamsprint; - -void (float update) Menu_Team; -void (float update) Menu_Class; -string (float pc, float tno) TeamFortress_ClassGetNoPlayersString; -void () Menu_Drop; -void () PlayerObserverMode; -void () Menu_Scout; -void (entity pe_player) Menu_Spy; -void () Menu_Spy_Skin; -void () Menu_Spy_Color; - -void (float inp) Menu_Scout_Input; -void (float inp) Menu_Spy_Input; -void (float inp) Menu_Spy_Skin_Input; -void (float inp) Menu_Spy_Color_Input; -void () CF_Spy_DisguiseLast; - -void () Menu_Demoman; -void () Menu_Demoman_Cancel; - -void (float inp) Menu_Demoman_Input; -void (float inp) Menu_Demoman_Cancel_Input; - -void (entity player) Menu_Engineer; -void () Menu_Engineer_Update; -void () Menu_EngineerFix_Dispenser; -void () Menu_EngineerFix_SentryGun; - -void (float inp) Menu_Engineer_Input; -void (float inp) Menu_EngineerFix_Dispenser_Input; -void (float inp) Menu_EngineerFix_SentryGun_Input; - -void () Menu_Dispenser; -void (float inp) Menu_Dispenser_Input; - -void (entity pl) Menu_Close = -{ - pl.menu_input = nil; - Status_Print(pl, ""); -}; - -void (float inp) Menu_Input = -{ - local f_void_float tmp = self.menu_input; - self.menu_input = nil; - self.impulse = 0; - tmp(inp); - Status_Print(self, ""); -}; - -void (float inp) Menu_Team_Input = { - if (self.classname == "observer") - return; - - if (inp == 0) { - return; - } - - if (inp == 5) { - TeamFortress_TeamPutPlayerInTeam(); - } else if ((inp <= number_of_teams) && (inp > 0)) { - TeamFortress_TeamSet(inp); - } else if ((number_of_teams == 0) && (inp <= 4)) { - TeamFortress_TeamSet(inp); - } else { - Menu_Team(0); - return; - } - - if ((self.playerclass == 0) && (self.lives != 0)) { - Menu_Class(0); - } -}; - -void () Menu_Team_Update = { - if (self.owner.menu_input == Menu_Team_Input) { - self.nextthink = time + 0.5; - self = self.owner; - Menu_Team(1); - } else { - self.owner.has_menutimer = 0; - dremove(self); - } -}; - -string (float pf_team_no, string ps_team) Menu_Team_TeamString = { - local string s_string = ""; - local float f_gap = 2; - local float f_players = TeamFortress_TeamGetNoPlayers(pf_team_no); - - if (number_of_teams >= pf_team_no) { - s_string = strpadl(ftos(f_players), f_gap); - if (f_players < 10) { - s_string = strpadl(s_string, (1 + f_gap)); - if (f_players == 1) - s_string = strcat(s_string, " player \n"); - else - s_string = strcat(s_string, " players\n"); - } else - s_string = strcat(s_string, " players\n"); - s_string = strcat(ps_team, s_string); - } - - return strzone(s_string); -}; - -void (float update) Menu_Team = { - local entity timer; - - if (self.classname == "observer") - Status_Menu(self, Menu_Team_Input, ""); - - // allow toggling team menu using any method to show it - if (!update && self.menu_input == Menu_Team_Input) { - Menu_Input(0); - return; - } - - if ((toggleflags & TFLAG_AUTOTEAM) && teamplay) { - if (TeamFortress_TeamPutPlayerInTeam()) - return; - } - - // prepare team strings - local string s_select = "Select team:\n\n"; - local string s_blue = "“‘ Blue team "; - local string s_red = "”‘ Red team "; - local string s_yellow = "•‘ Yellow team"; - local string s_green = "–‘ Green team "; - local string s_auto = "—‘ Auto-assign team"; - - // put together team strings - s_blue = Menu_Team_TeamString(1, s_blue); - s_red = Menu_Team_TeamString(2, s_red); - s_yellow = Menu_Team_TeamString(3, s_yellow); - s_green = Menu_Team_TeamString(4, s_green); - s_auto = strpadr(s_auto, (strlen(s_blue) - 1)); - - // don't show auto team if already assigned a team - if (self.team_no) - s_auto = ""; - - // update menu every 0.5 seconds - if (!self.has_menutimer) { - self.has_menutimer = 1; - timer = spawn(); - timer.classname = "menu_timer"; - timer.owner = self; - timer.think = Menu_Team_Update; - timer.nextthink = time + 0.5; - } - - Status_Menu(self, Menu_Team_Input, s_select, s_blue, s_red, s_yellow, s_green, "\n", s_auto); - - strunzone(s_blue); strunzone(s_red); strunzone(s_yellow); strunzone(s_green); -}; - -void (float inp) Menu_Class_Input = { - if (!inp) - return; - - // keep showing menu if class is invalid - if (inp > 10 || !IsLegalClass(inp) || CF_ClassIsRestricted(self.team_no, inp)) - Menu_Class(0); - - // don't try to change class if class is forbidden - if (!IsLegalClass(inp) || CF_GetClassRestriction(self.team_no, inp) == -1) - return; - - // close menu if selected class is current class - if (self.playerclass == inp || (inp == 10 && (self.tfstate & TFSTATE_RANDOMPC))) - Menu_Close(self); - - TeamFortress_ChangeClass(inp); -}; - -void () Menu_Class_Update = { - if (self.owner.menu_input == Menu_Class_Input) { - self.nextthink = time + 0.5; - self = self.owner; - Menu_Class(1); - } else { - self.owner.has_menutimer = 0; - dremove(self); - } -}; - -string (float pf_class, string ps_class) Menu_Class_ClassString = { - local string s_string; - local float f_gap = 5; - local float f_max = CF_GetClassRestriction(self.team_no, pf_class); - local float f_players = CF_GetClassPlayers(self.team_no, pf_class); - - if (IsLegalClass(pf_class) && f_max >= 0) { - if (f_players < 10) - s_string = strpadl(ftos(f_players), (1 + f_gap)); - else - s_string = strpadl(ftos(f_players), f_gap); - s_string = strcat(s_string, " / "); - if (f_max < 10 && TeamFortress_TeamGetNoPlayers(self.team_no) >= 10) - s_string = strpadr(s_string, (5 + f_gap)); - s_string = strcat(s_string, ftos(f_max)); - s_string = strcat(s_string, "\n"); - s_string = strcat(ps_class, s_string); - } else { - if (TeamFortress_TeamGetNoPlayers(self.team_no) >= 10) - s_string = strpadr(ps_class, (12 + f_gap - 3)); - else - s_string = strpadr(ps_class, (12 + f_gap - 3)); - s_string = strcat(s_string, "disabled\n"); - } - - return strzone(s_string); -}; - -void (float update) Menu_Class = { - local entity timer; - - // allow toggling team menu using any method to show it - if (!update && self.menu_input == Menu_Class_Input) { - Menu_Input(0); - return; - } - - // print map specific class menu - local entity e_info = find(world, classname, "info_tfdetect"); - if (e_info) { - if (self.team_no == 1) { - if (e_info.noise1 != string_null) { - Status_Menu(self, Menu_Class_Input, e_info.noise1); - return; - } - } else if (self.team_no == 2) { - if (e_info.noise2 != string_null) { - Status_Menu(self, Menu_Class_Input, e_info.noise2); - return; - } - } else if (self.team_no == 3) { - if (e_info.noise3 != string_null) { - Status_Menu(self, Menu_Class_Input, e_info.noise3); - return; - } - } else if (self.team_no == 4) { - if (e_info.noise4 != string_null) { - Status_Menu(self, Menu_Class_Input, e_info.noise4); - return; - } - } - } - - // prepare class strings - local string s_select = "Select class:\n\n"; - local string s_scout = "“‘ Scout "; - local string s_sniper = "”‘ Sniper "; - local string s_soldier = "•‘ Soldier "; - local string s_demoman = "–‘ Demoman "; - local string s_medic = "—‘ Medic "; - local string s_hwguy = "˜‘ HWGuy "; - local string s_pyro = "™‘ Pyro "; - local string s_spy = "š‘ Spy "; - local string s_engineer = "›‘ Engineer"; - local string s_randompc = "’‘ RandomPC"; - - // put together class strings - all strings are strzoned - s_scout = Menu_Class_ClassString(PC_SCOUT, s_scout); - s_sniper = Menu_Class_ClassString(PC_SNIPER, s_sniper); - s_soldier = Menu_Class_ClassString(PC_SOLDIER, s_soldier); - s_demoman = Menu_Class_ClassString(PC_DEMOMAN, s_demoman); - s_medic = Menu_Class_ClassString(PC_MEDIC, s_medic); - s_hwguy = Menu_Class_ClassString(PC_HVYWEAP, s_hwguy); - s_pyro = Menu_Class_ClassString(PC_PYRO, s_pyro); - s_spy = Menu_Class_ClassString(PC_SPY, s_spy); - s_engineer = Menu_Class_ClassString(PC_ENGINEER, s_engineer); - s_randompc = Menu_Class_ClassString(PC_RANDOM, s_randompc); - - // update menu every 0.5 seconds - if (!self.has_menutimer) { - self.has_menutimer = 1; - timer = spawn(); - timer.classname = "menu_timer"; - timer.owner = self; - timer.think = Menu_Class_Update; - timer.nextthink = time + 0.5; - } - - // print out class menu - self.menu_input = nil; - if (TeamFortress_TeamIsCivilian(self.team_no)) - Status_Print(self, "Your team can only be civilians\n"); - else - Status_Menu(self, Menu_Class_Input, s_select, s_scout, s_sniper, s_soldier, s_demoman, s_medic, s_hwguy, s_pyro, s_spy, s_engineer, s_randompc); - - strunzone(s_scout); strunzone(s_sniper); strunzone(s_soldier); strunzone(s_demoman); strunzone(s_medic); - strunzone(s_hwguy); strunzone(s_pyro); strunzone(s_spy); strunzone(s_engineer); strunzone(s_randompc); -}; - -void (float inp) Menu_Drop_Input = { - if ((inp > 0) && (inp < 5)) { - TeamFortress_DropAmmo(inp); - Menu_Drop(); - } -}; - -void () Menu_Drop = { - local string s_drop; - local string s_shells = "“‘ Shells \n"; - local string s_nails = "”‘ Nails \n"; - local string s_rockets = "•‘ Rockets \n"; - local string s_cells = "–‘ Cells \n"; - local string s_nothing = "\n—‘ Nothing "; - - if (!(self.ammo_shells + self.ammo_nails + self.ammo_rockets + self.ammo_cells)) { - sprint(self, PRINT_HIGH, "Not enough ammo\n"); - return; - } - - if (self.ammo_shells < DROP_SHELLS) - s_shells = "\n"; - if (self.ammo_nails < DROP_NAILS) - s_nails = "\n"; - if (self.ammo_rockets < DROP_ROCKETS) - s_rockets = "\n"; - if (self.ammo_cells < DROP_CELLS) - s_cells = "\n"; - - if (self.playerclass == PC_ENGINEER) { - if ((self.ammo_shells < DROP_SHELLS) && ((self.ammo_cells / AMMO_COST_SHELLS) > (DROP_SHELLS - self.ammo_shells))) - s_shells = "“‘ Shells (make) \n"; - if ((self.ammo_nails < DROP_NAILS) && ((self.ammo_cells / AMMO_COST_NAILS) > (DROP_NAILS - self.ammo_nails))) - s_nails = "”‘ Nails (make) \n"; - if ((self.ammo_rockets < DROP_ROCKETS) && ((self.ammo_cells / AMMO_COST_ROCKETS) > (DROP_ROCKETS - self.ammo_rockets))) - s_rockets = "•‘ Rockets (make)\n"; - if (self.ammo_cells < DROP_CELLS) - s_cells = "\n"; - } - - if (s_shells == "\n" && s_nails == "\n" && s_rockets == "\n" && s_cells == "\n") - return; - - self.menu_input = nil; - if (self.playerclass == PC_ENGINEER) - s_drop = "Drop or make:\n\n"; - else - s_drop = "Drop:\n\n"; - Status_Menu(self, Menu_Drop_Input, s_drop, s_shells, s_nails, s_rockets, s_cells, s_nothing); -}; - -void (float inp) Menu_Scout_Input = { - if (inp == 1) - self.impulse = TF_SCAN; - else if (inp == 2) - self.impulse = TF_SCAN_ENEMY; - else if (inp == 3) - self.impulse = TF_SCAN_FRIENDLY; - else if (inp == 4) - self.impulse = TF_SCAN_SOUND; - else - self.impulse = 0; -}; - -void () Menu_Scout = { - local string s_action = "Scanner settings:\n\n"; - local string s_scan, s_scane, s_scanf, s_scansound; - local string s_nothing = "\n—‘ Nothing \n\n"; - - if (!self.ScannerOn) - s_scan = "“‘ Turn Scanner on \n"; - else - s_scan = "“‘ Turn Scanner off \n"; - - if (self.tf_items_flags & NIT_SCANNER_ENEMY) - s_scane = "”‘ Do not scan for enemies \n"; - else - s_scane = "”‘ Scan for enemies \n"; - - - if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) - s_scanf = "•‘ Do not scan for friendlies\n"; - else - s_scanf = "•‘ Scan for friendlies \n"; - - if (self.tf_items_flags & 4) - s_scansound = "–‘ Turn off scan sound \n"; - else - s_scansound = "–‘ Turn on scan sound \n"; - - Status_Menu(self, Menu_Scout_Input, s_action, s_scan, s_scane, s_scanf, s_scansound, s_nothing); -}; - -void (float inp) Menu_Spy_Input = { - if ((inp == 1) || (inp == 2)) { - if (self.effects & (EF_DIMLIGHT | EF_BRIGHTLIGHT)) { - sprint(self, PRINT_HIGH, "You cannot go undercover while glowing\n"); - return; - } - if (self.is_unabletospy) { - sprint(self, PRINT_HIGH, "You cannot go undercover right now\n"); - return; - } - } - if (inp == 1) { - if (invis_only) - CF_Spy_Invisible(); - else if (self.is_undercover == 2) - CF_Spy_DisguiseStop(); - else - Menu_Spy_Skin(); - } else if (inp == 2 && !invis_only) { - CF_Spy_DisguiseLast(); - } else if (inp == 3) { - CF_Spy_FeignDeath(1); - if (self.is_feigning) { - Menu_Spy(self); - } - } else if (inp == 4) { - Spy_RemoveDisguise(self); - } -}; - -void (entity pe_player) Menu_Spy = { - local string s_action = "Action:\n\n"; - local string s_skin = "“‘ Disguise \n"; - local string s_last = "”‘ Last disguise \n"; - local string s_feign, s_reset; - local string s_nothing = "\n—‘ Nothing "; - - if (pe_player.effects & (EF_DIMLIGHT | EF_BRIGHTLIGHT) || pe_player.is_unabletospy == 1) { - return; - } - - if (invis_only) { - if (pe_player.is_undercover == 1) - s_skin = "“‘ Become visible \n"; - else if (pe_player.is_undercover == 2) - s_skin = "“‘ Stop going invisible \n"; - else - s_skin = "“‘ Go invisible \n"; - } else if (pe_player.is_undercover == 2) - s_skin = "“‘ Stop disguising \n"; - - if ((!pe_player.last_skin && !pe_player.last_team) || invis_only) - s_last = "\n"; - - if (pe_player.is_feigning) - s_feign = "•‘ Stop feigning \n"; - else - s_feign = "•‘ Start feigning (silent)\n"; - - if (pe_player.undercover_team && pe_player.undercover_skin) - s_reset = "–‘ Reset disguise \n"; - else if (pe_player.undercover_team) - s_reset = "–‘ Reset color \n"; - else if (pe_player.undercover_skin) - s_reset = "–‘ Reset skin \n"; - else - s_reset = "\n"; - - Status_Menu(pe_player, Menu_Spy_Input, s_action, s_skin, s_last, s_feign, s_reset, s_nothing); -}; - -void (float inp) Menu_Spy_Skin_Input = { - if (inp == 10) - return; - - if (self.effects & (EF_DIMLIGHT | EF_BRIGHTLIGHT)) { - sprint(self, PRINT_MEDIUM, "You cannot disguise while glowing\n"); - return; - } - - if (self.is_unabletospy) { - sprint(self, PRINT_HIGH, "You cannot go undercover right now\n"); - return; - } - - if (self.skin != inp) - CF_Spy_ChangeSkin(self, inp); - - if (number_of_teams > 2) - Menu_Spy_Color(); - else if (self.team_no == 1) - CF_Spy_ChangeColor(self, 2); - else - CF_Spy_ChangeColor(self, 1); - -}; - -void () Menu_Spy_Skin = { - if (self.is_unabletospy == 1) - return; - - local string s_disguise = "Disguise as enemy:\n\n"; - local string s_scout = "“‘ Scout \n"; - local string s_sniper = "”‘ Sniper \n"; - local string s_soldier = "•‘ Soldier \n"; - local string s_demoman = "–‘ Demoman \n"; - local string s_medic = "—‘ Medic \n"; - local string s_hwguy = "˜‘ HWGuy \n"; - local string s_pyro = "™‘ Pyro \n"; - local string s_spy = "š‘ Spy \n"; - local string s_engineer = "›‘ Engineer \n"; - local string s_nothing = "\n’‘ Nothing \n"; - - Status_Menu(self, Menu_Spy_Skin_Input, s_disguise, s_scout, s_sniper, s_soldier, s_demoman, s_medic, s_hwguy, s_pyro, s_spy, s_engineer, s_nothing); -}; - -void (float inp) Menu_Spy_Color_Input = { - local float color = stof(infokey(self, "bottomcolor")); - - if (inp == 1 && color == 13) - Menu_Spy_Color(); - else if (inp == 2 && color == 4) - Menu_Spy_Color(); - else if (inp == 3 && color == 12) - Menu_Spy_Color(); - else if (inp == 4 && color == 11) - Menu_Spy_Color(); - else if (inp > 0 && inp <= number_of_teams) - CF_Spy_ChangeColor(self, inp); -}; - -void () Menu_Spy_Color = { - local float color = stof(infokey(self, "bottomcolor")); - local string s_disguise = "Disguise as:\n\n"; - local string s_blue = "“‘ Blue team \n"; - local string s_red = "”‘ Red team \n"; - local string s_yellow = "•‘ Yellow team\n"; - local string s_green = "–‘ Green team \n"; - local string s_nothing = "\n—‘ Nothing "; - - if (number_of_teams == 0) { - sprint(self, PRINT_HIGH, "No color changing allowed in deathmatch\n"); - return; - } - - // don't display your own team - if (color == 13) - s_blue = "\n"; - else if (color == 4) - s_red = "\n"; - else if (color == 12) - s_yellow = "\n"; - else if (color == 11) - s_green = "\n"; - - self.menu_input = nil; - if (number_of_teams == 1) - sprint(self, PRINT_HIGH, "There is no other team\n"); - else if (number_of_teams == 2) - Status_Menu(self, Menu_Spy_Color_Input, s_disguise, s_blue, s_red, s_nothing, "\n\n"); - else if (number_of_teams == 3) - Status_Menu(self, Menu_Spy_Color_Input, s_disguise, s_blue, s_red, s_yellow, s_nothing, "\n"); - else - Status_Menu(self, Menu_Spy_Color_Input, s_disguise, s_blue, s_red, s_yellow, s_green, s_nothing); -}; - -void (float inp) Menu_Demoman_Input = { - if (inp == 1) - TeamFortress_SetDetpack(5); - else if (inp == 2) - TeamFortress_SetDetpack(20); - else if (inp == 3) - TeamFortress_SetDetpack(50); - else if (inp == 4) - TeamFortress_SetDetpack(255); -}; - -void () Menu_Demoman = { - local string s_detpack = "Set detpack for:\n\n"; - local string s_5 = "“‘ 5 seconds \n"; - local string s_20 = "”‘ 20 seconds \n"; - local string s_50 = "•‘ 50 seconds \n"; - local string s_255 = "–‘ 255 seconds\n"; - local string s_nothing = "\n—‘ Nothing "; - - Status_Menu(self, Menu_Demoman_Input, s_detpack, s_5, s_20, s_50, s_255, s_nothing); -} - -void (float inp) Menu_Demoman_Cancel_Input = { - if (inp == 1) - TeamFortress_DetpackStop(); - else - Menu_Demoman_Cancel(); -} - -void () Menu_Demoman_Cancel = { - local string s_detpack = "Setting detpack...\n\n"; - local string s_cancel = "“‘ Cancel!\n\n\n\n\n"; - - Status_Menu(self, Menu_Demoman_Cancel_Input, s_detpack, s_cancel); -} - -void (float inp) Menu_Engineer_Input = { - local float dismantle_sentrygun; - local float dismantle_dispenser; - local entity te; - - if (inp == 5) - return; - - if (self.is_building) { - Menu_Engineer(self); - return; - } - - dismantle_sentrygun = 0; - dismantle_dispenser = 0; - - if (inp == 1 && !self.has_sentry && self.ammo_cells >= 130) - TeamFortress_Build(2); - - if (inp == 2 && !self.has_dispenser && self.ammo_cells >= 100) - TeamFortress_Build(1); - - if (inp == 3 && self.has_sentry) { - te = findradius(self.origin, 100); - while (te) { - if (te.classname == "building_sentrygun") { - if (te.real_owner == self){ - sprint (self, PRINT_HIGH, "You dismantled the sentry gun and got 65 cells\n"); - self.ammo_cells = self.ammo_cells + 65; - dremove (te.trigger_field); - dremove (te); - self.has_sentry = 0; - dismantle_sentrygun = 1; - } - } - te = te.chain; - } - if (dismantle_sentrygun == 0) - DestroyBuilding(self, "building_sentrygun"); - } - - if (inp == 4 && self.has_dispenser) { - te = findradius(self.origin, 100); - while (te) { - if (te.classname == "building_dispenser") { - if (te.real_owner == self) { - sprint (self, PRINT_HIGH, "You dismantled the dispenser and got 50 cells\n"); - self.ammo_cells = self.ammo_cells + 50; - dremove (te); - self.has_dispenser = 0; - dismantle_dispenser = 1; - } - } - te = te.chain; - } - if (dismantle_dispenser == 0) - DestroyBuilding(self, "building_dispenser"); - } - -}; - -void (entity player) Menu_Engineer = { - local entity te, dist_checker; - local string s_action = "Action:\n\n"; - local string s_sentry = "\n"; - local string s_disp = "\n"; - local string s_dsentry = "\n"; - local string s_ddisp = "\n"; - local string s_nothing = "\n—‘ Nothing "; - - if (player.has_sentry) { - s_sentry = "\n"; - s_dsentry = "•‘ Destroy sentry gun \n"; - te = findradius(player.origin, 100); - while (te) { - if (te.classname == "building_sentrygun") { - if (te.real_owner == player) - s_dsentry = "•‘ Dismantle sentry gun\n"; - } - te = te.chain; - } - } else if (player.ammo_cells >= 130) { - s_sentry = "“‘ Build sentry gun \n"; - } - - if (player.has_dispenser) { - s_ddisp = "–‘ Destroy dispenser \n"; - te = findradius(player.origin, 100); - while (te) { - if (te.classname == "building_dispenser") { - if (te.real_owner == player) - s_ddisp = "–‘ Dismantle dispenser \n"; - } - te = te.chain; - } - } else if (player.ammo_cells >= 100) { - s_disp = "”‘ Build dispenser \n"; - } - - if ((player.has_dispenser || player.has_sentry) && !player.has_menutimer) { - player.has_menutimer = 1; - dist_checker = spawn(); - dist_checker.classname = "menu_timer"; - dist_checker.owner = player; - dist_checker.think = Menu_Engineer_Update; - dist_checker.nextthink = time + 0.3; - } - - Status_Menu(player, Menu_Engineer_Input, s_action, s_sentry, s_disp, s_dsentry, s_ddisp, s_nothing); -}; - -void () Menu_Engineer_Update = { - if (self.owner.menu_input == Menu_Engineer_Input) { - Menu_Engineer(self.owner); - self.nextthink = time + 0.3; - } else { - self.owner.has_menutimer = 0; - dremove(self); - } -}; - -void (float inp) Menu_EngineerFix_Dispenser_Input = { - if (self.classname != "player" || self.building == world) - return; - - if (inp == 1) { - Engineer_Dispenser_InsertAmmo(self.building); - } else if (inp == 2) { - Engineer_Dispenser_InsertArmor(self.building); - } else if (inp == 3 && old_spanner) { - Engineer_Dispenser_Repair(self.building); - } - - self.building = world; -}; - -void () Menu_EngineerFix_Dispenser = { - local string s_action = "Dispenser maintenance:\n\n"; - local string s_ammo, s_armor, s_repair; - local string s_nothing = "\n—‘ Nothing \n\n"; - - if ((self.ammo_shells > 0 && self.building.ammo_shells < 400) - || (self.ammo_nails > 0 && self.building.ammo_nails < 600) - || (self.ammo_rockets > 0 && self.building.ammo_rockets < 300) - || (self.ammo_cells > 0 && self.building.ammo_cells < 400)) - s_ammo = "“‘ Insert ammo \n"; - else - s_ammo = "\n"; - - if (self.armorvalue > 0 && self.building.armorvalue < 500) - s_armor = "”‘ Insert armor\n"; - else - s_armor = "\n"; - - if (old_spanner && self.building.health < self.building.max_health) - s_repair = "•‘ Repair \n"; - else - s_repair = "\n"; - - Status_Menu(self, Menu_EngineerFix_Dispenser_Input, s_action, s_ammo, s_armor, s_repair, s_nothing); -}; - -void (float inp) Menu_EngineerFix_SentryGun_Rotate_Input = { - if (inp == 1) { - sprint(self, PRINT_HIGH, "Rotating 45 degrees to the left...\n"); - self.building.waitmin = anglemod(self.building.waitmin + 45); - self.building.waitmax = anglemod(self.building.waitmax + 45); - } else if (inp == 2) { - sprint(self, PRINT_HIGH, "Rotating 180 degrees...\n"); - self.building.waitmin = anglemod(self.building.waitmin + 180); - self.building.waitmax = anglemod(self.building.waitmax + 180); - } else if (inp == 3) { - sprint(self, PRINT_HIGH, "Rotating 45 degrees to the right...\n"); - self.building.waitmin = anglemod(self.building.waitmin - 45); - self.building.waitmax = anglemod(self.building.waitmax - 45); - } -}; - -void () Menu_EngineerFix_SentryGun_Rotate = { - local string action = "Rotate sentry gun:\n\n"; - local string rotl = "“‘ 45 degrees left \n"; - local string rot180 = "”‘ 180 degrees \n"; - local string rotr = "•‘ 45 degrees right\n"; - local string nothing = "\n—‘ Nothing \n"; - - if (!self.building.real_owner.has_sentry || self.building.real_owner != self - || self.classname != "player" || self.building == world) - return; - - Status_Menu(self, Menu_EngineerFix_SentryGun_Rotate_Input, action, rotl, rot180, rotr, nothing); -}; - -void (float inp) Menu_EngineerFix_SentryGun_Input = { - if (!self.building.real_owner.has_sentry || self.building.real_owner != self - || self.classname != "player" || self.building == world) - return; - - if (inp == 1) { - Engineer_SentryGun_InsertAmmo(self.building); - } else if (inp == 2 && self.building.weapon < 3 && self.ammo_cells >= 130) { - Engineer_SentryGun_Upgrade(self.building); - } else if (inp == 3) { - Engineer_SentryGun_Repair(self.building); - } else if (inp == 4) { - Menu_EngineerFix_SentryGun_Rotate(); - } -}; - -void () Menu_EngineerFix_SentryGun = { - - // only show this menu if old_spanner setting is enabled, otherwise show rotate menu - if (!old_spanner) { - Menu_EngineerFix_SentryGun_Rotate(); - return; - } - - local string action = "Sentry gun maintenance:\n\n"; - local string putammo, upgrade, repair, rotate; - local string nothing = "\n—‘ Nothing "; - - if ((self.ammo_shells > 0 && self.building.ammo_shells < self.building.maxammo_shells) - || (self.ammo_rockets > 0 && self.building.weapon == 3 && self.building.ammo_rockets < self.building.maxammo_rockets)) - putammo = "“‘ Insert ammo\n"; - else - putammo = "\n"; - - if (self.building.weapon < 3 && self.ammo_cells >= 130) - upgrade = "”‘ Upgrade \n"; - else - upgrade = "\n"; - - if (self.building.health < self.building.max_health) - repair = "•‘ Repair \n"; - else - repair = "\n"; - - if (self.building.real_owner == self) - rotate = "–‘ Rotate \n"; - else - rotate = "\n"; - - Status_Menu(self, Menu_EngineerFix_SentryGun_Input, action, putammo, upgrade, repair, rotate, nothing); -}; - -void (float inp) Menu_Dispenser_Input = { - local float am; - local float empty; - - empty = FALSE; - if (inp == 1) { - if ((self.building.ammo_shells == 0) - && (self.building.ammo_nails == 0) - && (self.building.ammo_rockets == 0) - && (self.building.ammo_cells == 0)) { - empty = TRUE; - } else { - am = self.maxammo_shells - self.ammo_shells; - if (am > self.building.ammo_shells) - am = self.building.ammo_shells; - self.building.ammo_shells = self.building.ammo_shells - am; - self.ammo_shells = self.ammo_shells + am; - - am = self.maxammo_nails - self.ammo_nails; - if (am > self.building.ammo_nails) - am = self.building.ammo_nails; - self.building.ammo_nails = self.building.ammo_nails - am; - self.ammo_nails = self.ammo_nails + am; - - am = self.maxammo_rockets - self.ammo_rockets; - if (am > self.building.ammo_rockets) - am = self.building.ammo_rockets; - self.building.ammo_rockets = self.building.ammo_rockets - am; - self.ammo_rockets = self.ammo_rockets + am; - - am = self.maxammo_cells - self.ammo_cells; - if (am > self.building.ammo_cells) - am = self.building.ammo_cells; - self.building.ammo_cells = self.building.ammo_cells - am; - self.ammo_cells = self.ammo_cells + am; - } - } else if (inp == 2) { - if (self.building.armorvalue == 0) { - empty = TRUE; - } else { - am = self.maxarmor - self.armorvalue; - if (am > self.building.armorvalue) - am = self.building.armorvalue; - - if (self.armortype == 0) { - self.armortype = 0.3; - self.items = self.items | IT_ARMOR1; - } - self.building.armorvalue = self.building.armorvalue - am; - self.armorvalue = self.armorvalue + am; - } - } - if ((inp >= 1) && (inp <= 3)) { - if (empty) - sprint(self, PRINT_HIGH, "The dispenser is empty\n"); - - self.building = world; - self.building_wait = time + 0.5; - - bound_other_ammo(self); - if (self.armorvalue == 0) { - self.armortype = 0; - self.armorclass = 0; - self.items = - self.items - - (self.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); - } - W_SetCurrentAmmo(self); - } -}; - -void () Menu_Dispenser = { - local string s_action = "Use dispenser:\n\n"; - local string s_ammo, s_armor; - local string s_nothing = "\n—‘ Nothing \n\n"; - - if ((self.building.ammo_shells > 0 && self.ammo_shells < self.maxammo_shells) - || (self.building.ammo_nails > 0 && self.ammo_nails < self.maxammo_nails) - || (self.building.ammo_rockets > 0 && self.ammo_rockets < self.maxammo_rockets) - || (self.building.ammo_cells > 0 && self.ammo_cells < self.maxammo_cells)) - s_ammo = "“‘ Withdraw some ammo \n"; - else - s_ammo = "\n"; - - if (self.building.armorvalue > 0 && self.armorvalue < self.maxarmor) - s_armor = "”‘ Withdraw some armor\n"; - else - s_armor = "\n"; - - Status_Menu(self, Menu_Dispenser_Input, s_action, s_ammo, s_armor, s_nothing); -}; - -void (float inp) Menu_Engineer_Cancel_Input = { - if (inp == 1) - TeamFortress_EngineerBuildStop(); - else - Menu_Engineer_Cancel(); -} - -void () Menu_Engineer_Cancel = { - local string s_build = "Building...\n\n"; - local string s_cancel = "“‘ Cancel!\n\n\n\n\n"; - - Status_Menu(self, Menu_Engineer_Cancel_Input, s_build, s_cancel); -} diff --git a/menu/loadsave.qc b/menu/loadsave.qc new file mode 100644 index 000000000..d5ca15f0c --- /dev/null +++ b/menu/loadsave.qc @@ -0,0 +1,164 @@ +#ifndef LOADSAVE_QC +#define LOADSAVE_QC + +//I'm feeling lazy, so I'm going to only provide X slots, like quake's menu. +static string savenames[] = +{ + "a0", + "a1", + "a2", + "quick", + "s0", + "s1", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", +}; +#define NUMSAVESLOTS savenames.length + +/* +class mitem_savescreeny : mitem +{ + virtual void(vector pos) item_draw = + { + string s = sprintf("saves/s%g/screeny.png", selectedsaveslot); + if not(whichpack(s)) + if (drawgetimagesize(s) != '0 0 0') + ui.drawpic(pos, s, item_size, item_rgb, item_alpha, 0); + }; +}; +*/ +class mitem_saveoption : mitem_text +{ + string slot; + float mode; + + virtual void() mitem_saveoption = + { + if (mode) + item_flags |= IF_SELECTABLE; + }; + + virtual void(vector pos) item_draw = + { + //some sort of pulsing if its active. + if (item_flags & IF_KFOCUSED) + ui.drawfill(pos, item_size, '1 0 0', sin(cltime)*0.125+0.150, 0); + float w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); + ui.drawstring(pos + [(item_size_x-w)/2, 0], item_text, '1 1 0' * item_scale, menuitem_textcolour(this), item_alpha, 0); + }; + + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (!down) + return FALSE; + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) + { + if (item_flags & IF_KFOCUSED) + { + switch(mode) + { + case 0: + break; //can't load a slot which is empty. + case 1: + localcmd(sprintf("m_pop;load %s\n", slot)); + break; + case 2: + //FTE has a savegame_legacy command if you want compatibility with other engines. + localcmd(sprintf("m_pop;wait;echo \"%s\";save %s\n", _("Saving Game"), slot)); + //localcmd(sprintf("m_pop;wait;screenshot saves/s%g/screeny.png;echo \"%s\";save s%g\n", slot, _("Saving Game"), slot)); + break; + } + } + else + { + item_parent.item_focuschange(this, IF_KFOCUSED); + } + return TRUE; + } + return FALSE; + }; +}; + +class mitem_savepreview : mitem +{ + //assumption: the only selectable children in the parent are save options. + virtual void(vector pos) item_draw = + { + mitem_saveoption sel; + sel = (mitem_saveoption)item_parent.item_kactivechild; + if (sel) + { + string s = sprintf("saves/%s/screeny.tga", sel.slot); + if (drawgetimagesize(s) != '0 0 0') + ui.drawpic(pos, s, item_size, item_rgb, item_alpha, 0); + } + }; +}; + +static string(string savename) scansave = +{ + string l; + float f = fopen(sprintf("saves/%s/info.fsv", savename), FILE_READ); + if (f < 0) + f = fopen(sprintf("%s.sav", savename), FILE_READ); + if (f < 0) + return __NULL__; //weird + + fgets(f); //should be the version + l = fgets(f); //description + if (l) + l = strreplace("_", " ", l); + fclose(f); + return l; +}; + +void(mitem_desktop desktop, float mode) M_LoadSave = +{ + mitem_exmenu m = spawn(mitem_exmenu, item_text:"Load/Save", item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + string l; + float i; + float smode; + float pos = NUMSAVESLOTS*16/-2; + + mitem_pic banner = spawn(mitem_pic, item_text:((mode==2)?"gfx/p_save.lmp":"gfx/p_load.lmp"), item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(banner.item_size_x)*-0.5, pos-32], [(banner.item_size_x)*0.5, pos-8]); + + for (i = 0; i < NUMSAVESLOTS; i++) + { + l = scansave(savenames[i]); + smode = mode; + if (l=="") + { + l = "Empty Slot"; + if (mode==1) + smode = 0; + } + m.addm(spawn (mitem_saveoption, item_scale:16, slot:savenames[i], mode:smode, item_text:l), [-320, pos+i*16], [320, pos+(i+1)*16]); + } + + m.addm(spawn(mitem_savepreview), [-320, -240], [320, 240]); + addmenuback(m); +}; + +void(mitem_desktop desktop) M_Load = +{ + M_LoadSave(desktop, 1); +}; +void(mitem_desktop desktop) M_Save = +{ + if (!(isserver() || dp_workarounds)) + M_Main(desktop); //can't save when not connected. this should be rare, but can if you use the console or the main menu options are stale. + else + M_LoadSave(desktop, 2); +}; +#endif diff --git a/menu/main.qc b/menu/main.qc new file mode 100644 index 000000000..0a0dc5542 --- /dev/null +++ b/menu/main.qc @@ -0,0 +1,155 @@ +/* +The main / root menu. +Just a load of text with console commands attached. +Choice of buttons available is somewhat dynamic. + +There's also some generic kludge crap in here, like menu background tints +*/ + +/* +Adds a background tint to a (typically) exmenu parent. +In FTE, we use built-in stuff to give a sepia effect. +In DP, we just tint it black. +*/ +nonstatic void(mitem_frame m) addmenuback = +{ + if (iscachedpic("menutint")) //fte internal hacks! meh, admit it. its cool. + m.add(spawn (mitem_pic, item_text:"menutint", item_alpha:0.5), + RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [0, 0], [0, 0]); + else + m.add(spawn (mitem_fill, item_rgb:'0 0 0.01', item_alpha:0.5), + RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [0, 0], [0, 0]); +}; + +/* +helper functions to avoid blowing up in older clients. + +*/ +#define dp(dpc,qwc) (cvar_type(dpc)?dpc:qwc) +float(string cmd) assumetruecheckcommand = +{ + if (!checkextension("FTE_QC_CHECKCOMMAND")) + return TRUE; + return checkcommand(cmd); +}; +float(string cmd) assumefalsecheckcommand = +{ + if (!checkextension("FTE_QC_CHECKCOMMAND")) + return FALSE; + return checkcommand(cmd); +}; + + + +nonstatic void(mitem_desktop desktop) M_Main = +{ + local float y; + local mitem_exmenu m; + + //no dupes please. + m = (mitem_exmenu)desktop.findchildtext(_("Main Menu")); + if (m) + { + m.totop(); + return; + } + + //create a fullscreen frame + m = spawn(mitem_exmenu, item_text:_("Main Menu"), item_flags:IF_SELECTABLE); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + +// m.item_flags |= IF_NOKILL; +// m.adda(menuitempic_spawn ("gfx/qplaque.lmp", '32 144'), '16 4'); + + y = 7*-16/2; + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_main.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(160-banner.item_size_x)*0.5, y-32], [(160+banner.item_size_x)*0.5, y-8]); + + +//a macro, in a desperate attempt at readability +#define menuitemtext_cladd16(m,t,c,y) m.addm(spawn(mitem_text, item_text:t, item_command:c, item_scale:16, item_flags:IF_CENTERALIGN), [0, y], [160, y+16]) + +#ifdef CSQC + if (serverkey("constate") != "disconnected") {menuitemtext_cladd16(m, _("Return To Game"), "m_pop", y); y += 16;} + if (serverkey("constate") != "disconnected") {menuitemtext_cladd16(m, isserver?_("End Game"):_("Disconnect"),"m_pop;disconnect", y); y += 16;} else +#endif +#ifdef CSQC + if (checkextension("FTE_CSQC_SERVERBROWSER")) +#endif + {menuitemtext_cladd16(m, _("Join Server"), "m_pop;m_servers", y); y += 16;} + if (assumefalsecheckcommand("menu_demo")) {menuitemtext_cladd16(m, _("Demos"), "m_pop;menu_demo", y); y += 16;} + //if (assumetruecheckcommand("save") && (isserver()||dp_workarounds)) {menuitemtext_cladd16(m, _("Save"), "m_pop;m_save", y); y += 16;} + //if (assumetruecheckcommand("load")) {menuitemtext_cladd16(m, _("Load"), "m_pop;m_load", y); y += 16;} + //if (assumefalsecheckcommand("cef")) {menuitemtext_cladd16(m, _("Browser"), "m_pop;cef google.com", y); y += 16;} + //if (assumefalsecheckcommand("xmpp")) {menuitemtext_cladd16(m, _("Social"), "m_pop;xmpp", y); y += 16;} + //if (assumefalsecheckcommand("irc")) {menuitemtext_cladd16(m, _("IRC"), "m_pop;irc /info", y); y += 16;} + //if (assumefalsecheckcommand("menu_download")) {menuitemtext_cladd16(m, _("Updates+Packages"), "m_pop;menu_download", y); y += 16;} + if (assumefalsecheckcommand("qi")) {menuitemtext_cladd16(m, _("Quake Injector"), "m_pop;qi", y); y += 16;} + {menuitemtext_cladd16(m, _("Options"), "m_pop;m_options", y); y += 16;} + {menuitemtext_cladd16(m, _("Quit"), "m_pop;m_quit", y); y += 16;} + +#if 1//def CSQC + //spinny quad/pent, for the luls + //local string it = (random()<0.9)?"progs/quaddama.mdl":"progs/invulner.mdl"; + local string skin = "sol"; + local float r = random(), col = 13; //Blue + + if(r > 0.89) + skin = "sco"; + else if(r > 0.78) + skin = "sni"; + else if(r > 0.67) + skin = "dem"; + else if(r > 0.56) + skin = "med"; + else if(r > 0.45) + skin = "hwg"; + else if(r > 0.34) + skin = "pyr"; + else if(r > 0.23) + skin = "spy"; + else if(r > 0.12) + skin = "eng"; + + //skin = "player_red"; + + r = random(); + if(r > 0.95) { + col = 11; //Green + skin = strcat("gren_", skin); + } else if(r > 0.9) { + col = 12; //Yellow + skin = strcat("yell_", skin); + } else if(r > 0.45) { + col = 4; //Red + skin = strcat("red_", skin); + } else { + skin = strcat("blue_", skin); + } + + //m.add(spawn (mitem_spinnymodel, item_text: it), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]); + m.add(spawn (mitem_spinnymodel, + item_text: "progs/player.mdl", + firstframe:12, + framecount:5, + shootframe:119, + shootframes:6, + dontrotate:1, + startangle:'0 155 0', + customskin:skin, + rotatespeed:10, + topcolour:col, + bottomcolour:col + ), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-200, 16*-16/2], [-40, 16*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(-160-plaque.item_size_x)*0.5, 12*-16/2], [(-160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + + addmenuback(m); +}; diff --git a/menu/menu.src b/menu/menu.src new file mode 100644 index 000000000..c5b8dc78c --- /dev/null +++ b/menu/menu.src @@ -0,0 +1,138 @@ +#pragma target fte_5768 +#pragma optimise 3 +#pragma flag enable subscope + +#pragma progs_dat "../menu.dat" + +#define MENU //select the module + +#includelist +../share/fteextensions.qc //also sets up system defs +../share/debug.qc + +../menusys/mitems.qc //root item type +../menusys/mitems_common.qc //basic types +../menusys/mitem_desktop.qc //other sort of root item +../menusys/mitem_exmenu.qc //fullscreen/exclusive menus +../menusys/mitem_edittext.qc //simple text editor +../menusys/mitem_tabs.qc //tabs +../menusys/mitem_colours.qc //colour picker +../menusys/mitem_checkbox.qc //checkbox (boolean thingies) +../menusys/mitem_slider.qc //scrollbars +../menusys/mitem_combo.qc //multiple-choice thingies +../menusys/mitem_bind.qc //key binding thingie +../menusys/mitem_spinnymodel.qc //menu art +#endlist + +//might as well put this here. + +void(mitem_desktop desktop) M_Pop = +{ + mitem it = desktop.item_kactivechild; + if (it) + it.item_remove(); +}; + +//define the commands. +//cmd argments are: Name, Function, Sourcefile(may be empty) +#define concommandslist \ + cmd("m_main", M_Main, main.qc) \ + cmd("m_pop", M_Pop, ) \ + cmd("m_options", M_Options, options.qc) \ + cmd("m_keys", M_Options_Keys, options_keys.qc) \ + cmd("m_basicopts", M_Options_Basic, options_basic.qc) \ + cmd("m_video", M_Options_Video, options_video.qc) \ + cmd("m_effects", M_Options_Effects, options_effects.qc) \ + cmd("m_audio", M_Options_Audio, options_audio.qc) \ + cmd("m_load", M_Load, loadsave.qc) \ + cmd("m_save", M_Save, ) \ + cmd("m_quit", M_Quit, quit.qc) \ + cmd("m_servers", M_Servers, servers.qc) \ + cmd("m_reset", M_Reset, ) + + +#if 0 +#append concommandslist cmd("m_servers", M_Servers, servers.qc) +#define serverbrowser "m_servers" +#else +#define serverbrowser "menu_servers" +#endif + +//make sure all the right files are included +#define cmd(n,fnc,inc) inc +#includelist + concommandslist +#endlist +#undef cmd + +mitem_desktop desktop; +void() m_shutdown = {}; +void(vector screensize) m_draw = {items_draw(desktop);}; +void(float scan, float chr) m_keydown = {items_keypress(desktop, scan, chr, TRUE);}; +void(float scan, float chr) m_keyup = {items_keypress(desktop, scan, chr, FALSE);}; +void(float mode) m_toggle +{ //mode is stupid. 1=enable,0=disable,-1=actually toggle. + if (mode < 0) + mode = !desktop.item_kactivechild; + if (mode) + M_Main(desktop); + else while(desktop.item_kactivechild) + { + mitem it = desktop.item_kactivechild; + if (it.item_flags & IF_NOKILL) + break; + it.item_remove(); + } + + items_updategrabs(TRUE); +}; + +var float autocvar_dp_workarounds_allow = TRUE; +var float autocvar_dp_workarounds_force = FALSE; +void() m_init = +{ + desktop = spawn(mitem_desktop); + + //register the console commands via the alias command. +#define cmd(n,f) localcmd("alias " n " \"menu_cmd " n " $*\"\n"); + concommandslist +#undef cmd + + //work around some dp differences/bugs. + //this check identifies one significant bug in DP. + //if anyone actually cares to fix DP, then there is no reason they cannot do so by just removing DP_QC_RENDERSCENE and then fixing anything else that arises. + if (checkextension("DP_QC_RENDER_SCENE") && !checkextension("DP_CON_SET")) + dp_workarounds = autocvar(dp_workarounds_allow, TRUE); + if (autocvar(dp_workarounds_force, FALSE)) + dp_workarounds = TRUE; + + if (dp_workarounds) + print("^1WORKING AROUND DP BUGS\n"); + + //for compat with DP, 'none' is the default cursor in menuqc. + //naturally this is not ideal. + if (checkextension("FTE_QC_HARDWARECURSORS")) + setcursormode(TRUE, ""); + else + print("No hardware cursors\n"); + + if (clientstate() == 1) //disconnected==1, supposedly + m_toggle(1); +}; +void(string cstr) GameCommand = +{ + tokenize(cstr); + string cmd = argv(0); + + switch(cmd) + { +//switch on the known commands. +#define cmd(n,f) case n: f(desktop); break; + concommandslist +#undef cmd + default: + print("unknown command ", cmd, "\n"); + break; + } + items_updategrabs(TRUE); +}; diff --git a/menu/options.qc b/menu/options.qc new file mode 100644 index 000000000..132fb1756 --- /dev/null +++ b/menu/options.qc @@ -0,0 +1,107 @@ +/*************************************************************************** +Options menu. +just a simple list. +*/ +nonstatic void(mitem_desktop desktop) M_Options = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + +/* //center the actual items + pos = (16/-2)*(9); + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.addm(banner, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); +*/ + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-80-banner.item_size_x*0.5, -h-32], [-80+banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160-160, -h], [0+160, h*2]); + + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + pos = 0; + + + + //and show the options. + /* if (assumefalsecheckcommand("fps_preset")) */ + /* {fr.add(spawn(mitem_text, item_text:"Graphical Presets", item_command:"m_pop;m_preset", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} */ + fr.add(spawn(mitem_text, item_text:"Basic Setup", item_command:"m_pop;m_basicopts", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Audio", item_command:"m_pop;m_audio", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Video", item_command:"m_pop;m_video", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Effects", item_command:"m_pop;m_effects", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Controls", item_command:"m_pop;m_keys", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + if (assumefalsecheckcommand("cfg_save")) + {fr.add(spawn(mitem_text, item_text:"Save Settings", item_command:"cfg_save", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} + if (assumefalsecheckcommand("cvarreset")) + {fr.add(spawn(mitem_text, item_text:"Reset to Defaults", item_command:"m_reset", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} + + //random art for style +#if 1//def CSQC + //m.addm(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), [0, 12*-16/2], [160, 12*16/2]); + m.add(spawn ( + mitem_spinnymodel, + item_text: "progs/tf_stan.mdl", + dontrotate:0, + startangle:'0 192 0', + firstframe:0, + framecount:1, + fov:40 + ), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [64, 12*-16/2], [160, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + addmenuback(m); +}; + + + +static void(mitem_desktop desktop, string question, string affirmitive, string affirmitiveaction, string negative, string negativeaction) M_SimplePrompt = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:""); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_exclusive = m; + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //center the actual items + pos = (16/-2)*(2); + + //draw title art above the options +// mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); +// m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); + + m.add(spawn(mitem_text, item_text:question, item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos-(8+16)], [0, pos-8]); pos += 16; + + m.add(spawn(mitem_text, item_text:affirmitive, item_command:affirmitiveaction, item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:negative, item_command:negativeaction, item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + + //random art for style +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [0, 12*-16/2], [160, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + addmenuback(m); +}; + +nonstatic void(mitem_desktop desktop) M_Reset = +{ + M_SimplePrompt(desktop, "Really Reset All Settings?", "Yes!", "m_pop;cvarreset *;exec default.cfg", "NOOOO! MY PRECIOUS!!!", "m_pop"); +}; diff --git a/menu/options_audio.qc b/menu/options_audio.qc new file mode 100644 index 000000000..c739e5214 --- /dev/null +++ b/menu/options_audio.qc @@ -0,0 +1,76 @@ +nonstatic void(mitem_desktop desktop) M_Options_Audio = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Audio Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [banner.item_size_x*-0.5, -h-32], [banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + pos = 0; + + //add the options + fr.add(spawn(mitem_text, item_text:_("Restart Sound"), item_command:"snd_restart", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; + pos += 8; + fr.add(menuitemcombo_spawn(_("Sound Device"), "s_device", '280 8', cvar_string("_s_device_opts")), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Master Volume"), "volume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Jump Volume"), "fo_jumpvolume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Reload Volume"), "fo_reloadvolume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Ambient Volume"),"ambient_level", '0 0.5 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Self Volume"), "s_localvolume", '0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Music Volume"), "bgmvolume", '0 0.5 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Channels"), "s_numspeakers", '1 6 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Audio Quality"), "s_khz", '280 8', _( + "11025 \"11025hz (vanilla quake)\" " + "22050 \"22050hz\" " + "44100 \"44100hz (cd quality)\" " + "48000 \"48000hz (dvd quality)\" " + "96000 \"96000hz\" " + "192000 \"192000hz\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Doppler"), "s_doppler", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("8bit audio"), "s_loadas8bit", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Swap Speakers"), "s_swapstereo", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Latency"), "s_mixahead", '0.1 0.3 0.01', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Disable Sound"), "nosound", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + //ambient fade + fr.add(menuitemcheck_spawn(_("Static Sounds"), "cl_staticsounds", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Mix in Background"),"s_inactive", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + pos += 8; + fr.add(menuitemcombo_spawn(_("Microphone Device"), "cl_voip_capturedevice", '280 8', cvar_string("_cl_voip_capturedevice_opts")), + fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("VOIP Playback Vol"),"cl_voip_play", '0 2 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("VOIP Test"), "cl_voip_test", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("VOIP Record Vol"), "cl_voip_micamp", '0 4 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("VOIP Mode"), "cl_voip_send", '280 8', _( + "0 \"Push-To-Talk\" 1 " + "\"Voice Activation\" " + "2 \"Continuous\"" + )), fl, [0, pos], [0, 8]); pos += 8; + //VAD threshhold + //ducking + //noise cancelation + fr.add(menuitemcombo_spawn(_("VOIP Codec"), "cl_voip_codec", '280 8',_( + "0 \"speex (narrow 11khz)\" " + //"1 \"raw (wasteful)\" " + "2 \"opus\" " + "3 \"speex (narrow 8khz)\" " + "4 \"speex (wide 16khz)\" " + "5 \"speex (ultrawide 32khz)\" " + )), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(menuitemslider_spawn(_("Opus bitrate"), "cl_voip_bitrate", '0.5 128 0.5','280 8'), fl, [0, pos], [0, 8]); pos += 8; + + addmenuback(m); +}; diff --git a/menu/options_basic.qc b/menu/options_basic.qc new file mode 100644 index 000000000..5bd5cd523 --- /dev/null +++ b/menu/options_basic.qc @@ -0,0 +1,228 @@ +class mitem_playerpreview : mitem_spinnymodel +{ +#if 1//defined(FTE_QC_CUSTOMSKINS) + virtual void(vector pos) item_draw = + { + //if you wanted to get more advanced, you could use q3 skins here. + if (cvar("noskins")) + setcustomskin(self, "", sprintf("q1upper \"%s\"\nq1lower \"%s\"\n\n", cvar_string("topcolor"), cvar_string("bottomcolor"))); + else if (cvar_string("cl_teamskin") != "") + setcustomskin(self, "", sprintf("q1upper \"%s\"\nq1lower \"%s\"\nqwskin \"%s\"\n", cvar_string("topcolor"), cvar_string("bottomcolor"), cvar_string("cl_teamskin"))); + else + setcustomskin(self, "", sprintf("q1upper \"%s\"\nq1lower \"%s\"\nqwskin \"%s\"\n", cvar_string("topcolor"), cvar_string("bottomcolor"), cvar_string("skin"))); + + super::item_draw(pos); + }; +#endif +}; + +static string() skinopts = +{ + string opts = ""; + float s = search_begin("skins/*.pcx", TRUE, TRUE); + if (s < 0) + return opts; + float n = search_getsize(s); + for (float i = 0; i < n; i++) + { + string f = substring(search_getfilename(s, i), 6, -5); + opts = strcat(opts, "\"", f, "\" \"", f, "\" "); + } + return opts; +}; + +var float autocvar_m_pitch = 0.022; +class options_basic : mitem_exmenu +{ + virtual float(string key) isvalid = + { + if (key == "m_pitchsign") + return TRUE; + if (key == "cl_run") + return TRUE; + return super::isvalid(key); + }; + virtual string(string key) get = + { + if (key == "m_pitchsign") + return (autocvar_m_pitch<0)?"1":"0"; + if (key == "cl_run") + return (stof(super::get("cl_forwardspeed")) > 200)?"1":"0"; + //if (key == "fo_grentimer") + // return getplayerkeyvalue(player_localnum, "nt"); + return super::get(key); + }; + virtual void(string key, string newval) set = + { + if (key == "m_pitchsign") + { + float invert; + if (stof(newval)) + invert = autocvar_m_pitch > 0; + else + invert = autocvar_m_pitch < 0; + if (invert) + cvar_set("m_pitch", ftos(-autocvar_m_pitch)); + } + else if (key == "cl_run") + { + float setbackspeed = (super::get("cl_backspeed") != ""); + if (stof(newval)) + { + super::set("cl_forwardspeed", "400"); + super::set("cl_sidespeed", "400"); + if (setbackspeed) + super::set("cl_backspeed", "400"); + super::set("cl_movespeedkey", "0.5"); //makes +speed act like +walk + } + else + { + //these are the defaults from winquake. + super::set("cl_forwardspeed", "200"); + super::set("cl_sidespeed", "350"); + if (setbackspeed) + super::set("cl_backspeed", "200"); + super::set("cl_movespeedkey", "2.0"); + } + } + else + super::set(key, newval); + }; +}; +nonstatic void(mitem_desktop desktop) M_Options_Basic = +{ + mitem_exmenu m; + m = spawn(options_basic, item_text:_("Basic Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_cstm.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + float pos = 0; + + + fr.add(menuitemeditt_spawn(_("Player Name"), dp("_cl_name", "name"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + //fr.add(menuitemeditt_spawn(_("Player Team"), "team", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + //fr.add(menuitemcombo_spawn(_("Player Skin"), "skin", '280 8', skinopts()), fl, [0, pos], [0, 8]); pos += 8; + + + //fr.add(menuitemcolour_spawn(_("Upper Colour"), "topcolor", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + //fr.add(menuitemcolour_spawn(_("Lower Colour"), "bottomcolor", '280 8'), fl, [0, pos], [0, 8]); pos += 8; /*aka: arse colour*/ + pos += 8; + fr.add(menuitemcheck_spawn (_("Always Run"), "cl_run", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn (_("Invert Mouse"), "m_pitchsign", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Sensitivity"), "sensitivity", '3 20 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Fov"), "fov", '80 130 5', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Viewmodel Fov"), "r_viewmodel_fov", '80 130 5', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Gamma"), dp("v_gamma", "gamma"), '0.8 1.2 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Contrast"), dp("v_contrast", "contrast"), '0.8 1.2 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Brightness"), dp("v_brightness", "brightness"),'-0.2 0.2 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + pos += 8; + fr.add( + spawn( + mitem_text, + item_text:strcat("^{e080} ^a","Crosshair","^d ^{e082}"), + item_command:"", + item_scale:8, + item_flags:IF_CENTERALIGN + ), + RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, + [0, pos], + [0, 8] + ); pos += 8; + pos += 8; + fr.add(menuitemslider_spawn(_("Type"), "crosshair", '0.0 19 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcolour_spawn(_("Color"), "crosshaircolor", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn (_("Use Team Color"), "fo_team_color_crosshair", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + pos += 8; + fr.add( + spawn( + mitem_text, + item_text:strcat("^{e080} ^a","Grenades","^d ^{e082}"), + item_command:"", + item_scale:8, + item_flags:IF_CENTERALIGN + ), + RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, + [0, pos], + [0, 8] + ); pos += 8; + pos += 8; + fr.add(menuitemcombo_spawn (_("Timer"), "fo_grentimer", '280 8', _( + "2 \"On\" " + "0 \"Silent\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn (_("Sound"), "fo_grentimersound", '280 8', _( + "grentimer.wav \"Default\" " + "mcp_grentimer1.wav \"1\" " + "mcp_grentimer2.wav \"2\" " + "mcp_grentimer3.wav \"3\" " + "mcp_grentimer4.wav \"4\" " + "mcp_grentimer5.wav \"5\" " + "mcp_grentimer6.wav \"6\" " + "mcp_grentimer7.wav \"7\" " + "mcp_grentimer8.wav \"8\" " + "mcp_grentimer9.wav \"9\" " + "mcp_grentimer10.wav \"10\" " + "mcp_grentimer11.wav \"11\" " + "mcp_grentimer12.wav \"12\" " + "mcp_grentimer13.wav \"13\" " + "mcp_grentimer14.wav \"14\" " + "mcp_grentimer15.wav \"15\" " + "mcp_grentimer16.wav \"16\" " + "mcp_grentimer17.wav \"17\" " + "mcp_grentimer18.wav \"18\" " + "mcp_grentimer19.wav \"19\" " + "mcp_grentimer20.wav \"20\" " + "mcp_grentimer21.wav \"21\" " + "mcp_grentimer22.wav \"22\" " + "mcp_grentimer23.wav \"23\" " + "mcp_grentimer24.wav \"24\" " + "mcp_grentimer25.wav \"25\" " + "mcp_grentimer26.wav \"26\" " + )), fl, [0, pos], [0, 8]); pos += 8; + + + /* fr.add(menuitemcheck_spawn (_("Simple Bunnyhop"), "fo_autohop", '280 8'), fl, [0, pos], [0, 8]); pos += 8; */ + //fr.add(menuitemcheck_spawn (_("Grenade Timer"), "fo_grentimer", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + /* fr.add(menuitemcombo_spawn (_("Grenade Timer"), "fo_grentimer", '280 8', _( */ + /* "0 \"None\" " */ + /* "1 \"Server\" " */ + /* "2 \"Immediate\" " */ + /* )), fl, [0, pos], [0, 8]); pos += 8; */ + //fr.add(menuitemslider_spawn(_("TF Status Bar"), "crosshair", '0.0 19 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + //fr.add(menuitemslider_spawn(_("TF Flag Info"), "crosshair", '0.0 19 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + local string skin = "tf_sold"; + local float r = random(); + if(r > 0.89) + skin = "tf_scout"; + else if(r > 0.78) + skin = "tf_snipe"; + else if(r > 0.67) + skin = "tf_demo"; + else if(r > 0.56) + skin = "tf_medic"; + else if(r > 0.45) + skin = "tf_hwguy"; + else if(r > 0.34) + skin = "tf_pyro"; + else if(r > 0.23) + skin = "tf_spy"; + else if(r > 0.12) + skin = "tf_eng"; + + + //m.add(spawn (mitem_spinnymodel, item_text: it), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]); + m.add(spawn (mitem_spinnymodel, item_text: "progs/player.mdl", firstframe:12, framecount:5, shootframe:119, shootframes:6, dontrotate:1, startangle:'0 155 0', customskin:skin, rotatespeed:10, topcolour:0, bottomcolour:0), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-300, 16*-16/2], [-40, 16*16/2]); +// m.add(spawn (mitem_playerpreview, item_text: "progs/player.mdl", firstframe:0, framecount:6, shootframe:119, shootframes:6), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-200, 12*-16/2], [-40, 12*16/2]); + + addmenuback(m); +}; diff --git a/menu/options_effects.qc b/menu/options_effects.qc new file mode 100644 index 000000000..c081587fe --- /dev/null +++ b/menu/options_effects.qc @@ -0,0 +1,61 @@ +nonstatic void(mitem_desktop desktop) M_Options_Effects = +{ + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Effects Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + float pos = 0; + + fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Bloom"), "r_bloom", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Simple Textures"), "r_drawflat", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Paletted Rendering"), "r_softwarebanding", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("HDR"), "r_hdr_irisadaptation", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Coronas"), "r_coronas", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("High Res Textures"), "gl_load24bit", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Relief Mapping"), "r_glsl_offsetmapping", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Realtime Dynamic Lights"), "r_shadow_realtime_dlight", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Realtime World Lighting"), "r_shadow_realtime_world", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Texture Mode"), "gl_texturemode", '280 8', _( + "nll \"Blocky\" " + "lll \"Blurry\" " + )), fl, [0, pos], [0, 8]); pos += 8; + + + fr.add(menuitemslider_spawn(_("Particle Density"), "r_part_density", '0.25 4 0.25', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(menuitemcombo_spawn(_("Water Effects"), "r_waterstyle", '280 8', _( + "1 \"Classic\" " + "2 \"Ripples\" " + "3 \"Reflections\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("View Projection"), "r_projection", '280 8', _( + "0 \"Standard\" " + "1 \"Stereographic / Pannini\" " + "2 \"Fish-Eye\" " + "3 \"Panoramic\" " + "4 \"Lambert Azimuthal Equal-Area\" " + "5 \"Equirectangular\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("View Projection Fov"), "ffov", '280 8', _( + "90 \"Normal\" " + "180 \"180 degrees\" " + "270 \"270 degrees\" " + "360 \"360 degrees\" " + )), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(spawn(mitem_text, item_text:_("Apply"), item_command:"vid_restart", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; + + addmenuback(m); +}; + diff --git a/menu/options_keys.qc b/menu/options_keys.qc new file mode 100644 index 000000000..3f8bf92ce --- /dev/null +++ b/menu/options_keys.qc @@ -0,0 +1,158 @@ +const static struct +{ + string name; + string cmd; +} binds[] = +{ + {_(""), "Movement"}, + {0, 0}, + {_("Move Forward"), "+forward"}, + {_("Move Back"), "+back"}, + {_("Move Left"), "+moveleft"}, + {_("Move Right"), "+moveright"}, + {_("Jump"), "+jump"}, + {_("Swim Up"), "+moveup"}, + {_("Swim Down"), "+movedown"}, + /* {_("Turn Left"), "+left"}, */ + /* {_("Turn Right"), "+right"}, */ + /* {_("Look Up"), "+lookup"}, */ + /* {_("Look Down"), "+lookdown"}, */ + /* {_("Center view"), "centerview"}, */ + {0, 0}, + {0, 0}, + {_(""), "Actions"}, + {0, 0}, + {_("Fire"), "+attack"}, + {_("Class Special"), "special"}, + {_("Class Menu"), "fo_menu_special"}, + {_("Grenade 1"), "gren1"}, + {_("Grenade 2"), "gren2"}, + {_("Reload"), "reload"}, + {_("Reload Next"), "reloadnext"}, + {_("Change Class"), "changeclass"}, + {_("Change Team"), "changeteam"}, + {_("Drop Flag"), "dropflag"}, + {_("Discard Backpack"), "discard"}, + {_("Discard Ammo"), "dropammo"}, + {_("Call For Help"), "saveme"}, + {0, 0}, + {0, 0}, + {_(""), "Weapon Select"}, + {0, 0}, + {_("Next Weapon"), "weapnext"}, + {_("Prev Weapon"), "weapprev"}, + {_("Last Weapon"), "weaplast"}, + {_("Primary Weapon"), "impulse 1"}, + {_("Secondary Weapon"), "impulse 2"}, + {_("Tertiary Weapon"), "impulse 3"}, + {_("Melee"), "impulse 4"}, + {0, 0}, + {0, 0}, + {_(""), "Advanced Weaponry"}, + {0, 0}, + {_("Fire Primary"), "+quick1"}, + {_("Fire Secondary"), "+quick2"}, + {_("Fire Tertiary"), "+quick3"}, + {_("Fire Melee"), "+quick4"}, + {_("Quick Primary"), "+slot1"}, + {_("Quick Secondary"), "+slot2"}, + {_("Quick Tertiary"), "+slot3"}, + {_("Quick Melee"), "+slot4"}, + {_("Prime Grenade 1"), "primeone"}, + {_("Prime Grenade 2"), "primetwo"}, + {_("Throw Grenade"), "throwgren"}, + /* {_("Inventory"), "inv"}, */ + /* {_("Flag Status"), "flaginfo"}, */ + {0, 0}, + {0, 0}, + {_(""), "Misc"}, + {0, 0}, + {_("Scores"), "+fo_showscores"}, + {_("Ready Up"), "ready"}, + {_("Server Chat"), "messagemode"}, + {_("Team Chat"), "messagemode2"}, + {_("Map Help"), "maphelp"}, + {_("Main Menu"), "togglemenu"}, + /* {_("Break Match"), "break"}, */ + /* {_("Voice Chat"), "+voip"}, */ +// {_("Mouse Look"), "+mlook"}, +// {_("Keyboard Look"), "+klook"}, + /* {_("Strafe"), "+strafe"}, */ + /* {_("Run"), "+speed"}, */ + /* {_("Spectate"), "observe"}, */ + /* {_("Join Match"), "join"}, */ +}; +void(mitem_desktop desktop) M_Options_Keys = +{ + float i; + float h; + + //create the menu, give it focus, and make sure its displayed over everything else. + mitem_exmenu m = spawn(mitem_exmenu, item_text:_("Key Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //figure out the size of the stuff +// h = sizeof(binds) / sizeof(binds[0]); +// h *= 8; + h = 200; + h *= 0.5; //and halve it + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_cstm.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [banner.item_size_x*-0.5, -h-32], [banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_OWN_MIN, [-140, -h], [280, h*2]); + + float f = fopen("bindlist.lst", FILE_READ); + if (f >= 0) + { + //throw a load of bind options onto it by reading from the array. + for (i = 0; ; ) + { + string line = fgets(f); + if not (line) + break; //eof + float args = tokenize(line); + if (!args) + continue; //blank line + string c = argv(0); + string n = argv(1); + string t = argv(2); + if (c == "-") //command only + { + if (n != "") + { + mitem it = menuitemtext_spawn(n, "", 8); + it.item_flags &= ~IF_SELECTABLE; + fr.add(it, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN|RS_Y_MAX_OWN_MIN, [-it.item_size_x/2, i], it.item_size); + } + } + else + fr.add(menuitembind_spawn(n, c, '280 8'), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, i], '0 8'); + i += 8; + } + fclose(f); + } + else + { + //throw a load of bind options onto it by reading from the array. + for (i = 0; i < sizeof(binds) / sizeof(binds[0]); i++) + { + if (binds[i].name == "") { //no name is a spacer + if(binds[i].cmd) { + fr.add(spawn(mitem_text, item_text:strcat("^{e080} ^a",binds[i].cmd,"^d ^{e082}"), item_command:"", item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, (i*8)], [0, 8]); + i ++; + } + continue; + } + fr.add(menuitembind_spawn(binds[i].name, binds[i].cmd, '280 8'), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, (i*8) + 4], '0 8'); + } + } + + //and give us a suitable menu tint too, just because. + addmenuback(m); +}; diff --git a/menu/options_video.qc b/menu/options_video.qc new file mode 100644 index 000000000..5582e3198 --- /dev/null +++ b/menu/options_video.qc @@ -0,0 +1,95 @@ +nonstatic void(mitem_desktop desktop) M_Options_Video = +{ + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Video Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + float pos = 0; + + pos += 8; + fr.add(spawn(mitem_text, item_text:_("Apply / Restart"), item_command:"vid_restart", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; + pos += 8; + + if (cvar_type("vid_renderer")) fr.add(menuitemcombo_spawn(_("Renderer"), "vid_renderer", '280 8', cvar_string("_vid_renderer_opts")), fl, [0, pos], [0, 8]); pos += 8; + + //add the options + if (!dp_workarounds) + { + fr.add(menuitemcombo_spawn(_("Display Mode"), "vid_fullscreen", '280 8', + "0 \"Windowed\" " + "1 \"Fullscreen\" " + "2 \"Borderless Windowed\" " + ), fl, [0, pos], [0, 8]); pos += 8; + } + else + { + fr.add(menuitemcheck_spawn(_("Fullscreen"), "vid_fullscreen", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + } + if (cvar_type("vid_resizable")) fr.add(menuitemcheck_spawn(_("Resizable"), "vid_resizable", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Anti-Aliasing"), dp("vid_samples", "vid_multisample"), '280 8', + "0 \"Off\" " + "2 \"2x\" " + "4 \"4x\" " + ), fl, [0, pos], [0, 8]); pos += 8; + + //as far as video mode selections go, this is shite. + //should probably have an aspect+modes option instead, but that makes the combo really messy. especially as that would be two cvars. + fr.add(menuitemcombo_spawn(_("Video Width"), "vid_width", '280 8', _( + "0 \"Default\" " + "640 \"640\" " + "800 \"800\" " + "1024 \"1024\" " + "1280 \"1280\" " + "1920 \"1920\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Video Height"), "vid_height", '280 8', _( + "0 \"Default\" " + "480 \"480\" " + "600 \"600\" " + "768 \"768\" " + "720 \"720\" " + "1080 \"1080\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Video Zoom"), "vid_conautoscale", '280 8', _( + "0 \"Default\" " + "1.5 \"x1.5\" " + "2 \"x2\" " + "4 \"x4\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Colour Depth"), dp("vid_bitsperpixel", "vid_bpp"), '280 8', _( + "16 \"16bit\" " + "32 \"24bit\" " //alpha doesn't count. + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Refresh Rate"), "vid_displayfrequency", '280 8', _( + "0 \"Default\" " + // "60 \"60\" " + // "75 \"75\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("VSync"), dp("vid_vsync", "vid_wait"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("View Size"), "viewsize", '50 120 10', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Field Of View"), "fov", '50 140 5', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Gamma"), "gamma", '1.3 0.5 -0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Contrast"), "contrast", '0.7 2 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Brightness"), "brightness", '0 0.4 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Hardware Gamma"), "vid_hardwaregamma", '280 8', + "0 \"Off\" " + "1 \"Auto\" " + "2 \"Soft\" " + "3 \"Hard\" " + "4 \"Scene Only\" " + ), fl, [0, pos], [0, 8]); pos += 8; + + addmenuback(m); +}; + diff --git a/menu/presets.qc b/menu/presets.qc new file mode 100644 index 000000000..e69de29bb diff --git a/menu/quit.qc b/menu/quit.qc new file mode 100644 index 000000000..f8eb7b13c --- /dev/null +++ b/menu/quit.qc @@ -0,0 +1,46 @@ +/*************************************************************************** +Quit menu. Quite lame for now. +*/ +float() cvars_haveunsaved = #0; +nonstatic void(mitem_desktop desktop) M_Quit = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //center the actual items + pos = (16/-2)*(2); + + //draw title art above the options +// mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); +// m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); + + if (cvars_haveunsaved()) + { + m.add(spawn(mitem_text, item_text:"Save configuration?", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos-(8+16)], [0, pos-8]); pos += 16; + + m.add(spawn(mitem_text, item_text:"Save and quit", item_command:"m_pop;cfg_save;quit", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:"Quit and discard settings", item_command:"m_pop;quit", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:"Keep playing.", item_command:"m_pop;m_main", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + } + else + { + m.add(spawn(mitem_text, item_text:"Really Quit?", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos-(8+16)], [0, pos-8]); pos += 16; + + m.add(spawn(mitem_text, item_text:"Yes, I'm late for work.", item_command:"m_pop;quit", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:"No, keep playing!", item_command:"m_pop;m_main", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + } + + //random art for style +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [0, 12*-16/2], [160, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + addmenuback(m); +}; \ No newline at end of file diff --git a/menu/servers.qc b/menu/servers.qc new file mode 100644 index 000000000..8cbb1ccf2 --- /dev/null +++ b/menu/servers.qc @@ -0,0 +1,411 @@ +//very very basic server browser. +//this was written as an example for the api, rather than a truely usable server browser. + +//WARNING: make sure you only create one server browser widget otherwise they'll fight each other + +#ifndef MOD_GAMEDIR +//look up what its meant to be. +//#define MOD_GAMEDIR cvar_string("game") +#endif + +#include "../menusys/mitem_menu.qc" //subwindow + +var float autocvar_sb_shownumplayers = 1; +var float autocvar_sb_showmaxplayers = 0; +var float autocvar_sb_showfraglimit = 0; +var float autocvar_sb_showtimelimit = 0; +var float autocvar_sb_showping = 1; +var float autocvar_sb_showgamedir = 0; +var float autocvar_sb_showaddress = 0; +var float autocvar_sb_showmap = 1; +var float autocvar_sb_showname = 1; + +//#define COLUMN(width, sortname, title, draw) +#define COLUMN_NUMPLAYERS COLUMN(2*8, numplayers, "Pl", ui.drawstring(pos, sprintf("%-2g", gethostcachenumber(field_numplayers, sv)), '8 8', col, 1, 0);) +#define COLUMN_MAXPLAYERS COLUMN(2*8, maxplayers, "MP", ui.drawstring(pos, sprintf("%-2g", gethostcachenumber(field_maxplayers, sv)), '8 8', col, 1, 0);) +#define COLUMN_PING COLUMN(4*8, ping, "Ping", ui.drawstring(pos, sprintf("%-4g", gethostcachenumber(field_ping, sv)), '8 8', col, 1, 0);) +#define COLUMN_FRAGLIMIT COLUMN(4*8, fraglimit, "FL", ui.drawstring(pos, sprintf("%-3g", gethostcachenumber(field_fraglimit, sv)), '8 8', col, 1, 0);) +#define COLUMN_TIMELIMIT COLUMN(4*8, timelimit, "TL", ui.drawstring(pos, sprintf("%-3g", gethostcachenumber(field_timelimit, sv)), '8 8', col, 1, 0);) +#define COLUMN_GAMEDIR COLUMN(8*8, gamedir, "Gamedir", ui.drawstring(pos, sprintf("%-.8s", gethostcachestring(field_gamedir, sv)), '8 8', col, 1, 0);) +#define COLUMN_ADDRESS COLUMN(16*8, address, "Address", ui.drawstring(pos, sprintf("%-.16s", gethostcachestring(field_address, sv)), '8 8', col, 1, 0);) +#define COLUMN_MAP COLUMN(8*8, map, "Map", ui.drawstring(pos, sprintf("%-.8s", gethostcachestring(field_map, sv)), '8 8', col, 1, 0);) +#define COLUMN_HOSTNAME COLUMN(64*8, name, "Name", ui.drawstring(pos, sprintf("%s", gethostcachestring(field_name, sv)), '8 8', col, 1, 0);) +//FIXME: add a little * icon before the hostname for favourites or something + +#define COLUMNS COLUMN_NUMPLAYERS COLUMN_MAXPLAYERS COLUMN_PING COLUMN_FRAGLIMIT COLUMN_TIMELIMIT COLUMN_GAMEDIR COLUMN_ADDRESS COLUMN_MAP COLUMN_HOSTNAME + +class mitem_servers : mitem +{ + float server_selected; + mitem_vslider slider; + float dbltime; + float dobound; + + void() mitem_servers = + { + dbltime = cltime - 10; + this.item_flags |= IF_SELECTABLE; + server_selected = -1; + + //clear the filter + resethostcachemasks(); +#ifdef MOD_GAMEDIR + if (MOD_GAMEDIR != "") + { + //constrain the list to only servers with the right gamedir. + sethostcachemaskstring(0, gethostcacheindexforkey("gamedir"), MOD_GAMEDIR, SLIST_TEST_EQUAL); + } +#endif + //sort by ping by default + sethostcachesort(gethostcacheindexforkey("ping"), FALSE); + //(re)query the servers. + refreshhostcache(); + + resorthostcache(); + }; + + virtual void(vector pos) item_draw = + { + local float sv, maxsv = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); + local float maxy = self.item_size_y; + float left = pos_x; + + local vector omin = ui.drawrectmin, omax = ui.drawrectmax; + local vector cmin = pos + '0 8', cmax = pos + item_size; + + cmin_x = max(omin_x, cmin_x); + cmin_y = max(omin_y, cmin_y); + cmax_x = min(omax_x, cmax_x); + cmax_y = min(omax_y, cmax_y); + + slider.maxv = maxsv - (floor(maxy/8) - 1); + + //constrain the view the lazy way. + if (dobound) + { + dobound = FALSE; + if (slider.val < 0) + slider.val = 0; + while (slider.val + floor(maxy/8) - 1 <= server_selected) + slider.val+=1; + while (server_selected < slider.val && slider.val > 0) + slider.val-=1; + } + + float field_name = gethostcacheindexforkey("name"); + float field_ping = gethostcacheindexforkey("ping"); + float field_numplayers = gethostcacheindexforkey("numhumans"); + if (field_numplayers < 0) + field_numplayers = gethostcacheindexforkey("numplayers"); + float field_maxplayers = gethostcacheindexforkey("maxplayers"); + float field_gamedir = gethostcacheindexforkey("gamedir"); + if (field_gamedir < 0) + field_gamedir = gethostcacheindexforkey("mod"); + float field_address = gethostcacheindexforkey("cname"); + float field_map = gethostcacheindexforkey("map"); + float field_timelimit = gethostcacheindexforkey("timelimit"); + float field_fraglimit = gethostcacheindexforkey("fraglimit"); + + maxy = ceil(maxy/8) - 1; + if (maxsv > slider.val + maxy) + maxsv = slider.val + maxy; + + float sort = gethostcachevalue(SLIST_SORTFIELD); + string colkey = ""; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {if (ui.mousepos[0] > pos_x && ui.mousepos[1] < pos_y+8) colkey = #sortname; pos_x += width+8;} + COLUMNS +#undef COLUMN + + vector col; + pos_x = left; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {col = '1 1 1'; if (sort == field_##sortname) col_z = 0; if (colkey == #sortname) col_x = 0; ui.drawstring(pos, title, '8 8', col, 1, 0); pos_x += width+8;} + COLUMNS +#undef COLUMN + + //make sure things get cut off if we have too many rows (or fractional start) + ui.setcliparea(cmin[0], cmin[1], cmax[0] - cmin[0], cmax[1] - cmin[1]); + + pos_y += 8 * (1-(slider.val-floor(slider.val))); + for (float y=pos_y, sv = max(0, floor(slider.val)); sv < maxsv; sv+=1) + { + col = (sv&1)?'0.1 0.1 0.1':'0.15 0.1 0.05'; + drawfill([left, y], [item_size_x,8], col, 0.8, 0); + y += 8; + } + for ( sv = max(0, floor(slider.val)); sv < maxsv; sv+=1) + { + col = ((server_selected==sv)?'1 1 0':'1 1 1'); + + //if isproxy + //if islocal + //if isfavorite + + pos_x = left; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {draw pos_x += width+8; } + COLUMNS +#undef COLUMN + pos_y += 8; + } + + ui.setcliparea(omin_x, omin_y, omax_x - omin_x, omax_y - omin_y); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + float displaysize; + string addr; + /*just sink all inputs*/ + if (!down) + return FALSE; + if (scan != K_MOUSE1) + dbltime = cltime - 10; + if (scan == K_MOUSE1) + { + float news = ui.mousepos[1] - (pos_y+8); + news = floor(news / 8); + if (news == -1) + { + string colkey = ""; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {if (ui.mousepos[0] > pos_x) colkey = #sortname; pos_x += width+8;} + COLUMNS +#undef COLUMN + if (colkey != "") + { + float fld = gethostcacheindexforkey(colkey); + if (colkey == "numplayers") + { + //favour descending order + sethostcachesort(fld, (gethostcachevalue(SLIST_SORTFIELD) != fld) || !gethostcachevalue(SLIST_SORTDESCENDING)); + } + else + { + //favour ascending order + sethostcachesort(fld, (gethostcachevalue(SLIST_SORTFIELD) == fld) && !gethostcachevalue(SLIST_SORTDESCENDING)); + } + resorthostcache(); //tell the engine that its okay to resort everything. + dobound = TRUE; + } + } + if (news < 0 || news >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) + return FALSE; + + news += floor(slider.val); + + if (server_selected == news && dbltime > cltime) + { + //connect on double clicks. because we can. + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;connect \"%s\"\n", addr)); + } + else + server_selected = news; + dobound = TRUE; + + dbltime = cltime + 0.5; + } + else if (scan == K_ENTER) + { //connect normally + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;connect \"%s\"\n", addr)); + } + else if (scan == 's') + { //s = join as a spectator + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;observe \"%s\"\n", addr)); + } + else if (scan == 'j') + { //s = join as a spectator + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;join \"%s\"\n", addr)); + } + else if (scan == K_UPARROW || scan == K_MWHEELUP) + { + this.server_selected -= 1; + if (this.server_selected < 0) + { + this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); + if (this.server_selected) + this.server_selected -= 1; + } + dobound = TRUE; + } + else if (scan == K_DOWNARROW || scan == K_MWHEELDOWN) + { + this.server_selected += 1; + if (this.server_selected >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) + this.server_selected = 0; + dobound = TRUE; + } + else if (scan == K_PGUP) + { + displaysize = (item_size[1]-8)/(8); //this many rows + displaysize = floor(displaysize*0.5); + if (displaysize < 1) + displaysize = 1; + this.server_selected -= displaysize ; + if (this.server_selected < 0) + this.server_selected = 0; + dobound = TRUE; + } + else if (scan == K_PGDN) + { + displaysize = (item_size[1]-8)/(8); //this many rows + displaysize = floor(displaysize*0.5); + if (displaysize < 1) + displaysize = 1; + this.server_selected += displaysize; + if (this.server_selected >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) + this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)-1; + dobound = TRUE; + } + else if (scan == K_HOME) + { + this.server_selected = 0; + dobound = TRUE; + } + else if (scan == K_END) + { + this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)-1; + if (this.server_selected < 0) + this.server_selected = 0; + dobound = TRUE; + } + else if (char == 'r' || char == 'R' || scan == K_F5) + { + refreshhostcache(); + resorthostcache(); + } + else + return FALSE; + return TRUE; + }; +}; + +class mitem_servers_players : mitem +{ + mitem_servers listing; + + void() mitem_servers_players = + { +// this.item_flags |= IF_SELECTABLE; + }; + + virtual void(vector pos) item_draw = + { + float player; + float y; + float m; + vector opos = pos; + if (listing.server_selected < 0) + return; + for (player = 0, y = 0; player < 256; player++) + { + string playerinfo = gethostcachestring(gethostcacheindexforkey(sprintf("player%g", player)), listing.server_selected); + if (!playerinfo) + break; + tokenize(playerinfo); + float userid = stof(argv(0)); + float frags = stof(argv(1)); + float ontime = stof(argv(2)); + float ping = stof(argv(3)); + string name = argv(4); + string skin = argv(5); + vector top = stov(argv(6)); + vector bot = stov(argv(7)); + + drawfill(pos, '16 4', top, 1, 0); + drawfill(pos+'0 4 0', '16 4', bot, 1, 0); + drawstring(pos, sprintf("%g", frags), '8 8', '1 1 1', 1, 0); + drawstring(pos+'20 0', name, '8 8', '1 1 1', 1, 0); + pos_y += 8; + if (++y == 8) + { + y-= 8; + pos_y = opos_y; + pos_x += 16*8; + } + } + + if (y) + { + pos_y = opos_y; + pos_x += 16*6; + y = 0; + } +// drawtextfield(opos, item_size, 3, gethostcachestring(gethostcacheindexforkey("serverinfo"), listing.server_selected)); + m = tokenizebyseparator(gethostcachestring(gethostcacheindexforkey("serverinfo"), listing.server_selected), "\\"); + for(player = 1; player <= m; player += 2) + { + drawtextfield(pos, '64 8', 6, argv(player)); + drawtextfield(pos+'68 0', [32*8-40, 8], 3, argv(player+1)); + + pos_y += 8; + if (++y == 8) + { + y-= 8; + pos_y -= 8*8; + pos_x += 32*8; + } + } + }; +}; + + +nonstatic void(mitem_desktop desktop) M_Servers = +{ + mitem_menu o; + + if (assumefalsecheckcommand("menu_servers") && argv(1) != "force") + { + localcmd("menu_servers\n"); + return; + } + +#ifdef CSQC + if not (checkextension("FTE_CSQC_SERVERBROWSER")) + { + print(_("Sorry, your client does not support FTE_CSQC_SERVERBROWSER\n")); + return; + } +#endif + + o = (mitem_menu)desktop.findchildtext(_("Servers List")); + if (o) + o.totop(); + else + { +#if 1 + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); +#else + mitem_menu m; + m = menu_spawn(desktop, _("Servers List"), '320 200'); + m.item_command = "m_main"; + m.item_flags |= IF_RESIZABLE; + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); +#endif + + mitem_vslider sl = spawn(mitem_vslider, stride:4, item_flags:IF_SELECTABLE); + mitem_servers ls = spawn(mitem_servers, slider:sl); + + m.add(ls, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '-16 -68'); + m.add(spawn(mitem_servers_players, listing:ls), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [8*20, -8*8], [0, -8*0]); + m.add(sl, RS_X_MIN_PARENT_MAX|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [-16, 8], [0, -68]); + + m.add(menuitemcheck_spawn(_("Ping"), "sb_showping", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*8], [8*20, -8*7]); + m.add(menuitemcheck_spawn(_("Address"), "sb_showaddress", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*7], [8*20, -8*6]); + m.add(menuitemcheck_spawn(_("Map"), "sb_showmap", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*6], [8*20, -8*5]); + m.add(menuitemcheck_spawn(_("Gamedir"), "sb_showgamedir", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*5], [8*20, -8*4]); + m.add(menuitemcheck_spawn(_("NumPlayers"), "sb_shownumplayers", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*4], [8*20, -8*3]); + m.add(menuitemcheck_spawn(_("MaxPlayers"), "sb_showmaxplayers", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*3], [8*20, -8*2]); + m.add(menuitemcheck_spawn(_("Fraglimit"), "sb_showfraglimit", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*2], [8*20, -8*1]); + m.add(menuitemcheck_spawn(_("Timelimit"), "sb_showtimelimit", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*1], [8*20, -8*0]); + } +}; diff --git a/menusys/mitem_bind.qc b/menusys/mitem_bind.qc new file mode 100644 index 000000000..08ef94b79 --- /dev/null +++ b/menusys/mitem_bind.qc @@ -0,0 +1,108 @@ +/*************************************************************************** +key binding item. +interactable - queries binds for a command, and accepts new scan codes to bind for its given command. +*/ +class mitem_bind : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float flag) item_focuschange; + + void() mitem_bind = + { + item_scale = item_size[1]; + + item_flags |= IF_SELECTABLE; + + item_text = strzone(item_text); + item_command = strzone(item_command); + }; + virtual void() item_remove = + { + strunzone(item_text); + strunzone(item_command); + }; +}; +#define menuitembind_spawn(text,command,sz) \ + spawn(mitem_bind, \ + item_text: text, \ + item_command: command, \ + item_size: sz \ + ) + +void(vector pos) mitem_bind::item_draw = +{ + /*this is not my API...*/ + tokenize(findkeysforcommand(self.item_command)); + string key1 = argv(0); + string key2 = argv(1); + if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); + if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); + + super::item_draw(pos); + pos_x += self.item_size_x / 2; + + if (self.item_flags & IF_INTERACT) + { + ui.drawstring(pos, "Please press a key", '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); + } + else + { + ui.drawstring(pos, key1, '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); + pos_x += stringwidth(key1, TRUE, '1 1 0'*self.item_scale); + + if (key2 != "") + { + ui.drawstring(pos, " or ", '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); + pos_x += stringwidth(" or ", TRUE, '1 1 0'*self.item_scale); + + ui.drawstring(pos, key2, '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); +// pos_x += stringwidth(key2, TRUE, '1 1 0'*self.item_scale); + } + } +}; +float(vector pos, float scan, float char, float down) mitem_bind::item_keypress = +{ + if (!down) + return FALSE; + + if (self.item_flags & IF_INTERACT) + { + if (scan == K_ESCAPE) + { + } + else if (scan) + localcmd(sprintf("bind \"%s\" \"%s\"\n", keynumtostring(scan), self.item_command)); + else + return FALSE; + self.item_flags -= IF_INTERACT; + return TRUE; + } + else + { + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, self.item_size))) + { + self.item_flags |= IF_INTERACT; + return TRUE; + } + if (scan == K_DEL || scan == K_BACKSPACE) + { + /*again, this is not my API...*/ + tokenize(findkeysforcommand(self.item_command)); + string key1 = argv(0); + string key2 = argv(1); + if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); + if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); + if (key1 != "") localcmd(sprintf("bind \"%s\" \"\"\n", key1)); + if (key2 != "") localcmd(sprintf("bind \"%s\" \"\"\n", key2)); + return TRUE; + } + return FALSE; + } +}; +void(mitem newfocus, float flag) mitem_bind::item_focuschange = +{ + if (!(self.item_flags & IF_KFOCUSED)) + self.item_flags = self.item_flags - (self.item_flags & IF_INTERACT); +}; + diff --git a/menusys/mitem_checkbox.qc b/menusys/mitem_checkbox.qc new file mode 100644 index 000000000..b6ebd1296 --- /dev/null +++ b/menusys/mitem_checkbox.qc @@ -0,0 +1,69 @@ +/*************************************************************************** +checkbox, directly linked to a cvar. +*/ +class mitem_check : mitem +{ + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (!down) + return FALSE; + + if (scan == K_ENTER || scan == K_SPACE || scan == K_LEFTARROW || scan == K_RIGHTARROW || scan == K_MOUSE1) + { + pos_x += this.item_size_x / 2; +// if (ui.mousepos[0] > pos_x || scan != K_MOUSE1) //don't do anything if they clicked the bit on the left to select it + set(item_command, ftos(!stof(get(item_command)))); + return TRUE; + } + else if (scan == K_DEL && down && cvar_type(item_command)) + set(item_command, cvar_defstring(item_command)); + return FALSE; + }; + virtual void(vector pos) item_draw = + { + vector rgb = item_rgb; + if (!(item_flags & IF_SELECTABLE)) + rgb *= 0.2; + local float truth = stof(get(item_command)); + + super::item_draw(pos); + pos_x += item_size_x / 2; + + if (dp_workarounds) + { //lame, but whatever + ui.drawstring(pos, chr2str(0xe080, 0xe082), '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); + if (truth) + ui.drawstring(pos + '4 0', chr2str(0xe083), '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); + } + else + { + ui.drawstring(pos, "^{e080}^{e082}", '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); + if (truth) + ui.drawstring(pos + '4 0', "^{e083}", '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); + } + }; + void() mitem_check = + { + if (!item_scale) + item_scale = 8; + if (!item_size_y) + item_size_y = item_scale; + if (isvalid(item_command)) + item_flags |= IF_SELECTABLE; + }; + + virtual void() item_resized = + { + if (isvalid(item_command)) + item_flags |= IF_SELECTABLE; + else + item_flags &= ~IF_SELECTABLE; + super::item_resized(); + }; +}; + +//optional, can spawn direcly +mitem_check(string text, string command, vector sz) menuitemcheck_spawn = +{ + return spawn(mitem_check, item_scale: sz_y, item_text: text, item_size: sz, item_command: command); +}; diff --git a/menusys/mitem_colours.qc b/menusys/mitem_colours.qc new file mode 100644 index 000000000..1588baba8 --- /dev/null +++ b/menusys/mitem_colours.qc @@ -0,0 +1,247 @@ +/*************************************************************************** +hue selection thing, directly linked to a cvar. +We hard code 1 in the saturation+value arguments for the selection + +Screw Quake colours, they're too brown! +*/ +class mitem_colours : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + + virtual void() item_resized = + { + if (isvalid(item_command)) + item_flags |= IF_SELECTABLE; + else + item_flags &= ~IF_SELECTABLE; + super::item_resized(); + }; +}; + +//http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c +static vector(vector rgb) rgbtohsv = +{ + float r = rgb_x, g = rgb_y, b = rgb_z; + float maxc = max(r, g, b), minc = min(r, g, b); + float h, s, l = (maxc + minc) / 2; + + local float d = maxc - minc; + if (maxc) + s = d / maxc; + else + s = 0; + + if(maxc == minc) + { + h = 0; // achromatic + } + else + { + if (maxc == r) + h = (g - b) / d + ((g < b) ? 6 : 0); + else if (maxc == g) + h = (b - r) / d + 2; + else + h = (r - g) / d + 4; + h /= 6; + } + + return [h, s, l]; +}; + +//http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c +static vector(vector hsv) hsvtorgb = +{ + float h = hsv_x, s = hsv_y, v = hsv_z; + float r=0, g=1, b=0; + + while(h < 0) + h+=1; + while(h >= 1) + h-=1; + + float i = floor(h * 6); + float f = h * 6 - i; + float p = v * (1 - s); + float q = v * (1 - f * s); + float t = v * (1 - (1 - f) * s); + switch(i) + { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + return [r, g, b]; +}; + +float(float chr) nibbletofloat = +{ + chr = chr&0xff; + if (chr >= '0' && chr <= '9') + return chr - '0'; + if (chr >= 'a' && chr <= 'f') + return chr - 'a' + 10; + if (chr >= 'A' && chr <= 'F') + return chr - 'A' + 10; + return 0; +}; + +static vector(string v) hextorgb = +{ + if (!strncmp(v, "0x", 2)) + { + vector r; + r_x = (nibbletofloat(str2chr(v, 2))*16 + nibbletofloat(str2chr(v, 3)))/255; + r_y = (nibbletofloat(str2chr(v, 4))*16 + nibbletofloat(str2chr(v, 5)))/255; + r_z = (nibbletofloat(str2chr(v, 6))*16 + nibbletofloat(str2chr(v, 7)))/255; + return r; + } + else + { + float legacycolour = stof(v); + switch(legacycolour) + { + case 0: return [0xeb, 0xeb, 0xeb]/255; + case 1: return [0x8f, 0x6f, 0x23]/255; + case 2: return [0x8b, 0x8b, 0xcb]/255; + case 3: return [0x6b, 0x6b, 0x0f]/255; + case 4: return [0x7f, 0x00, 0x00]/255; + case 5: return [0xaf, 0x67, 0x23]/255; + case 6: return [0xff, 0xf3, 0x1b]/255; + case 7: return [0xe3, 0xb3, 0x97]/255; + + case 8: return [0xab, 0x8b, 0xa3]/255; + case 9: return [0xbb, 0x73, 0x97]/255; + case 10: return [0xdb, 0xc3, 0xbb]/255; + case 11: return [0x6f, 0x83, 0x7b]/255; + case 12: return [0xff, 0xf3, 0x1b]/255; + case 13: return [0x00, 0x00, 0xff]/255; + //14+15 are fullbrights, so not valid. + + default: + return '0 0 0'; + } + } +}; +static string(vector v) rgbtohex = +{ + v *= 255; + return sprintf("0x%02x%02x%02x", v_x, v_y, v_z); +}; + +void(vector pos) mitem_colours::item_draw = +{ + local float step; + local float stride; + local string curval; + local vector rgb; + + super::item_draw(pos); + + //calculate the rgb from hue at each step across the colour block +#define STEPS 32 + pos_x += item_size_x / 2; + + if (ui.mgrabs == this) + { + float frac; + //if we're sliding it, update the value + frac = (ui.mousepos[1] - pos_x-(item_size_y+4)) / (item_size_x / 2 - (item_size_y+4)); + if (frac >= 0 && frac <= 1) + { + set(item_command, rgbtohex(hsvtorgb([frac, 1, 1]))); + } + } + curval = get(item_command); + + stride = (item_size_x / 2 - (item_size_y+4)) / STEPS; + + ui.drawfill(pos, [item_size_y, item_size_y], hextorgb(curval), item_alpha, 0); + pos_x += item_size_y+4; + + pos_y += 1; +#if defined(MENU) || 1 + for (step = 0; step < STEPS; step += 1, pos_x += stride) + { + rgb = hsvtorgb([step/STEPS, 1, 1]); + if (!(item_flags & IF_SELECTABLE)) + rgb *= 0.2; + ui.drawfill(pos, [stride, item_size_y-2], rgb, item_alpha, 0); + } +#else +//FIXME: WTF is going on here? it comes out as black? wtf? + //draw quads (we should probably not use an internal-to-engine shader here...) + R_BeginPolygon("fill_opaque", 4); //outside so we can skip it for faster reuse by avoiding lookups + rgb = hsvtorgb([0, 1, 1]); + for (step = 0; step < STEPS;) + { + R_PolygonVertex([pos_x, pos_y+item_size_y-2], '0 1', rgb, item_alpha); + R_PolygonVertex([pos_x, pos_y], '0 0', rgb, 1); + + pos_x += stride; + step += 1; + rgb = hsvtorgb([step/STEPS, 1, 1]); + + R_PolygonVertex([pos_x, pos_y], '1 0', rgb, 1); + R_PolygonVertex([pos_x, pos_y+item_size_y-2], '1 1', rgb, item_alpha); + + R_EndPolygon(); + } +#endif +#undef STEPS +}; + +float(vector pos, float scan, float char, float down) mitem_colours::item_keypress = +{ + if (!down) + { + if (ui.mgrabs == this) + ui.mgrabs = __NULL__; + return FALSE; + } + local float curval = rgbtohsv(hextorgb(get(item_command)))[0]; + if (scan == K_MWHEELUP || scan == K_MWHEELDOWN) + { + if (mouseinbox(pos, item_size)) + scan = ((scan == K_MWHEELDOWN)?K_LEFTARROW:K_RIGHTARROW); + } + if (scan == K_MOUSE1) + { + pos_x += item_size_x / 2; + pos_x += item_size_y+4; + curval = (ui.mousepos[0] - pos_x) / (item_size_x / 2 - item_size_y+4); + if (curval < 0 || curval > 1) + return FALSE; + curval = curval; + set(item_command, rgbtohex(hsvtorgb([(curval), 1, 1]))); + ui.mgrabs = this; + } + else if (scan == K_LEFTARROW || scan == K_SPACE) + { + set(item_command, rgbtohex(hsvtorgb([curval - (1/64.0), 1, 1]))); //yay autorepeat + } + else if (scan == K_RIGHTARROW || scan == K_ENTER) + { + set(item_command, rgbtohex(hsvtorgb([curval + (1/64.0), 1, 1]))); + } + else + return FALSE; + return TRUE; +}; +mitem_colours(string text, string command, vector sz) menuitemcolour_spawn = +{ + mitem_colours n = spawn(mitem_colours); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + + n.item_command = command; + n.item_flags |= IF_SELECTABLE; + return n; +}; + diff --git a/menusys/mitem_combo.qc b/menusys/mitem_combo.qc new file mode 100644 index 000000000..23f419bdd --- /dev/null +++ b/menusys/mitem_combo.qc @@ -0,0 +1,303 @@ +/*************************************************************************** +combo item. +No longer using pointers, now using a tokenized string. Less efficient, but saves creating lots of different arrays, just pass a string. +The string list is doubled, first is the actual value, second is the friendly text. +Will show actual value when focused, and will show readable value when not. +The possible values is a separate popup. +*/ + +//FIXME: should probably set up a grabs to intercept right-click / escape outside of the item + +class mitem_combo; +class mitem_combo_popup; + +class mitem_combo : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float changedflag) item_focuschange; + + mitem_combo_popup cfriend; + string mstrlist; + float firstrow; + float visrows; + + virtual void() item_remove; + + virtual void() item_resized = + { + if (isvalid(item_command)) + item_flags |= IF_SELECTABLE; + else + item_flags &= ~IF_SELECTABLE; + super::item_resized(); + }; +}; + +class mitem_combo_popup : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float changedflag) item_focuschange; + + mitem_combo pfriend; + + virtual void() item_remove = + { + if (pfriend) + pfriend.cfriend = 0; + super::item_remove(); + }; +}; + +void() mitem_combo::item_remove = +{ + mitem_combo_popup p = cfriend; + if (p) + p.item_remove(); + strunzone(mstrlist); + super::item_remove(); +}; + +void(vector pos) mitem_combo::item_draw = +{ + local float i, v; + local string curval = get(item_command); + vector rgb = item_rgb; + if (!(item_flags & IF_SELECTABLE)) + rgb *= 0.2; + + if (cfriend) + cfriend.item_position = pos + [item_size_x / 2, item_size_y]; + + super::item_draw(pos); + + v = tokenize(mstrlist); + + //display the friendly string if the current value matches +// if (!(item_flags & IF_KFOCUSED) && (!cfriend || !(cfriend.item_flags & IF_KFOCUSED))) + { + for (i = 0; i < v; i+=2) + { + if (argv(i) == curval) + { + curval = argv(i+1); + break; + } + } + } + + pos_x += item_size_x / 2; + + +/* //border + ui.drawfill(pos, [item_size_x/2, 1], TD_BOT, item_alpha, 0); + ui.drawfill(pos, [1, item_size_y - 1], TD_RGT, item_alpha, 0); + ui.drawfill(pos + [item_size_x/2-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); + ui.drawfill(pos + [0, item_size_y-1], [item_size_x/2, 1], TD_TOP, item_alpha, 0); +*/ + //silly strings need to get cut off properly. + ui.setcliparea(pos[0], pos[1], item_size_x/2, item_size_y); + pos_y += (item_size_y - item_scale)*0.5; + pos_x += 1; + ui.drawstring(pos, curval, '1 1 0' * item_scale, rgb, item_alpha, 0); + ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); +}; +void(mitem newfocus, float flag) mitem_combo::item_focuschange = +{ + if (!cfriend || !(flag & IF_KFOCUSED)) + return; //don't care + if (newfocus != (mitem)this && newfocus != (mitem)cfriend) + { + cfriend.item_size = cfriend.maxs = '0 0'; + cfriend.item_flags &~= IF_SELECTABLE; + } +}; +void(mitem newfocus, float flag) mitem_combo_popup::item_focuschange = +{ + pfriend.item_focuschange(newfocus, flag); +}; +void(vector pos) mitem_combo_popup::item_draw = +{ + vector col; + if (item_size_y < 1) + return; + + local mitem_combo f = pfriend; + item_command = f.item_command; + local string curval = f.get(f.item_command); + local float i, m, c, v; + + if (!((f.item_flags | item_flags) & IF_KFOCUSED)) + { + item_size = maxs = '0 0'; + item_flags &~= IF_SELECTABLE; + return; + } + + ui.drawfill(pos, item_size, item_rgb, item_alpha, 0); + +/* //border + ui.drawfill(pos, [item_size_x, 1], TD_BOT, item_alpha, 0); + ui.drawfill(pos, [1, item_size_y - 1], TD_RGT, item_alpha, 0); + ui.drawfill(pos + [item_size_x-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); + ui.drawfill(pos + [0, item_size_y-1], [item_size_x, 1], TD_TOP, item_alpha, 0); +*/ pos_x += 1; + + v = tokenize(f.mstrlist); + for (c = 0; c < v; c += 2) + if (argv(c) == curval) + break; + if (c >= v) + c = 0; + + i = f.firstrow; + i = i*2; + if (!f.visrows) + i = 0; + else + { + //bound the displayed position + if (c < i) + i = c; + if (i < c - (f.visrows-1)*2) + i = c - (f.visrows-1)*2; + } + m = i + f.visrows*2; + f.firstrow = floor(i*0.5); + + //constrain the drawing so it doesn't overflow the combo + ui.setcliparea(pos[0], pos[1], item_size[0], item_size[1]); + + for (; i < m && i < v ; i+=2) + { + col = f.item_rgb; + if (item_flags & IF_MFOCUSED) + if (mouseinbox(pos, [item_size_x, item_scale])) + col_z = 0; + if (c == i) + col_x = 0; + + ui.drawstring(pos, argv(i+1), '1 1 0' * item_scale, col, f.item_alpha, 0); + pos_y += item_scale; + } + + //reset the clip area + ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); +}; +float(vector pos, float scan, float char, float down) mitem_combo_popup::item_keypress = +{ + return pfriend.item_keypress(pos - [0, pfriend.item_size_y], scan, char, down); +}; +float(vector pos, float scan, float char, float down) mitem_combo::item_keypress = +{ + if (!down) + return FALSE; + + local string curval = get(item_command); + local float i, c; + local float f; + c = tokenize(mstrlist); + + //find which one is active + for (i = 0; i < c; i+=2) + { + if (argv(i) == curval) + { + break; + } + } + + if (scan == K_ESCAPE || scan == K_MOUSE2) + { + if (cfriend) + { + cfriend.item_remove(); + return TRUE; + } + return FALSE; + } + else if (scan == K_MWHEELUP || (scan == K_UPARROW && cfriend)) + { + i -= 2; + if (i < 0) + i = c - 2; + curval = argv(i); + } + else if (scan == K_MWHEELDOWN || (scan == K_DOWNARROW && cfriend)) + { + i += 2; + if (i >= c) + i = 0; + curval = argv(i); + } + else if (scan == K_MOUSE1 || scan == K_ENTER) + { + if (scan == K_ENTER && cfriend) + { + cfriend.item_remove(); + return TRUE; + } + + visrows = ((c>18)?18/2:c/2); + if (!cfriend) + { + cfriend = spawn(mitem_combo_popup); + cfriend.pfriend = this; + cfriend.item_scale = 8; + cfriend.item_rgb = MENUBACK_RGB; + cfriend.item_alpha = MENUBACK_ALPHA; + pos = item_position; + mitem_frame fr = item_parent; + while (fr.item_parent) + { //try to inject the combo thingie into the desktop item. this is to avoid scissoring. + pos += fr.item_position; + fr = fr.item_parent; + } + fr.addr(cfriend, RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN, pos + [self.item_size_x / 2, self.item_size_y], [item_size_x*0.5, item_size_y*visrows]); + } + cfriend.item_size = cfriend.maxs = [item_size_x*0.5, item_size_y*visrows]; + cfriend.item_flags |= IF_SELECTABLE; + cfriend.totop(); + + if (scan == K_MOUSE1 && (cfriend.item_flags & IF_MFOCUSED)) + { + //if they clicked inside the popup, change the selected item. + f = ui.mousepos[1] - (pos_y + item_size_y); + f /= cfriend.item_scale; + f += firstrow; + i = floor(f) * 2; + if (i < c) + { + curval = argv(i); + cfriend.item_flags &~= IF_SELECTABLE; + cfriend.item_size = cfriend.maxs = '0 0'; + item_parent.item_focuschange(this, IF_MFOCUSED|IF_KFOCUSED); + + cfriend.item_remove(); + } + } + } + else if (scan == K_BACKSPACE || scan == K_DEL) + curval = substring(curval, 0, -2); + else if (char >= ' ') + curval = strcat(curval, chr2str(char)); + else + return FALSE; + + set(item_command, curval); + return TRUE; +}; +mitem_combo(string text, string command, vector sz, string valuelist) menuitemcombo_spawn = +{ + mitem_combo n = spawn(mitem_combo); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + n.mstrlist = strzone(valuelist); + + n.item_command = command; + if (n.isvalid(command)) + n.item_flags |= IF_SELECTABLE; + return n; +}; diff --git a/menusys/mitem_console.qc b/menusys/mitem_console.qc new file mode 100644 index 000000000..30d9b3816 --- /dev/null +++ b/menusys/mitem_console.qc @@ -0,0 +1,58 @@ +/*************************************************************************** +Embed a (sub) console in a menu item thing. + +you can print to these consoles with con_print("consolename", "text to be printed\n"); +they can be detached from the system console with con_getset("consolename", "hidden", "1"). be sure to getset(con, "close", "1") or unhide afterwards. +you can enumerate the active consoles with con="";while((con=con_getset(con,"next"))!=""){print(con_getset(con,"title"));} this is useful for finding xmpp conversations. you might then want to hide them afterwards. +*/ + +class mitem_console : mitem +{ + virtual void() item_remove = + { + strunzone(item_command); + super::item_remove(); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + local float ret; + if (scan == K_ESCAPE) //let the owning menu take it. + return FALSE; + if (down) + { + ret = con_input(item_command, IE_KEYDOWN, scan, char, 0); + if (scan == K_MOUSE1 || scan == K_MOUSE2) //grab mouse, so we still receive mouse up events. + { + ui.mgrabs = this; + ret = TRUE; + local mitem_frame p = item_parent; + p.item_focuschange(this, IF_KFOCUSED); + } + } + else + { + ret = con_input(item_command, IE_KEYUP, scan, char, 0); //note the engine never tries to cancel key up events anyway. + if (ui.mgrabs == this) + ui.mgrabs = __NULL__; + } + return ret; + }; + virtual void(mitem newfocus, float flag) item_focuschange = + { + con_input(item_command, IE_FOCUS, !!(item_flags&IF_MFOCUSED), !!(item_flags&IF_KFOCUSED), 0); + }; + virtual void(vector pos) item_draw = + { + con_input(item_command, IE_MOUSEABS, ui.mousepos[0] - pos_x, ui.mousepos[1] - pos_y, 0); + con_draw(item_command, pos, item_size, 12); //use a 12-point font, if we can. + }; + void() mitem_console = + { + item_flags |= IF_SELECTABLE; + item_command = strzone(item_command); + }; +}; + +#define menuitemconsole_spawn(conname) spawn(mitem_console, item_command:conname) + + diff --git a/menusys/mitem_desktop.qc b/menusys/mitem_desktop.qc new file mode 100644 index 000000000..36032da10 --- /dev/null +++ b/menusys/mitem_desktop.qc @@ -0,0 +1,465 @@ +/*************************************************************************** +desktop and top-level functions. +*/ + +#ifdef USEPOINTERS +typedef mitem mitem_desktop; +#else +class mitem_desktop : mitem_frame +{ + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float flag) item_focuschange; + virtual void(vector pos) item_draw; //draws the game, then calls mitem_frame::item_draw for children. +#ifndef MENU + virtual void(float seat, vector minpos, vector size) drawgame = __NULL__; //if overridden, should call renderscene and then draw whatever hud it needs to. this will do splitscreen efficiently and automatically. +#endif + void() mitem_desktop; //the constructor. uninteresting. just ensures the size defaults to fullscreen. +}; +#endif + +#if !defined(MENU) && !defined(CSQC_SIMPLE) +float sb_showscores; +#endif + +float drawfont; +float(string fontname, string fontmaps, string sizes, float slot, optional float fix_scale, optional float fix_voffset) loadfont = #357; + +void() mitem_desktop::mitem_desktop = +{ +#define menu_font_win autocvar(menu_font_win, "cour") +#define menu_font autocvar(menu_font, "") +#define menu_font_fallback autocvar(gl_font, "") + queryscreensize(); + + if (checkextension("DP_GFX_FONTS")) + { + //make sure we have a font that can cope with slightly up-scaled stuff. + //windows is special because we can load from the system fonts + string fontname = menu_font_fallback; + if (menu_font_win != "" && !strncasecmp(cvar_string("sys_platform"), "Win", 3)) + fontname = menu_font_win; + else if (menu_font != "") + fontname = menu_font; + drawfont = loadfont("", fontname, "8 12 16", -1); + } + + item_text = "desktop"; + if (!item_flags) + { + item_size = ui.screensize; + item_flags = IF_SELECTABLE | IF_MFOCUSED | IF_KFOCUSED | IF_RESIZABLE | IF_NOCURSOR; + } + + if (!ui.kgrabs && !ui.mgrabs && (item_flags & IF_NOCURSOR)) + { + ui.kgrabs = this; + ui.mgrabs = this; + } +}; + +void(mitem newfocus, float flag) mitem_desktop::item_focuschange = +{ + super::item_focuschange(newfocus, flag); + + if (flag & IF_KFOCUSED) + { + //if we're deselecting the current one, reenable grabs + if (newfocus == __NULL__) + { + if(item_flags & IF_NOCURSOR) + { + ui.kgrabs = this; + ui.mgrabs = this; + } + } + else + { + if (ui.kgrabs == this) + { + ui.kgrabs = __NULL__; + ui.mgrabs = __NULL__; + } + } + } +}; + +//the interact flag says that the mouse is held down on the desktop +float(vector pos, float scan, float char, float down) mitem_desktop::item_keypress = +{ + float oldfl = item_flags; + + if (down & 2) + { + down &= 1; + //if we're grabbing, then cancel if they press escape, otherwise block other items from taking the keys. + if (scan >= K_MOUSE1 && scan <= K_MOUSE5) + return 2; //block other wigits, don't cancel the event so the engine still does its thing + else + { + if (scan == K_ESCAPE && down) + { + local mitem sc; + //block the keyevent if we already have menus loaded but not focused (select one if we do). + for (sc = item_children; sc; sc = sc.item_next) + { + if (sc.item_flags & IF_SELECTABLE) + { + if (!item_kactivechild) + item_focuschange(sc, IF_KFOCUSED); + return 3; + } + } + //make sure our code takes it, instead of showing the engine menu... +#ifdef MENU + m_toggle(0); + return 3; +#else + return 2|CSQC_ConsoleCommand("togglemenu"); +#endif + } + return 2; + } + } + + if (mitem_frame::item_keypress(pos, scan, char, down)) + return TRUE; + + if (scan == K_MOUSE1 && down) + { +#if defined(CSQC) && defined(FTE_SPLITSCREEN) + if (numclientseats) + localcmd("in_forcesplitclient 0\n"); +#endif + if (item_flags & IF_NOCURSOR) + { + ui.kgrabs = this; + ui.mgrabs = this; + } + return TRUE; + } + if (scan == K_MOUSE2 && down) + { +#if defined(CSQC) && defined(FTE_SPLITSCREEN) + if (numclientseats > 3) + localcmd(strcat("in_forcesplitclient ", ftos(1 + ((ui.mousepos[0]>ui.screensize[0]/2)?1:0) + ((ui.mousepos[1]>ui.screensize[1]/2)?2:0)), "\n")); + else if (numclientseats > 1) + localcmd(strcat("in_forcesplitclient ", ftos(1 + floor(ui.mousepos[1]*numclientseats/ui.screensize[1])), "\n")); + else if (numclientseats) + localcmd("in_forcesplitclient 0\n"); +#endif + if (item_flags & IF_NOCURSOR) + { + ui.kgrabs = this; + ui.mgrabs = this; + } + return TRUE; + } + +#ifdef CSQC + //catch otherwise unhangled escape presses, just to be sure we can use escape to toggle the menu + if (scan == K_ESCAPE && down) + { + return CSQC_ConsoleCommand("togglemenu"); + } +#endif + return FALSE; +}; + +#if !defined(MENU) && !defined(CSQC_SIMPLE) +static vector(vector v) vtodpp = +{ + //so fucking disgustingly ugly. + if (dp_workarounds) + { + v_x *= cvar("vid_width") / cvar("vid_conwidth"); + v_y *= cvar("vid_height") / cvar("vid_conheight"); + } + return v; +}; + +//vector pmove_org; +void(float seat, vector minpos, vector size) mitem_desktop::drawgame_helper = +{ + if not(seat) + { + clearscene(); + addentities(MASK_ENGINE|MASK_VIEWMODEL); + } + else + { + setviewprop(VF_LPLAYER, seat); + setproperty(VF_VIEWENTITY, player_localentnum); + addentities(MASK_VIEWMODEL); //don't do mask_engine because that's already done + } +// if (dp_workarounds) +// setproperty(VF_ORIGIN, pmove_org); + setproperty(VF_MIN, minpos); + setproperty(VF_SIZE, vtodpp(size)); + + setproperty(VF_DRAWCROSSHAIR, (ui.mgrabs == this)); + + drawgame(seat, minpos, size); + if (mouseinbox(minpos, size)) + { + ui.havemouseworld = TRUE; + ui.mouseworldnear = unproject([ui.mousepos[0], ui.mousepos[1], 0]); + ui.mouseworldfar = unproject([ui.mousepos[0], ui.mousepos[1], 100000]); + } +}; +#endif + +void(vector pos) mitem_desktop::item_draw = +{ +#if !defined(MENU) && !defined(CSQC_SIMPLE) +//menuqc picks up the game/console as a background + string constate = serverkey("constate"); + if (constate != "" && constate != "active") //allow empty, so things still kinda work with dp too. + { + drawfill(pos, ui.screensize, '0 0 0', 1, 0); + } + else if (this.drawgame != __NULL__) + { +#ifdef FTE_SPLITSCREEN + if (numclientseats > 3) + { + drawgame_helper(0, [0, 0], 0.5*ui.screensize); + drawgame_helper(1, [ui.screensize[0]*0.5, 0], 0.5*ui.screensize); + drawgame_helper(2, [0, ui.screensize[1]*0.5], 0.5*ui.screensize); + drawgame_helper(3, [ui.screensize[0]*0.5, ui.screensize[1]*0.5], 0.5*ui.screensize); + setviewprop(VF_LPLAYER, 0); + } + else if (numclientseats > 2) + { + drawgame_helper(0, [0, 0], [ui.screensize[0], ui.screensize[1]*0.333]); + drawgame_helper(1, [0, ui.screensize[1]*0.333], [ui.screensize[0], ui.screensize[1]*0.333]); + drawgame_helper(2, [0, ui.screensize[1]*0.666], [ui.screensize[0], ui.screensize[1]*0.333]); + setviewprop(VF_LPLAYER, 0); + } + else if (numclientseats > 1) + { + drawgame(0, [0, 0], [ui.screensize[0], ui.screensize[1]*0.5]); + drawgame(1, [0, ui.screensize[1]*0.5], [ui.screensize[0], ui.screensize[1]*0.5]); + setviewprop(VF_LPLAYER, 0); + } + else +#endif + { + drawgame_helper(0, '0 0', ui.screensize); + } + } +#endif + super::item_draw(pos); + + if (ui.kgrabs == this) + { +#if defined(CSQC) && !defined(CSQC_SIMPLE) + if (sb_showscores && ui.mgrabs == this) + ui.mgrabs = __NULL__; + else +#endif + if (!ui.mgrabs) + ui.mgrabs = this; + } +}; + + +var string autocvar_cl_cursor = "gfx/cursor.lmp"; +var float autocvar_cl_cursorsize = 32; +var float autocvar_cl_cursorbias = 4; + +static var float oldgrabstate; //to work around a DP bug (as well as unnecessary spam) + +void(float force) items_updategrabs = +{ + if (!ui.mgrabs || !(ui.mgrabs.item_flags & IF_NOCURSOR)) + { + if (!oldgrabstate || force) + { + oldgrabstate = TRUE; +#ifdef MENU + setkeydest(2); + setmousetarget(2); +#else + setcursormode(TRUE); + //setcursormode(TRUE, autocvar_cl_cursor, autocvar_cl_cursorbias*'1 1', autocvar_cl_cursorscale); +#endif + } + } + else if (oldgrabstate || force) + { + oldgrabstate = FALSE; +#ifdef MENU + setkeydest(0); + setmousetarget(1); +#else + setcursormode(FALSE); +#endif + } +}; + +void(mitem_desktop desktop) items_draw = +{ + queryscreensize(); + +#ifdef MENU + ui.mousepos = getmousepos(); +#else + if (ui.havemouseworld) + ui.havemouseworld = 2; //stale, but not too stale +#endif + + if (desktop.item_size != ui.screensize) + { + desktop.item_size = ui.screensize; + desktop.item_resized(); + } + ui.drawrectmax = ui.screensize; + + desktop.item_draw(desktop.item_position); + drawresetcliparea(); + + items_updategrabs(FALSE); + if (dp_workarounds && oldgrabstate) + { + if (drawgetimagesize(autocvar_cl_cursor) == '0 0') + ui.drawcharacter(ui.mousepos - [stringwidth("+", TRUE, '4 4')*0.5, 4], '+', '8 8', '1 1 1', 1, 0); + else + ui.drawpic(ui.mousepos - autocvar_cl_cursorbias*'1 1', autocvar_cl_cursor, autocvar_cl_cursorsize*'1 1', '1 1 1', 1, 0); + } + +#ifndef MENU + if (ui.havemouseworld == 2) //if its still stale then its totally invalid. + ui.havemouseworld = FALSE; +#endif + ui.oldmousepos = ui.mousepos; +}; + +#ifdef CSQC +//items_keypress has quite strong dimorphism. These are meant to tailored to the target's available event notifications, rather than being really rather annoying. +csqconly float(mitem_desktop desktop, float evtype, float scanx, float chary, float devid) items_keypress = +{ + local float result = FALSE; + vector pos; + mitem p; + switch(evtype) + { + case IE_KEYDOWN: + case IE_KEYUP: + if (scanx == K_SHIFT) + ui.shiftheld = evtype==IE_KEYDOWN; +#ifdef HEIRACHYDEBUG + if (scanx == K_F1 && evtype == IE_KEYDOWN) + { + mitem_printtree(desktop, "items_keypress", __LINE__); + return TRUE; + } +#endif + if (scanx >= K_MOUSE1 && scanx <= K_MOUSE5) + { + if (ui.mgrabs) + { + pos = '0 0 0'; + for (p = ui.mgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.mgrabs.item_keypress(pos, scanx, chary, (evtype == IE_KEYDOWN)|2); + if (result & 2) + { + ui.mousedown = 0; + return result & 1; + } + } + } + else + { + if (ui.kgrabs) + { + pos = '0 0 0'; + for (p = ui.kgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.kgrabs.item_keypress(pos, scanx, chary, (evtype == IE_KEYDOWN)|2); + if (result & 2) + return result & 1; + } + } + if (desktop && desktop.item_keypress) + result = desktop.item_keypress(desktop.item_position, scanx, chary, evtype == IE_KEYDOWN); + if (scanx >= K_MOUSE1 && scanx <= K_MOUSE5) + { + if (evtype == IE_KEYDOWN) + ui.mousedown |= pow(1, scanx-K_MOUSE1); + else + ui.mousedown &~= pow(1, scanx-K_MOUSE1); + } + result = result & 1; + break; + case IE_MOUSEDELTA: + result = !ui.mgrabs || !(ui.mgrabs.item_flags & IF_NOCURSOR); + if (result) + { + queryscreensize(); + ui.mousepos[0] = bound(0, ui.mousepos[0]+scanx, ui.screensize[0]); + ui.mousepos[1] = bound(0, ui.mousepos[1]+chary, ui.screensize[1]); + } + break; + case IE_MOUSEABS: + ui.mousepos[0] = scanx; + ui.mousepos[1] = chary; + result = !ui.mgrabs || !(ui.mgrabs.item_flags & IF_NOCURSOR); + break; + } + return result; +}; +#endif + +#ifdef MENU +menuonly float(mitem_desktop desktop, float scan, float char, float down) items_keypress = +{ + local float result = FALSE; + local vector pos; + local mitem p; + ui.mousepos = getmousepos(); + queryscreensize(); +#ifdef HEIRACHYDEBUG + if (scan == K_F1 && down) + { + mitem_printtree(desktop, "items_keypress", __LINE__); + return TRUE; + } +#endif + + if (scan >= K_MOUSE1 && scan <= K_MOUSE5) + { + if (ui.mgrabs) + { + pos = '0 0 0'; + for (p = ui.mgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.mgrabs.item_keypress(pos, scan, char, (down)|2); + if (result & 2) + { + ui.mousedown = 0; + return result & 1; + } + } + } + else + { + if (ui.kgrabs) + { + pos = '0 0 0'; + for (p = ui.kgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.kgrabs.item_keypress(pos, scan, char, (down)|2); + if (result & 2) + return result & 1; + } + } + + if (desktop && desktop.item_keypress) + result = desktop.item_keypress(desktop.item_position, scan, char, down); + + return result; +}; +#endif + + diff --git a/menusys/mitem_edittext.qc b/menusys/mitem_edittext.qc new file mode 100644 index 000000000..825065a5c --- /dev/null +++ b/menusys/mitem_edittext.qc @@ -0,0 +1,98 @@ +/*************************************************************************** +editable text, directly linked to a cvar. +FIXME: This can only edit the end of the string. +*/ +class mitem_edit : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void() item_remove; + float spos; + + virtual void() item_resized = + { + if (isvalid(item_command)) + item_flags |= IF_SELECTABLE; + else + item_flags &= ~IF_SELECTABLE; + super::item_resized(); + }; +}; + +void() mitem_edit::item_remove = +{ + strunzone(item_text); + strunzone(item_command); + super::item_remove(); +}; + +void(vector pos) mitem_edit::item_draw = +{ + local string curval = get(item_command); + + super::item_draw(pos); + + pos_x += item_size_x / 2; +/* ui.drawfill(pos, [item_size_x/2, 1], TD_BOT, item_alpha, 0); + ui.drawfill(pos, [1, self.item_size_y - 1], TD_RGT, item_alpha, 0); + ui.drawfill(pos + [item_size_x/2-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); + ui.drawfill(pos + [0, item_size_y-1], [item_size_x/2, 1], TD_TOP, item_alpha, 0); +*/ pos_y += (item_size_y - item_scale)*0.5; + pos_x += 1; + + spos = min(spos, strlen(curval)); + if (((cltime*4)&1) && (item_flags & IF_KFOCUSED)) + curval = strcat(substring(curval, 0, spos), chr2str(0xe00b), substring(curval, spos+1, -1)); //replace the char with a box... ugly, whatever + ui.drawstring(pos, curval, '1 1 0' * item_scale, item_rgb, item_alpha, 0); +}; +float(vector pos, float scan, float char, float down) mitem_edit::item_keypress = +{ + if (!down) + return FALSE; + + local string curval = get(item_command); + spos = min(spos, strlen(curval)); + + if (scan == K_ESCAPE) + return FALSE; + else if (scan == K_LEFTARROW) + spos = max(spos-1, 0); + else if (scan == K_RIGHTARROW) + spos+=1; +/* else if (scan == K_MOUSE1) + { + //FIXME: figure out the spos for the cursor + return TRUE; + }*/ + else if (scan == K_BACKSPACE || scan == K_DEL) + { + if (spos) + { + curval = strcat(substring(curval, 0, spos-1), substring(curval, spos, -1)); + spos -= 1; + } + } + else if (char >= ' ') + { + curval = strcat(substring(curval, 0, spos), chr2str(char), substring(curval, spos, -1)); + spos += strlen(chr2str(char)); + } + else + return FALSE; + + set(item_command, curval); + return TRUE; +}; +mitem_edit(string text, string command, vector sz) menuitemeditt_spawn = +{ + mitem_edit n = spawn(mitem_edit); + n.item_scale = sz_y; + n.item_text = strzone(text); + n.item_size = sz; + n.spos = 100000; //will be clipped so meh + + n.item_command = strzone(command); + n.item_flags |= IF_SELECTABLE; + return n; +}; + diff --git a/menusys/mitem_exmenu.qc b/menusys/mitem_exmenu.qc new file mode 100644 index 000000000..7dbd8f468 --- /dev/null +++ b/menusys/mitem_exmenu.qc @@ -0,0 +1,58 @@ +/*************************************************************************** +fullscreen exclusive menu +you should only have ONE of these visible at once. +interactable - basically just a container for the items in the menu, but also handles killing them+itself when the user presses escape. +will keep stealing focus from the desktop, so you won't be able to play while one of these is active. +will not steal focus from siblings. this means console slideouts or whatever are still usable. + +these items will automatically be added to the desktop/workspace thing +Call it.item_remove() to pop the menu. +Regular items can be added to the menu by first spawning them, then calling menu_additem to actually add it to the desired menu in the right place. +*/ + +class mitem_exmenu : mitem_frame +{ + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + local float ret = super::item_keypress(pos, scan, char, down); + if (!ret && down) + { + ret = TRUE; + if (scan == K_MOUSE2 || scan == K_ESCAPE) + { + localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. + item_remove(); + } + else if (scan == K_UPARROW && down) + menu_selectnextitem(this, TRUE); + else if (scan == K_DOWNARROW && down) + menu_selectnextitem(this, FALSE); + else if (scan >= K_F1 && scan <= K_F12) //allow f1-f12 to work, but every other button event gets canceled. + ret = FALSE; + } + return ret; + }; + virtual void(vector pos) item_draw = + { + if (ui.mgrabs == item_parent || ui.kgrabs == item_parent) //if our parent has grabs, steal it instead, this means you can select the console, but the game never gets focus + { + if (item_flags & IF_SELECTABLE) + { + ui.mgrabs = __NULL__; + ui.kgrabs = __NULL__; + item_parent.item_focuschange(this, IF_KFOCUSED); + } + } + super::item_draw(pos); + }; + + //same as mitem, but does not propogate to parent. + virtual string(string key) get = + { + return cvar_string(key); + }; + virtual void(string key, string newval) set = + { + cvar_set(key, newval); + }; +}; \ No newline at end of file diff --git a/menusys/mitem_frame.qc b/menusys/mitem_frame.qc new file mode 100644 index 000000000..cf93a3109 --- /dev/null +++ b/menusys/mitem_frame.qc @@ -0,0 +1,675 @@ +/*************************************************************************** +Basic frame, containing sub-items. +Logically the parent of menu objects. Also used for tabs. +*/ + +#ifndef MITEM_FRAME_QC__ +#define MITEM_FRAME_QC__ + +class mitem_vslider : mitem +{ + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(vector pos) item_draw; + void() mitem_vslider; + float val; + float minv; + float maxv; + float stride; //size of one 'notch' +}; + +class mitem_frame : mitem +{ + virtual void(mitem newfocus, float changedflag) item_focuschange; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void() item_resized; + virtual void(vector pos) item_draw; + virtual void() item_remove; + virtual void(mitem fromitem, string cmd) item_execcommand; + + mitem item_children; + mitem item_mactivechild; + mitem item_kactivechild; + mitem item_exclusive; + + mitem_vslider vslider; //displayed if any client item's max[y] > our item_size[y] + vector item_framesize; //x=sides, y=top, z=bottom + float frame_hasscroll; + + static mitem(string title) findchildtext; + static mitem(string title) findchildcmd; + static void(mitem newitem, float originflags, vector originmin, vector originmax) add; + static void(mitem newitem, vector pos) adda; + static void(mitem newitem, vector originmin, vector originmax) addm; + static void(mitem newitem, float originflags, vector originmin, vector originmax) addr; + static void(mitem newitem, float ypos) addc; + + void() mitem_frame = + { + item_flags |= IF_ISFRAME; + }; +}; + +void(mitem fromitem, string cmd) mitem_frame::item_execcommand = +{ + if (item_parent) + item_parent.item_execcommand(fromitem, cmd); + else + localcmd(strcat(cmd, "\n")); +}; + +mitem(string title) mitem_frame::findchildtext = +{ + mitem ch; + for (ch = item_children; ch; ch = ch.item_next) + { + if (ch.item_text == title) + return ch; + } + return __NULL__; +}; +mitem(string title) mitem_frame::findchildcmd = +{ + mitem ch; + for (ch = item_children; ch; ch = ch.item_next) + { + if (ch.item_command == title) + return ch; + } + return __NULL__; +}; + + +//adds an item with the desired origin settings +void(mitem newitem, float originflags, vector originmin, vector originmax) mitem_frame::add = +{ + newitem.item_next = item_children; + item_children = newitem; + newitem.item_parent = this; + + //set up position and resize + newitem.resizeflags = originflags; + newitem.mins = originmin; + newitem.maxs = originmax; + mitem_parentresized(newitem, [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]); + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//adds an item to the parent with an absolute position relative to the parent's top-left. objects are expected to already have a size specified. +void(mitem newitem, vector pos) mitem_frame::adda = +{ + local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + newitem.item_next = item_children; + item_children = newitem; + newitem.item_parent = this; + + newitem.resizeflags = RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN; + newitem.mins = pos; + newitem.maxs = newitem.item_size; + + mitem_parentresized(newitem, parentsize); + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//adds an item to the parent in reverse order (ie: at the tail, so the actual order in code) +void(mitem newitem, float originflags, vector originmin, vector originmax) mitem_frame::addr = +{ + local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + if (item_children) + { + local mitem prev; + for (prev = item_children; prev.item_next; prev = prev.item_next) + ; + prev.item_next = newitem; + newitem.item_next = __NULL__; + } + else + { + newitem.item_next = item_children; + item_children = newitem; + } + newitem.item_parent = this; + + newitem.resizeflags = originflags; + newitem.mins = originmin; + newitem.maxs = originmax; + mitem_parentresized(newitem, parentsize); + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//adds an item on the parent with the x coord centered, and absolute y. +//if multiple items are at the same ypos, it will recenter all with respect to the others +void(mitem newitem, float ypos) mitem_frame::addc = +{ + float w, c; + local mitem prev; + local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + + newitem.item_next = item_children; + item_children = newitem; + newitem.item_position[1] = ypos; + newitem.item_position[0] = (parentsize[0] - newitem.item_size[0])*0.5; + newitem.item_parent = this; + + + newitem.resizeflags = RS_X_MIN_PARENT_MID | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN; + newitem.mins[0] = 0; + newitem.mins[1] = ypos; + newitem.maxs = newitem.item_size; + + //count the width of the other items at this height. + w = 0; + c = 0; + for (prev = item_children; prev; prev = prev.item_next) + if (prev.resizeflags == newitem.resizeflags && prev.mins[1] == ypos && prev.maxs[1] == newitem.maxs[1]) + { + w += prev.maxs[0]; + c += 1; + } + + //distribute them evenly (from the right, because its add-at-head) + w += (c-1)*16; //this much gap space + for (prev = item_children; prev; prev = prev.item_next) + { + if (prev.resizeflags == newitem.resizeflags && prev.mins[1] == ypos && prev.maxs[1] == newitem.maxs[1]) + { + w -= prev.maxs[0]; + prev.mins[0] = (w+prev.maxs[0])/-2; + mitem_parentresized(prev, parentsize); + w -= 16; + } + } + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//Adds the item in the exact middle of the parent, in both axis +void(mitem newitem, vector min, vector max) mitem_frame::addm = +{ + this.add(newitem, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, min, max); +}; + +//updates this.item_activechild, and calls focus notifications to ensure things get the message. +//flag should be IF_MFOCUSED or IF_KFOCUSED +void(mitem newfocus, float flag) mitem_frame::item_focuschange = +{ + local mitem it; + + if (newfocus == this) + { + //our focus didn't change, but the parent is telling us that *it* got changed while we're the focus + if (flag & IF_MFOCUSED) + { + if (item_parent.item_flags & IF_MFOCUSED && item_parent.item_mactivechild == this) + item_flags |= IF_MFOCUSED; + else + item_flags = item_flags - (item_flags&IF_MFOCUSED); + //and tell the child + it = item_mactivechild; + if (it) + if (it.item_focuschange) + it.item_focuschange(it, IF_MFOCUSED); + } + if (flag & IF_KFOCUSED) + { + if (item_parent.item_flags & IF_KFOCUSED && item_parent.item_kactivechild == this) + item_flags |= IF_KFOCUSED; + else + item_flags = item_flags - (item_flags&IF_KFOCUSED); + //and tell the child + it = item_kactivechild; + if (it) + if (it.item_focuschange) + it.item_focuschange(it, IF_KFOCUSED); + } + return; + } + + if ((flag & IF_MFOCUSED) && item_mactivechild != newfocus) + { + //make key focus follow the mouse cursor. this should probably only apply to button menus or something? :s + if (newfocus && (item_flags & IF_FOCUSFOLLOWSMOUSE)) + flag |= IF_KFOCUSED; + + it = item_mactivechild; + item_mactivechild = newfocus; + if (it) + { + it.item_flags = it.item_flags - (it.item_flags&IF_MFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_MFOCUSED); + } + it = item_mactivechild; + if (it) + { + it.item_flags = it.item_flags | (item_flags & IF_MFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_MFOCUSED); + } + } + + if ((flag & IF_KFOCUSED) && item_kactivechild != newfocus) + { + it = item_kactivechild; + item_kactivechild = newfocus; + if (it) + { + it.item_flags = it.item_flags - (it.item_flags&IF_KFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_KFOCUSED); + } + it = item_kactivechild; + if (it) + { + it.item_flags = it.item_flags | (item_flags & IF_KFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_KFOCUSED); + } + } +}; + + +float(vector pos, float scan, float char, float down) mitem_vslider::item_keypress = +{ + if (down != 1) + { + if (scan == K_MOUSE1 && ui.mgrabs == this) + ui.mgrabs = __NULL__; + return FALSE; + } + if (scan == K_MWHEELDOWN) + val = bound(minv, val+stride, maxv); + else if (scan == K_MWHEELUP) + val = bound(minv, val-stride, maxv); + else if (scan == K_MOUSE1) + ui.mgrabs = this; + else + return FALSE; + return TRUE; +}; +void(vector pos) mitem_vslider::item_draw = +{ + float f; + float w = item_size[0]; + float h = item_size[1]; + float isize = w; + + vector v = drawgetimagesize("scrollbars/slider.tga"); + + if (v != '0 0 0') + { + ui.drawpic(pos, "scrollbars/arrow_up.tga", [w, w], '1 1 1', item_alpha, 0); //top + pos_y += w; + h -= w*2; + ui.drawpic(pos + [0, h], "scrollbars/arrow_down.tga", [w, w], '1 1 1', item_alpha, 0); //bottom + ui.drawpic(pos, "scrollbars/slidebg.tga", [w, h], '1 1 1', item_alpha, 0); //back-middle + + isize = (v_y * w) / (v_x); + if (isize > h/2) + isize = h/2; + } + else + { +// ui.drawfill(pos, [w, w], '1 1 1', item_alpha, 0); //top +// pos_y += w; +// h -= w*2; +// ui.drawfill(pos + [0, h], [w, w], '1 1 1', item_alpha, 0); //bottom + ui.drawfill(pos, [w, h], '0.5 0.5 0.5', item_alpha, 0); //back-middle + } + + if (ui.mgrabs == this) + { + f = (ui.mousepos[1] - pos_y - (isize/2))/(h - isize); + f = bound(0, f, 1); + val = minv + (f * (maxv - minv)); + } + else + f = bound(0, (val - minv) / (maxv - minv), 1); + + h -= isize; + if (v != '0 0 0') + ui.drawpic(pos + [0, h*f], "scrollbars/slider.tga", [w, isize], '1 1 1', item_alpha, 0); //back-middle + else + ui.drawfill(pos + [0, h*f], [w, isize], '1 1 1', item_alpha, 0); //back-middle +}; +void() mitem_vslider::mitem_vslider = +{ + item_size[0] = 16; + item_size[1] = 128; +}; + +//does NOT wrap. +//does NOT pass go. +static mitem(mitem item, float upwards) menu_simplenextitem = +{ + mitem_frame menu = item.item_parent; + mitem prev; + if (upwards) + { + if (item) + { + item = item.item_next; + if (!item) + return __NULL__; + return item; + } + else + return __NULL__; + } + else + { + for(prev = menu.item_children; prev.item_next; prev = prev.item_next) + { + if (prev.item_next == item) + return prev; + } + return __NULL__; + } +}; + +//finds the next/prev item through multiple children, returning NULL when it runs out of items in the sequence. +//call this with item==null to find the first item in the sequence (to handle wraps). +static mitem(mitem_frame menu, float upwards, mitem item) menu_findnextitem = +{ + mitem_frame frame; + mitem citem; + + if (item && (item.item_flags & IF_ISFRAME)) + { + frame = (mitem_frame)item; + citem = menu_findnextitem(frame, upwards, frame.item_kactivechild?frame.item_kactivechild:frame.item_mactivechild); + if (citem) + return citem; + } + + for(;;) + { + if (!item) + { //we go for the opposite end here, as we assume to be starting/unfocused + item = menu.item_children; + if (!upwards && item) + { + while(item.item_next) + item = item.item_next; + } + } + else + item = menu_simplenextitem(item, upwards); + + if (!item) + { //we reached the end of the list, let the parent frame try its next + return __NULL__; + } + + if (item.item_flags & IF_ISFRAME) + { //if the next item is a frame, try and select its first element instead + frame = (mitem_frame)item; + citem = menu_findnextitem(frame, upwards, __NULL__); + if (citem) + return citem; + } + + if (item.item_flags & IF_INVISIBLE) + continue; + if (item.item_flags & IF_SELECTABLE) + return item; + } + +}; +static void(mitem item) menu_deselectitem = +{ + if (!item) + return; + if (item && (item.item_flags & IF_ISFRAME)) + { + mitem_frame frame = (mitem_frame)item; + if (frame.item_kactivechild) + menu_deselectitem(frame.item_kactivechild); + } + item.item_focuschange(__NULL__, IF_KFOCUSED); //deselect the previous one +}; +static void(mitem item) menu_selectitem = +{ + if (!item) + return; + mitem_frame menu = item.item_parent; + if (menu) + { + if (menu.item_kactivechild != item) + menu_deselectitem(menu.item_kactivechild); + menu_selectitem(menu); + menu.item_focuschange(item, IF_KFOCUSED); //focus on the new + } +}; +void(mitem_frame rootmenu, float upwards) menu_selectnextitem = +{ + mitem item = menu_findnextitem(rootmenu, upwards, rootmenu.item_kactivechild?rootmenu.item_kactivechild:rootmenu.item_mactivechild); + if (!item) + item = menu_findnextitem(rootmenu, upwards, __NULL__); + + menu_selectitem(item); + item.item_focuschange(item, IF_KFOCUSED); //focus on the new +}; + +float(vector pos, float scan, float char, float down) mitem_frame::item_keypress = +{ + mitem ch; + float handled = FALSE; + + //compensate for the frame + pos[0] += item_framesize[0]; + pos[1] += item_framesize[1]; + + if (scan >= K_MOUSE1 && scan <= K_MOUSE5 && scan != K_MWHEELUP && scan != K_MWHEELDOWN) + { + if (item_exclusive) + ch = item_exclusive; + else + ch = item_mactivechild; + if (down) //keyboard focus follows on mouse click. + { + item_focuschange(ch, IF_KFOCUSED); + } + } + else + { + ch = item_kactivechild; + if (!ch && down) + { + //if there's no key child active, then go and pick one so you can just start using keyboard access etc. + if (item_exclusive) + ch = item_exclusive; + else if (item_mactivechild) + ch = item_mactivechild; + else + { + mitem c; + for (c = item_children; c; c = c.item_next) + { + if (c.item_flags & IF_SELECTABLE) + ch = c; + } + } + if (ch) + item_focuschange(ch, IF_KFOCUSED); + ch = item_kactivechild; + } + } + + if (vslider) + pos[1] -= vslider.val; + + if (ch) + { + if (ch.item_keypress) + handled = ch.item_keypress(pos + ch.item_position, scan, char, down); + } + if (vslider && !handled && (scan == K_MWHEELUP || scan == K_MWHEELDOWN)) //give mwheel to the slider if its visible. + handled = vslider.item_keypress(pos + vslider.item_position, scan, char, down); + + return handled; +}; +void() mitem_frame::item_remove = +{ + local mitem ch; + while (this.item_children) + { + ch = this.item_children; + this.item_children = ch.item_next; + ch.item_remove(); + } + super::item_remove(); +}; +void() mitem_frame::item_resized = +{ + mitem ch; + vector framemax = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + for (ch = this.item_children; ch; ch = ch.item_next) + { + vector os = ch.item_size; + mitem_parentresized(ch, framemax); + if (ch.item_resized && ch.item_size != os) + ch.item_resized(); + } + item_flags |= IF_CLIENTMOVED; //make sure it happens. + + super::item_resized(); +}; +void(vector pos) mitem_frame::item_draw = +{ + local vector omin = ui.drawrectmin, omax = ui.drawrectmax; + local mitem ch; + local vector clientpos; + local vector clientsize; + + if (frame_hasscroll && (item_flags & IF_CLIENTMOVED)) + { + //if a client object moved, make sure the scrollbar is updated + item_flags &~= IF_CLIENTMOVED; + clientsize= '0 0'; + for(ch = item_children; ch; ch = ch.item_next) + { + if (clientsize[0] < ch.item_position[0] + ch.item_size[0]) + clientsize[0] = ch.item_position[0] + ch.item_size[0]; + if (clientsize[1] < ch.item_position[1] + ch.item_size[1]) + clientsize[1] = ch.item_position[1] + ch.item_size[1]; + } +// if (clientsize[0] > item_size[0] - item_framesize[0]*2) +// //add hscroll + if (clientsize[1] > item_size[1] - (item_framesize[1]+item_framesize[2])) + { + if (!vslider) + { + local mitem_vslider tmp; + tmp = spawn(mitem_vslider, val:0, stride:8); + vslider = tmp; + } + vslider.maxv = clientsize[1] - (item_size[1] - (item_framesize[1]+item_framesize[2])); + } + else if (vslider) + { + vslider.item_remove(); + vslider = (mitem_vslider)__NULL__; + } + } + else if (!frame_hasscroll && this.vslider) + { + vslider.item_remove(); + vslider = (mitem_vslider)__NULL__; + } + + + //compensate for the frame + pos[0] += item_framesize[0]; + pos[1] += item_framesize[1]; + + clientpos = pos; + clientsize = this.item_size; + clientsize[0] -= item_framesize[0]*2; + clientsize[1] -= (item_framesize[1]+item_framesize[2]); + + if (vslider) + { + //scroll+shrink the client area to fit the slider on it. + clientpos[1] -= vslider.val; + clientsize[0] -= vslider.item_size[0]; + } + + + if (ui.mousepos != ui.oldmousepos) + { + local mitem newfocus = __NULL__; + //if the mouse moved, select the new item + if (item_exclusive) + newfocus = item_exclusive; + else + { + for(ch = item_children; ch; ch = ch.item_next) + { + if (ch.item_flags & IF_SELECTABLE) + if (mouseinbox(clientpos + ch.item_position, ch.item_size)) + { + newfocus = ch; + } + } + } + if (vslider) + if (mouseinbox(pos + [clientsize[0], 0], vslider.item_size)) + newfocus = vslider; + this.item_focuschange(newfocus, IF_MFOCUSED); + } + + //clip the draw rect to our area, so children don't draw outside it. don't draw if its inverted. + if (pos_x > ui.drawrectmin[0]) + ui.drawrectmin[0] = pos_x; + if (pos_y > ui.drawrectmin[1]) + ui.drawrectmin[1] = pos_y; + if (pos_x+clientsize_x < ui.drawrectmax[0]) + ui.drawrectmax[0] = pos_x+clientsize_x; + if (pos_y+clientsize_y < ui.drawrectmax[1]) + ui.drawrectmax[1] = pos_y+clientsize[1]; + if (ui.drawrectmax[0] > ui.drawrectmin[0] && ui.drawrectmax[1] > ui.drawrectmin[1]) + { + ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); + if (item_exclusive) + item_exclusive.item_draw(clientpos + item_exclusive.item_position); + else + { + for(ch = item_children; ch; ch = ch.item_next) + { + if not (ch.item_flags & IF_INVISIBLE) + ch.item_draw(clientpos + ch.item_position); + } + } + ui.setcliparea(omin_x, omin_y, omax_x - omin_x, omax_y - omin_y); + + if (vslider) + { + vslider.item_size[1] = clientsize[1]; + vslider.item_draw(pos + [clientsize[0], 0]); + } + } + ui.drawrectmin = omin; + ui.drawrectmax = omax; +}; +#define menuitemframe_spawn(sz) spawn(mitem_frame, item_size:sz) + +#endif //MITEM_FRAME_QC__ diff --git a/menusys/mitem_menu.qc b/menusys/mitem_menu.qc new file mode 100644 index 000000000..ab7f261f8 --- /dev/null +++ b/menusys/mitem_menu.qc @@ -0,0 +1,332 @@ +/*************************************************************************** +window-like menu. +interactable - basically just a container for the items in the menu, but also handles killing them+itself when the user presses escape. + +these items will automatically be added to the desktop/workspace thing +Call it.item_remove() to pop the menu. +Regular items can be added to the menu by first spawning them, then calling menu_additem to actually add it to the desired menu in the right place. +*/ + +class mitem_menu : mitem_frame +{ + vector draginfo; //x, y, resizeflags={0:none, 1:left, 2:bottom, 4:right, 8:top, 16:move} + + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float flag) item_focuschange; + virtual void(vector pos) item_draw; + + virtual void() item_remove = + { + strunzone(item_text); + super::item_remove(); + }; + + nonvirtual float(vector pos) whichgrabhandle; + nonvirtual void(vector pos, float handle) drawgrabhandle; + void() mitem_menu = + { + item_text = strzone(item_text); + item_scale = 8; + item_rgb = MENUBACK_RGB; + item_alpha = MENUBACK_ALPHA; + + if (item_framesize == '0 0 0') + item_framesize = '4 16 4'; + + item_size[0] += item_framesize[0]; + item_size[1] += item_framesize[1]+item_framesize[2]; + + item_flags |= IF_SELECTABLE; + +#ifdef CSQC + cprint(""); +#endif + }; + + //same as mitem, but does not propogate to parent. + virtual string(string key) get = + { + return cvar_string(key); + }; + virtual void(string key, string newval) set = + { + cvar_set(key, newval); + }; +}; + +void(mitem newfocus, float flag) mitem_menu::item_focuschange = +{ + if (flag & IF_KFOCUSED) + { + //move the window to the top of the z-order when it gets focus. + if (item_flags & IF_KFOCUSED) + totop(); + //release grabs if someone else took focus. + else if (ui.mgrabs == this) + { + ui.mgrabs = __NULL__; + draginfo[2] = 0; + } + } + + super::item_focuschange(newfocus, flag); +}; + +void(vector pos, float handle) mitem_menu::drawgrabhandle = +{ + if ((handle & 3) == 3) + { + //bottom left + drawfill(pos + [0, item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]], '1 1 0', 1, 0); + handle &~= 3; + } + if ((handle & 6) == 6) + { + //bottom right + drawfill(pos + [item_size[0]-item_framesize[0], item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]], '1 1 0', 1, 0); + handle &~= 6; + } + if (handle & 1) + { + //left + drawfill(pos + [0, 0], [item_framesize[0], item_size[1]], '1 1 0', 1, 0); + } + if (handle & 2) + { + //bottom + drawfill(pos + [0, item_size[1]-item_framesize[2]], [item_size[0], item_framesize[2]], '1 1 0', 1, 0); + } + if (handle & 4) + { + //right + drawfill(pos + [item_size[0]-item_framesize[0], 0], [item_framesize[0], item_size[1]], '1 1 0', 1, 0); + } +}; + +float(vector pos) mitem_menu::whichgrabhandle = +{ + //handle dragging and resizing. + if (mouseinbox(pos, [item_size[0]-item_framesize[1], item_framesize[1]])) + { + //drag + return 16; + } + else if (this.item_flags & IF_RESIZABLE) + { + if (mouseinbox(pos + [0, item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]])) + { + //bottom-left + return 3; + } + else if (mouseinbox(pos + [item_size[0]-item_framesize[0], item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]])) + { + //bottom-right + return 6; + } + else if (mouseinbox(pos + [0, 0], [item_framesize[0], item_size[1]])) + { + //left + return 1; + } + else if (mouseinbox(pos + [0, item_size[1]-item_framesize[2]], [item_size[0], item_framesize[2]])) + { + //bottom + return 2; + } + else if (mouseinbox(pos + [item_size[0]-item_framesize[0], 0], [item_framesize[0], item_size[1]])) + { + //right + return 4; + } + } + return 0; +}; + +float(vector pos, float scan, float char, float down) mitem_menu::item_keypress = +{ + if (scan == K_MOUSE1 && (down&1)) + { + if (!(item_flags & IF_NOKILL) && mouseinbox(pos + [item_size[0]-item_framesize[1], 0], item_framesize[1] * '1 1')) + { + localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. + this.item_remove(); + return TRUE; + } + draginfo[2] = this::whichgrabhandle(pos); + if (draginfo[2]) + ui.mgrabs = this; + if (draginfo[2] & (1|16)) + draginfo[0] = ui.mousepos[0] - pos_x; + if (draginfo[2] & 2) + draginfo[1] = (ui.mousepos[1] - pos_y - item_size[1]) + item_position[1]; + if (draginfo[2] & 4) + draginfo[0] = ui.mousepos[0] - pos_x - item_size[0] + item_position[0]; + if (draginfo[2] & (8|16)) + draginfo[1] = ui.mousepos[1] - pos_y; + if (draginfo[2]) + return TRUE; + } + else if (scan == K_MOUSE1 && draginfo[2]) + { + if (!draginfo[2] && (item_flags & IF_RESIZABLE)) + { + //if they dropped the window at the top of the screen, make sure its fullscreened to match how it was drawn... + queryscreensize(); + if (ui.mousepos[1] < 8) + item_size = ui.screensize; + if (this.item_resized) + item_resized(); + } + ui.mgrabs = __NULL__; + draginfo[2] = 0; + return TRUE; + } + + if (!super::item_keypress(pos, scan, char, down)) + { + down &= 1; + if (scan == K_ESCAPE && down) + { + if (this.item_flags & IF_NOKILL) + return FALSE; + else + { + localcmd(strcat(this.item_command, "\n")); //console command to exec if someone clicks the close button. + this.item_remove(); + return TRUE; + } + } + else if (scan == K_UPARROW && down) + menu_selectnextitem(this, TRUE); + else if (scan == K_DOWNARROW && down) + menu_selectnextitem(this, FALSE); + else if (scan == K_MOUSE2 && down && !(this.item_flags & IF_NOKILL)) + { //unhandled right click closes menus, if we're allowed + localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. + this.item_remove(); + return TRUE; + } + else if (scan >= K_MOUSE1 && scan <= K_MWHEELDOWN) + return TRUE; //don't let the parent take unhandled mouse events. + else + return FALSE; + } + return TRUE; +}; +void(vector pos) mitem_menu::item_draw = +{ + float inset; + vector efsize = item_size; + if (draginfo[2]) + { + if (draginfo[2] & 1) + { //resize left + local float nmax = item_position[0] + item_size[0]; + local float nmin = item_position[0] + item_size[0]; + nmin = ui.mousepos[0] - (pos_x - item_position[0]) - draginfo[0]; + if (nmax-nmin < 128) + nmin = nmax - 128; + pos_x -= item_position[0]; + item_position[0] = nmin; + pos_x += item_position[0]; + item_size[0] = nmax - nmin; + if (item_resized) + item_resized(); + } + if (draginfo[2] & 2) + { //resize bottom + item_size[1] = ui.mousepos[1] - (pos_y - item_position[1]) - draginfo[1]; + if (item_size[1] < 16) + item_size[1] = 16; + if (item_resized) + item_resized(); + } + if (draginfo[2] & 4) + { //resize right + item_size[0] = ui.mousepos[0] - (pos_x - item_position[0]) - draginfo[0]; + if (item_size[0] < 128) + item_size[0] = 128; + if (item_resized) + item_resized(); + } + efsize = item_size; + if (draginfo[2]&16) + { + queryscreensize(); + if (ui.mousepos[1] < 8 && (item_flags & IF_RESIZABLE)) + { + //make it look as though its fullscreen, without destroying its actual size. + //yes, we will fake-resize everything inside. + item_position = '0 0'; + + item_size = ui.screensize; + if (this.item_resized) + this.item_resized(); + item_size = efsize; + efsize = ui.screensize; + } + else if (item_size == ui.screensize && (item_flags & IF_RESIZABLE)) + { + item_size = item_size*0.5; + draginfo[0] = draginfo[0]*0.5; + if (this.item_resized) + this.item_resized(); + efsize = item_size; + } + else + { + item_position = ui.mousepos - (pos - item_position) - draginfo; + item_position[2] = 0; + if (item_flags & IF_RESIZABLE) + if (item_resized) + item_resized(); + } + } + } + + //bound it to the screen... just in case the screen got resized or something + if (item_position[0] > ui.screensize[0] - item_size[0]*0.25) + item_position[0] = ui.screensize[0] - item_size[0]*0.25; + if (item_position[1] > ui.screensize[1] - item_framesize[1]) + item_position[1] = ui.screensize[1] - item_framesize[1]; + if (item_position[0] < item_size[0]*-0.75) + item_position[0] = item_size[0]*-0.75; + if (item_position[1] < 0) + item_position[1] = 0; + + local float drag = 0; + if (draginfo[2]) + drag = draginfo[2]; + else if (item_flags & IF_MFOCUSED) + drag = this::whichgrabhandle(pos); + if (drag) + this::drawgrabhandle(pos, drag); + + ui.drawfill(pos, efsize, item_rgb, item_alpha, 0); + local vector col = '1 1 1'; + if (item_flags & IF_KFOCUSED) + col_x = 0; + if ((item_flags & IF_MFOCUSED) && mouseinbox(pos, [efsize_x-item_framesize[1], item_framesize[1]])) + col_z = 0; + inset = (item_framesize[1]-item_scale)*0.5; + ui.drawstring(pos + '1 1'*inset, item_text, item_scale*'1 1', col, 1, 0); + if (!(item_flags & IF_NOKILL)) + { + if (mouseinbox(pos + [efsize_x-item_framesize[1], 0], item_framesize[1]*'1 1')) + col = '1 1 0'; + else + col = '1 1 1'; + ui.drawstring(pos + [efsize_x+inset-item_framesize[1], inset], "X", item_scale*'1 1', col, 1, 0); + } + super::item_draw(pos); +}; +mitem_menu(mitem_frame desktop, string mname, vector sz) menu_spawn = +{ + mitem_menu n = spawn(mitem_menu, item_text:mname, item_framesize:'4 16 4', item_size:sz); + +// n.item_flags |= IF_RESIZABLE; + + if (desktop) + desktop.addr(n, RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN, (desktop.item_size - n.item_size) * 0.5, n.item_size); + return n; +}; diff --git a/menusys/mitem_slider.qc b/menusys/mitem_slider.qc new file mode 100644 index 000000000..c671df859 --- /dev/null +++ b/menusys/mitem_slider.qc @@ -0,0 +1,153 @@ +/*************************************************************************** +slider item, directly attached to a cvar. +interactable - executes a given console command. +*/ +class mitem_hslider : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + + vector item_slidercontrols; //min, max, step + + virtual void() item_resized = + { + if (isvalid(item_command)) + item_flags |= IF_SELECTABLE; + else + item_flags &= ~IF_SELECTABLE; + super::item_resized(); + }; +}; +void(vector pos) mitem_hslider::item_draw = +{ + local float curval; + vector rgb = self.item_rgb; + if (!(item_flags & IF_SELECTABLE)) + rgb *= 0.2; + + super::item_draw(pos); + pos_x += item_size_x / 2; + + if (ui.mgrabs == this) + { + //if we're sliding it, update the value + curval = (ui.mousepos[0] - pos_x-8) / (10*8); + curval = bound(0, curval, 1); + curval = curval * (item_slidercontrols_y - item_slidercontrols_x); + if (!ui.shiftheld) + curval = rint(curval / item_slidercontrols_z) * item_slidercontrols_z; //round it. + curval += item_slidercontrols_x; + set(item_command, sprintf("%g", curval)); + } + curval = stof(get(item_command)); + + if (dp_workarounds) + { //no ^U markup support. chr2str avoids warnings about non-utf8 strings which at least allows bi-compat to work. + string s = strcat(chr2str(0xe080, 0xe081, 0xe081, 0xe081, 0xe081, 0xe081), chr2str(0xe081, 0xe081, 0xe081, 0xe081, 0xe081, 0xe082)); + //slider background uses the fallback quake chars + ui.drawstring(pos, sprintf("%s (%g)", s, curval), '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); + //now draw an indicater char in the right place. + //the inner side of the boundary is 4 pixels wide so we can overlap the ends by that many pixels. + curval = (curval - self.item_slidercontrols_x) / (self.item_slidercontrols_y - self.item_slidercontrols_x); //fractionize it + curval = bound(0, curval, 1); + ui.drawstring(pos + [4 + curval*10*8, 0], chr2str(0xe083), '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); + } + else + { + //slider background uses the fallback quake chars + ui.drawstring(pos, sprintf("^{e080}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e082} (%g)", curval), '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); + //now draw an indicater char in the right place. + //the inner side of the boundary is 4 pixels wide so we can overlap the ends by that many pixels. + curval = (curval - self.item_slidercontrols_x) / (self.item_slidercontrols_y - self.item_slidercontrols_x); //fractionize it + curval = bound(0, curval, 1); + ui.drawstring(pos + [4 + curval*10*8, 0], "^{e083}", '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); + } +}; +float(vector pos, float scan, float char, float down) mitem_hslider::item_keypress = +{ + if (down&2) + { + //we have grabs, and mouse was released? + if (scan == K_MOUSE1 && !(down&1)) + { //we're done here. + ui.mgrabs = __NULL__; + return TRUE; + } + return FALSE; //not handled, don't inhibit + } + if (down) + { + local float curval = stof(get(item_command)); + if (scan == K_MWHEELUP || scan == K_MWHEELDOWN) + { + if (ui.mousepos[0] > pos_x + item_size[0]/2) + scan = ((scan == K_MWHEELDOWN)?K_LEFTARROW:K_RIGHTARROW); + } + if (scan == K_MOUSE1 && down) + { + pos_x += item_size_x / 2; + if (ui.mousepos[0] < pos_x) + return TRUE;//goto keyenter; + curval = (ui.mousepos[0] - pos_x-8) / (10*8); + if (curval < 0 || curval > 1) + return FALSE; + curval = curval * (item_slidercontrols_y - item_slidercontrols_x) + item_slidercontrols_x; + set(item_command, sprintf("%g", curval)); + ui.mgrabs = this; + } + else if (scan == K_DEL && down && cvar_type(item_command)) + set(item_command, cvar_defstring(item_command)); + else if ((scan == K_LEFTARROW || scan == K_MWHEELUP) && down) + { + if (item_slidercontrols_x > item_slidercontrols_y) + set(item_command, sprintf("%g", min(curval - item_slidercontrols_z, item_slidercontrols_x))); + else + set(item_command, sprintf("%g", max(curval - item_slidercontrols_z, item_slidercontrols_x))); + } + else if ((scan == K_RIGHTARROW || scan == K_MWHEELDOWN) && down) + { + if (item_slidercontrols_x > item_slidercontrols_y) + set(item_command, sprintf("%g", max(curval + item_slidercontrols_z, item_slidercontrols_y))); + else + set(item_command, sprintf("%g", min(curval + item_slidercontrols_z, item_slidercontrols_y))); + } + else if ((scan == K_ENTER || scan == K_SPACE) && down) + { +//keyenter: + if (item_slidercontrols_x > item_slidercontrols_y) + { + if (curval-0.001 <= item_slidercontrols_y) + set(item_command, sprintf("%g", item_slidercontrols_x)); + else + set(item_command, sprintf("%g", max(curval + item_slidercontrols_z, item_slidercontrols_y))); + } + else + { + if (curval+0.001 >= item_slidercontrols_y) + set(item_command, sprintf("%g", item_slidercontrols_x)); + else + set(item_command, sprintf("%g", min(curval + item_slidercontrols_z, item_slidercontrols_y))); + } + } + else + return FALSE; + return TRUE; + } + else if (scan == K_MOUSE1 && ui.mgrabs == this) + ui.mgrabs = __NULL__; + return FALSE; +}; +mitem_hslider(string text, string command, vector controls, vector sz) menuitemslider_spawn = +{ + mitem_hslider n = spawn(mitem_hslider); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + + n.item_slidercontrols = controls; + + n.item_command = command; + if (n.isvalid(command)) + n.item_flags |= IF_SELECTABLE; + return n; +}; diff --git a/menusys/mitem_spinnymodel.qc b/menusys/mitem_spinnymodel.qc new file mode 100644 index 000000000..c59ed59b0 --- /dev/null +++ b/menusys/mitem_spinnymodel.qc @@ -0,0 +1,172 @@ +/* +renderscene stuff is always available in csqc. +If the engine supports DP_QC_RENDER_SCENE then its meant to be available in menuqc too. +In practise, DP advertises this to menuqc even though it has never officially supported it. +At the time of writing, FTE's dev builds can do it (and only advertise the extension in builds that try to support it). + +Note: the basemenu mod does not make use of this in menuqc if only because its vaugely trying to support both engines, and its easier to just use an ifdef. + +The mitem_spinnymodel item just shows a rotating model centered on the z axis. Simple as that. +For the sake of fun gimmicks, you can specify frame information with firstframe and framecount. +Additionally, you can cause it to switch to a different animation when it faces towards the camera with shootframe and shootframes. +This style of animation shouldn't really be considered very complex, but might help give a small demo of the basic concept of animation. +*/ +/* +Note - DP Bugs: +1: viewport positions are interpreted in physical pixels in DP. + there is no reliable way to know how many physical pixels there actually are. + custom viewports are thus near unusable. +2: failure to revert the viewport to default afterwards fucks over any 2d stuff drawn afterwards. + including the console. +3: bloom on worldless models draws the world. +4: lighting is applied on models even if there's no world. + enabling rtlights really fucks everything up. +5: gamma/contrast/brightness are applied to the 3d view. even if there's no world. + this results in horrible squares if these settings are used. +6: DP_QC_RENDER_SCENE is advertised in menuqc, but renderscene is not supported there at all. +7: avoid the use of cltime. DP doesn't support it. + +probably others. it really wouldn't surprise me. +*/ + +//helper function to work around a DP bug. +static vector(vector v) vtodpp = +{ +#pragma warning disable F333 +#ifndef CSQC_SIMPLE + //so fucking disgustingly ugly. + if (dp_workarounds) + { + v_x *= cvar("vid_width") / cvar("vid_conwidth"); + v_y *= cvar("vid_height") / cvar("vid_conheight"); + } +#endif + return v; +#pragma warning enable F333 +}; +//make sure the fields are all defined if we're in menuqc or something +noref .vector origin; +noref .vector angles; +noref .vector mins; +noref .vector maxs; +noref .string model; +noref .float frame, frame2, lerpfrac, renderflags; +noref .string skin; +float frametime; +class mitem_spinnymodel : mitem +{ + float zbias; + float firstframe; + float framecount; + float shootframe; + float shootframes; + float dontrotate; + float rotatespeed; + vector startangle; + string customskin; + string trueskin; + float topcolour; + float bottomcolour; + float fov; + + //angles.y = startangle; +#ifndef EXT_CSQC + virtual void(vector pos) item_draw = {}; +#else + + //might as well use the class as the entity that is drawn. + //it might end up clobbering any fields used for multiple things... + virtual void(vector pos) item_draw = + { + vector orgbias; + orgbias = '0 0 0'; + if (dp_workarounds) + orgbias = (vector)getviewprop(VF_ORIGIN); //DP still lights the entity even if the world isn't drawn. this results in inconsistant/buggy light levels. there's nothing we can do about that other than stopping it from being completely black. + origin = orgbias; + origin_z += zbias; + +// angles_y = cltime*90;//frametime*90; +// angles_y += frametime*90; + if(dontrotate) { + angles = startangle; + } else { + if(!rotatespeed) + rotatespeed = 90; + angles_y += frametime*rotatespeed; + } + + clearscene(); //wipe the scene, and apply the default rendering values. + setviewprop(VF_MIN, vtodpp(pos)); //min pos + setviewprop(VF_SIZE, vtodpp(item_size)); //size=maxpos-minpos + if(!fov) + fov = 30; + if (dp_workarounds) + setviewprop(VF_FOV, [fov, atan(item_size_y/(item_size_x/tan(fov/360*6.28))) * 360/6.28]); //set an explicit fov. this thing had better be square. DP doesn't support VF_AFOV + else + setviewprop(VF_AFOV, fov); //set the aproximate fov (ie: engine takes care of aspect ratio). +#ifdef CSQC + setproperty(VF_DRAWENGINESBAR, FALSE); + setproperty(VF_DRAWWORLD, FALSE); + setproperty(VF_DRAWCROSSHAIR, FALSE); +#endif + setviewprop(VF_ORIGIN, orgbias + '-128 0 0'); //look towards it. + setviewprop(VF_ANGLES, '0 0 0'); + + //animate it a bit + lerpfrac -= frametime*10; + while(lerpfrac < 0) + { + lerpfrac += 1; + frame2 = frame; + frame += 1; + if (angles_y >= 170 && shootframes && !dontrotate) + { + if (frame == shootframe+shootframes) + { //reached the last frame. clear the shooting 'flag' and go back to idle + frame = firstframe; + angles_y -= 360; + } + else if (frame < shootframe || frame >= shootframe+shootframes) + frame = shootframe; //we were still idle, apparently. + } + else + { + if (frame < firstframe || frame >= firstframe+framecount) + frame = firstframe; + } + } + + addentity(this); + renderscene(); + if (dp_workarounds) + { //dp fucks up 2d stuff if we don't explicitly restore the 3d view to fullscreen + setviewprop(VF_MIN, vtodpp('0 0 0')); + setviewprop(VF_SIZE, vtodpp(ui.screensize)); + } + setcustomskin(self, "", sprintf("q1upper \"%f\"\nq1lower \"%f\"\nqwskin \"%s\"\n", topcolour, bottomcolour, customskin)); + skin = trueskin; + //setcustomskin(self, skin, ""); + //setcustomskin(self, "", sprintf("q1upper \"%s\"\nq1lower \"%s\"\n\n", cvar_string("topcolor"), cvar_string("bottomcolor"))); + }; + + virtual void(vector pos) dp_draw = + { + }; + + void() mitem_spinnymodel = + { +#if defined(MENU) || defined(CSQC_SIMPLE) + if (!checkextension("DP_QC_RENDER_SCENE") || dp_workarounds) + { + item_draw = dp_draw; + return; + } +#endif + precache_model(item_text); + setmodel(this, item_text); //use the size information from the engine, woo for unreliability. + zbias += (mins_z - maxs_z)/2 - mins_z; //center the model on its z axis, so the whole thing is visible. + frame = firstframe; + }; +#endif +}; + diff --git a/menusys/mitem_tabs.qc b/menusys/mitem_tabs.qc new file mode 100644 index 000000000..acd684eb5 --- /dev/null +++ b/menusys/mitem_tabs.qc @@ -0,0 +1,168 @@ +/*************************************************************************** +tabs/tab widgets. +the 'tabs' widget is simply a tab-selection control. horizontal multiple choice. it draws only its currently active child. +the 'tab' widget is merely a container of other widgets, no different from a standard frame object, just has a name and a specific size. +*/ +class mitem_tabs : mitem_frame /*frame... but not really*/ +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; +// virtual void() item_resize; + + void() mitem_tabs = + { + item_framesize = '2 16 2'; + item_flags |= IF_SELECTABLE|IF_RESIZABLE; + }; +}; + +class mitem_tab : mitem_frame +{ + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (scan == K_UPARROW && down) + menu_selectnextitem(this, TRUE); + else if (scan == K_DOWNARROW && down) + menu_selectnextitem(this, FALSE); + else if (super::item_keypress(pos, scan, char, down)) + return TRUE; + else + return FALSE; + return TRUE; + }; + + void() mitem_tab = + { + item_framesize = '0 0 0'; + item_flags |= IF_SELECTABLE|IF_RESIZABLE; + }; +}; + +void(vector pos) mitem_tabs::item_draw = +{ + local mitem ch; + local vector tpos = pos; + local float w; + local vector col; + + //to highlight the active tab, we draw the top line 1 pixel higher, and no bottom line + for (ch = item_children; ch; ch = ch.item_next) + { + w = stringwidth(ch.item_text, TRUE, '8 8') + 8; + + ui.drawfill(tpos + '0 1', [1, 15], TD_LFT, item_alpha, 0); + ui.drawfill(tpos + [w-1, 1], [1, 14], TD_RGT, item_alpha, 0); + if (ch == item_kactivechild) + { + //top line + ui.drawfill(tpos, [w, 1], TD_TOP, item_alpha, 0); + } + else + { + //top line + ui.drawfill(tpos + '0 1', [w, 1], TD_TOP, item_alpha, 0); + //bottom + ui.drawfill(tpos + '0 15', [w, 1], TD_TOP, item_alpha, 0); + } + + col = item_rgb; + if (!(ch.item_flags & IF_SELECTABLE)) + col *= 0.2; + else + { + if (!item_kactivechild) + item_focuschange(ch, IF_KFOCUSED); + if (mouseinbox(tpos, [w, 16])) + col_z = 0; + if (ch.item_flags & IF_KFOCUSED) + col_x = 0; + } + + ui.drawstring(tpos + '4 4', ch.item_text, '8 8', col, item_alpha, 0); + tpos_x += w; + } + ui.drawfill(tpos + '0 15', [pos_x + item_size_x - tpos_x, 1], TD_TOP, item_alpha, 0); //top + ui.drawfill(pos + '0 16', [1, item_size_y - 16], TD_LFT, item_alpha, 0); //left + ui.drawfill(pos + [item_size_x-1, 16], [1, item_size_y - 17], TD_RGT, item_alpha, 0); //right + ui.drawfill(pos + [1, item_size_y-1], [item_size_x-1, 1], TD_BOT, item_alpha, 0); //bottom + + if (item_mactivechild != item_kactivechild) + item_focuschange(item_kactivechild, IF_MFOCUSED); //give the tab full focus. + ch = item_kactivechild; + if (ch) + ch.item_draw(pos + ch.item_position + [item_framesize[0], item_framesize[1]]); +}; +float(vector pos, float scan, float char, float down) mitem_tabs::item_keypress = +{ + local mitem ch; + local vector tpos = pos; + local vector sz = '0 0 0'; + local float result; + + if (down && (scan == K_MOUSE1 || scan == K_MOUSE2 || scan == K_MOUSE3)) + { + sz_y = 16; + //to highlight the active tab, we draw the top line 1 pixel higher, and no bottom line + for (ch = this.item_children; ch; ch = ch.item_next) + { + sz_x = stringwidth(ch.item_text, TRUE, '8 8') + 8; + if (mouseinbox(tpos, sz)) + { + item_focuschange(ch, IF_KFOCUSED); //give the tab full focus. + return TRUE; + } + + tpos_x += sz_x; + } + } + ch = item_kactivechild; + if (ch) + { + result = ch.item_keypress(pos + [item_framesize[0], item_framesize[1]] + ch.item_position, scan, char, down); + if (!result && down) + { + if (scan == K_TAB || scan == K_RIGHTARROW) + { + ch = ch.item_next; + if (!ch) + ch = item_children; + item_focuschange(ch, IF_KFOCUSED); + result = TRUE; + } +// else if (scan == K_LEFTARROW) +// { +// ch = ch.item_next; +// if (!ch) +// ch = item_children; +// item_focuschange((ch.item_next?ch.item_next:this.item_children), IF_KFOCUSED); +// result = TRUE; +// } + } + } + else + result = FALSE; + return result; +}; +/*void() mitem_tabs::item_resize = +{ + local mitem ch; + for (ch = this.item_children; ch; ch = ch.item_next) + { + ch.item_size = this.item_size; + if (ch.item_resized) + ch.item_resized(); + } +};*/ + +mitem_tabs(vector sz) menuitemtabs_spawn = +{ + return spawn(mitem_tabs, item_size:sz); +}; +mitem_tab(mitem_tabs tabs, string itname) menuitemtab_spawn = +{ + //a tab itself is little different from a frame, just has no implicit focus, and has a title + mitem_tab n = spawn(mitem_tab, item_text:itname, frame_hasscroll:TRUE); + + tabs.addr(n, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + return n; +}; diff --git a/menusys/mitems.qc b/menusys/mitems.qc new file mode 100644 index 000000000..ee62af9d8 --- /dev/null +++ b/menusys/mitems.qc @@ -0,0 +1,441 @@ +/*all these .funcs etc need self assigned properly first, as is customary with qc*/ + +#ifdef MENU +#define cltime time +#endif + +#ifdef CSQC +#define csqconly +#else +#define csqconly __strip +#endif +#ifdef MENU +#define menuonly +#else +#define menuonly __strip +#endif + + + +//#include "mitems.qh" + +__strip var float dp_workarounds; + +#define IF_SELECTABLE (1<<0) //can accept KFOCUSED/MFOCUSED and key events etc. cannot be selected otherwise. +#define IF_INTERACT (1<<1) //generic interaction flag for use by the widgets themselves. +#define IF_RESIZABLE (1<<2) //may be resized, either by parent (it takes the full space) or by user. +#define IF_MFOCUSED (1<<3) //mouse is currently sitting over it +#define IF_KFOCUSED (1<<4) //has keyboard focus +#define IF_NOKILL (1<<5) //kill button is disabled. move to frame/menu? +#define IF_DRAGGABLE (1<<6) //can be dragged. this is stupid. +#define IF_DROPPABLE (1<<7) //dragged items can be dropped on this item. +#define IF_CLIENTMOVED (1<<8) //recalc required client dimensions (and toggle scrollbars if needed). move to frame? +#define IF_CENTERALIGN (1<<9) // +#define IF_RIGHTALIGN (1<<10) // +#define IF_NOCURSOR (1<<11) //when mgrabbed, the cursor will not be shown +#define IF_ISFRAME (1<<12) //is derived from mitem_frame (helps debugging recurion). +#define IF_FOCUSFOLLOWSMOUSE (1<<13) //on frames, child keyboard focus (mostly) follows the mouse cursor. not like windows, but handy on things with lots of buttons. annoying on the desktop. move to frame? +#define IF_INVISIBLE (1<<14) + +#define RS_X_MIN_PARENT_MIN 0x0000 +#define RS_X_MIN_PARENT_MID 0x0001 +#define RS_X_MIN_PARENT_MAX 0x0002 +#define RS_X_FRACTION 0x0004 + +#define RS_X_MAX_OWN_MIN 0x0000 +#define RS_X_MAX_PARENT_MIN 0x0010 +#define RS_X_MAX_PARENT_MID 0x0020 +#define RS_X_MAX_PARENT_MAX 0x0040 + +#define RS_Y_MIN_PARENT_MIN 0x0000 +#define RS_Y_MIN_PARENT_MID 0x0100 +#define RS_Y_MIN_PARENT_MAX 0x0200 +#define RS_Y_FRACTION 0x0400 + +#define RS_Y_MAX_OWN_MIN 0x0000 +#define RS_Y_MAX_PARENT_MIN 0x1000 +#define RS_Y_MAX_PARENT_MID 0x2000 +#define RS_Y_MAX_PARENT_MAX 0x4000 + +//the 3d effect needs some sort of fake lighting values. +//these are for bumps. invert for inset things. +#define TD_TOP '0.8 0.8 0.8' +#define TD_LFT '0.6 0.6 0.6' +#define TD_RGT '0.2 0.2 0.2' +#define TD_BOT '0.0 0.0 0.0' + +#ifndef MENUBACK_RGB +#define MENUBACK_RGB '0.4 0.365 0.29' +//#define MENUBACK_RGB '0.5 0.5 0.6' +#endif +#ifndef MENUBACK_ALPHA +#define MENUBACK_ALPHA 0.8 +#endif + +//#ifdef TARGET_FTE +//#pragma TARGET FTE +//#endif + +//#define HEIRACHYDEBUG //enable this and press f1 to print out the current menuitem tree + +class mitem_frame; +.vector mins; //gravity mins +.vector maxs; //gravity mins +class mitem +{ + void() mitem; + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress = {return FALSE;}; + virtual void(mitem newfocus, float changedflag) item_focuschange; //move into frame? + virtual string(string key) get; + virtual void(string key, string newval) set; + virtual float(string key) isvalid; + virtual void() item_remove; + virtual void() item_resized; + float item_flags; //contains IF_ flags + vector item_position; //relative to the parent's client area. which is admittedly not well defined... + vector item_size; //interaction bounding box. + float item_scale; //text etc scale + vector item_rgb; //colours! + float item_alpha; //transparency value + string item_text; //used as a unique identifier + string item_command; //something to do when clicked. move out? + mitem_frame item_parent; //the item that contains us. make mitem_frame? + mitem item_next; //the next child within the parent + + float resizeflags; + + + static void() totop; +}; + +//this struct is used to access the various drawing functions. this allows the functions to be replaced for worldspace stuff +typedef struct uiinfo_s +{ + void(float min_x, float min_y, float max_x, float max_y) setcliparea; + float(vector min, string imagename, vector size, vector col, float alph, float drawflag) drawpic; + float(vector min, vector size, vector col, float alph, float drawflag) drawfill; + float(vector min, float charcode, vector scale, vector col, float alph, float drawflag) drawcharacter; + float(vector min, string text, vector scale, vector col, float alph, float drawflag) drawstring; + + mitem kgrabs; //kfocused or mfocused or both or none to say what sort of grabs is in effect. + mitem mgrabs; //says who has stolen all input events. + + float shiftheld; //shift is held. + float mousedown; //which mouse buttons are currently held. + vector oldmousepos; //old mouse position, to track whether its moved. + vector mousepos; //current mouse position. + vector screensize; //total screen size + vector drawrectmin; //minimum scissor region, to clamp children to within the confines of its parent. + vector drawrectmax; //maximum scissor region + +#ifndef MENU + //menuqc has no concept of the world and cannot display or query 3d positions nor projections. Any related UI elements are thus not available to menuqc. + //these globals are not part of the ui struct either, because they're illegal in world UIs. + float havemouseworld; //whether the mouseworld stuff is valid - ie: that the cursor is in a 3d view + vector mouseworldnear; //position of the mouse cursor upon the near clip plane in world space + vector mouseworldfar; //position of the mouse cursor upon the far(ish) clip plane in world space +#endif +} uiinfo_t; +var uiinfo_t ui = +{ + drawsetcliparea, + drawpic, + drawfill, + drawcharacter, + drawstring +}; + + + +void() queryscreensize = +{ +#pragma warning disable F333 +#ifdef MENU + //there is no proper way to do this. + //fte thus has special checks for these cvars, and they should not be autocvars if you want them to work properly. + ui.screensize[0] = cvar("vid_conwidth"); + ui.screensize[1] = cvar("vid_conheight"); + ui.screensize[2] = 0; +#endif +#ifdef CSQC + #ifdef CSQC_SIMPLE + ui.screensize = screensize; + #else + //make sure the screensize is set. + normalize('0 0 1'); //when getproperty fails to return a meaningful value, make sure that we don't read some random stale value. + ui.screensize = (vector)getproperty(VF_SCREENVSIZE); + if (ui.screensize[2]) //lingering return value from normalize. + { + ui.screensize[2] = 0; + ui.screensize[0] = cvar("vid_conwidth"); + ui.screensize[1] = cvar("vid_conheight"); + } + #endif +#endif +#pragma warning enable F333 +}; + +//helper function +float(vector minp, vector sz) mouseinbox = +{ + if (ui.mousepos[0] < minp_x) + return FALSE; + if (ui.mousepos[1] < minp_y) + return FALSE; + if (ui.mousepos[0] >= minp_x + sz_x) + return FALSE; + if (ui.mousepos[1] >= minp_y + sz_y) + return FALSE; + + return TRUE; +}; + + +void(mitem ch, vector parentsize) mitem_parentresized = +{ + float f = ch.resizeflags; + + if (f & RS_X_FRACTION) + ch.item_position[0] = parentsize[0] * ch.mins[0]; + else if (f & RS_X_MIN_PARENT_MAX) + ch.item_position[0] = parentsize[0] + ch.mins[0]; + else if (f & RS_X_MIN_PARENT_MID) + ch.item_position[0] = parentsize[0]/2 + ch.mins[0]; + else //if (f & RS_X_MIN_PARENT_MIN) + ch.item_position[0] = ch.mins[0]; + + if (f & RS_X_FRACTION) + ch.item_position[0] = parentsize[0] * ch.maxs[0]; + else if (f & RS_X_MAX_PARENT_MIN) + ch.item_size[0] = ch.maxs[0] - ch.item_position[0]; + else if (f & RS_X_MAX_PARENT_MID) + ch.item_size[0] = ch.maxs[0]+parentsize[0]/2 - ch.item_position[0]; + else if (f & RS_X_MAX_PARENT_MAX) + ch.item_size[0] = ch.maxs[0]+parentsize[0] - ch.item_position[0]; + else //if (f & RS_X_MAX_OWN_MIN) + ch.item_size[0] = ch.maxs[0]; + + if (f & RS_Y_FRACTION) + ch.item_position[1] = parentsize[1] * ch.mins[1]; + else if (f & RS_Y_MIN_PARENT_MAX) + ch.item_position[1] = parentsize[1] + ch.mins[1]; + else if (f & RS_Y_MIN_PARENT_MID) + ch.item_position[1] = parentsize[1]/2 + ch.mins[1]; + else //if (f & RS_Y_MIN_PARENT_MIN) + ch.item_position[1] = ch.mins[1]; + + if (f & RS_Y_FRACTION) + ch.item_position[1] = parentsize[1] * ch.maxs[1]; + else if (f & RS_Y_MAX_PARENT_MIN) + ch.item_size[1] = ch.maxs[1] - ch.item_position[1]; + else if (f & RS_Y_MAX_PARENT_MID) + ch.item_size[1] = ch.maxs[1]+parentsize[1]/2 - ch.item_position[1]; + else if (f & RS_Y_MAX_PARENT_MAX) + ch.item_size[1] = ch.maxs[1]+parentsize[1] - ch.item_position[1]; + else //if (f & RS_Y_MAX_OWN_MIN) + ch.item_size[1] = ch.maxs[1]; +}; + +#include "mitem_frame.qc" +void() mitem::item_resized = +{ + if (this.item_parent) + this.item_parent.item_flags |= IF_CLIENTMOVED; +}; + + +#ifdef HEIRACHYDEBUG +void mitem_printnode(float depth, mitem root, string prefix) +{ + mitem ch; + string col = ""; + if (root.item_flags & IF_KFOCUSED && root.item_flags & IF_MFOCUSED) + col = "^2"; + else if (root.item_flags & IF_KFOCUSED) + col = "^5"; + else if (root.item_flags & IF_MFOCUSED) + col = "^3"; + + print(sprintf("%s%s%i:%s%s\n", col, strpad(depth, ""), root, prefix, root.item_text)); + + //can only recurse into items which are actually frames. + if (root.item_flags & IF_ISFRAME) + { + mitem_frame fr = (mitem_frame)root; + depth+=1; + for(ch = fr.item_children; ch; ch = ch.item_next) + { + if (ch.item_parent != root) + print("corrupt parent\n"); + if (ch.item_next == ch) + { + print("infinite loop\n"); + break; + } + string pre = ""; + if (fr.item_mactivechild == ch) + pre = strcat(pre, "m"); + if (fr.item_kactivechild == ch) + pre = strcat(pre, "k"); + if (pre) + pre = strcat(pre, " "); + mitem_printnode(depth, ch, pre); + } + } +}; +void mitem_printtree(mitem_frame root, string from, float line) +{ + print(sprintf("%s:%g\n", from, line)); + mitem_printnode(0, root, ""); +} +#endif + +string(string key) mitem::get = +{ + //walk through to the parent for menu parents to track all this. + if (this.item_parent) + return this.item_parent.get(key); + else //no parent, just assume its a cvar. + return cvar_string(key); +}; +void(string key, string newval) mitem::set = +{ + //walk through to the parent for menu parents to track all this. + if (this.item_parent) + this.item_parent.set(key, newval); + else //no parent, just assume its a cvar. + cvar_set(key, newval); +}; +float(string key) mitem::isvalid = +{ + //walk through to the parent for menu parents to track all this. + if (this.item_parent) + return this.item_parent.isvalid(key); + else //no parent, just assume its a cvar. + return cvar_type(key); +}; + + +/*************************************************************************** +basic 'null' item, for completeness, every other item logically inherits from this. +drawing this just shows its text right-aligned at w/2. most things will want to override this. +*/ +void(mitem newfocus, float flag) mitem::item_focuschange = +{ +}; + +//z order is determined by the list order. the ones at the end (oldest) are on top. +void() mitem::totop = +{ + mitem_frame p = item_parent; + mitem prev; + //unlink it + if (p.item_children == this) + prev = p.item_children = item_next; + else + { + for (prev = p.item_children; prev; prev = prev.item_next) + { + if (prev.item_next == this) + { + prev.item_next = item_next; + break; + } + } + } + + item_next = __NULL__; + if (prev) + { + //add it on the end + while(prev.item_next) + prev = prev.item_next; + prev.item_next = this; + } + else + p.item_children = this; +}; + +vector(mitem it) menuitem_textcolour = +{ + local vector col; + col = it.item_rgb; + if (!(it.item_flags & IF_SELECTABLE) && it.item_keypress && it.item_command != "") + col *= 0.2; + else + { + if (it.item_flags & IF_MFOCUSED) + col_z = 0; + if (it.item_flags & IF_KFOCUSED) + col_x = 0; + } + return col; +}; +void(vector pos) mitem::item_draw = +{ + vector col; + pos_x += this.item_size[0] / 2 - stringwidth(this.item_text, TRUE, '1 1 0'*this.item_scale) - 8; + col = menuitem_textcolour(this); + ui.drawstring(pos, this.item_text, '1 1 0' * this.item_scale, col, this.item_alpha, 0); +}; +void() mitem::item_remove = +{ + local mitem ch; + //any children got removed by the frame code. + + //make sure the item is removed from its parent. + local mitem_frame p = this.item_parent; + if (p) + { + if (p.item_exclusive == this) + p.item_exclusive = __NULL__; + + if (p.item_children == this) + p.item_children = this.item_next; + else + { + for(ch = p.item_children; ch; ch = ch.item_next) + { + if (ch.item_next == this) + { + ch.item_next = this.item_next; + break; + } + } + } + } + + //pick a different item on the parent. + if (p.item_mactivechild == this) + p.item_focuschange(__NULL__, IF_MFOCUSED); + if (p.item_kactivechild == this) + { + for (ch = p.item_children; ch; ch = ch.item_next) + { + if (ch.item_flags & IF_SELECTABLE) + break; + } + p.item_focuschange(ch, IF_KFOCUSED); + } + if (ui.kgrabs == this) + ui.kgrabs = __NULL__; + if (ui.mgrabs == this) + ui.mgrabs = __NULL__; + remove((entity)this); + + //force mousefocus to update + ui.oldmousepos = '-1 -1'; +}; +void() mitem::mitem = +{ + if (!this.item_scale) + this.item_scale = 1; + if (!this.item_rgb) + this.item_rgb = '1 1 1'; + if (!this.item_alpha) + this.item_alpha = 1; + + //force mousefocus to update + ui.oldmousepos = '-1 -1'; +}; diff --git a/menusys/mitems_common.qc b/menusys/mitems_common.qc new file mode 100644 index 000000000..b8b73a3be --- /dev/null +++ b/menusys/mitems_common.qc @@ -0,0 +1,185 @@ +/*************************************************************************** +simple block-fill item +non-interactable. +*/ +class mitem_fill : mitem +{ + virtual void(vector pos) item_draw = + { + ui.drawfill(pos, this.item_size, this.item_rgb, this.item_alpha, 0); + }; +}; +#define menuitemfill_spawn(sz,rgb,alph) spawn(mitem_fill, item_size:sz, item_rgb:rgb, item_alpha:alph) + +/*************************************************************************** +basic picture item. +non-interactable. + +item_text: the normal image to use +item_text_mactive: the image to use when the mouse is over it. +item_size: if not set, will be set to the size of the image named by item_text. if only y is set, x will be sized to match aspect with y. +*/ +class mitem_pic : mitem +{ + string item_text_mactive; + virtual void(vector pos) item_draw = + { + if (item_text_mactive != "" && (item_flags & IF_MFOCUSED)) + ui.drawpic(pos, item_text_mactive, item_size, item_rgb, item_alpha, 0); + else + ui.drawpic(pos, item_text, item_size, item_rgb, item_alpha, 0); + }; + void() mitem_pic = + { + if (dp_workarounds) + { + if (substring(item_text, -4, 4) == ".lmp") + item_text = substring(item_text, 0, -5); + if (substring(item_text_mactive , -4, 5) == ".lmp") + item_text_mactive = substring(item_text_mactive, 0, -4); + } + + item_text = strzone(item_text); + precache_pic(item_text); + if (item_text_mactive) + { + item_text_mactive = strzone(item_text_mactive); + precache_pic(item_text_mactive); + } + + if (!item_size[0]) + { + float y = item_size[1]; + item_size = drawgetimagesize(item_text); + if (y) //rescale x to ma + { + if (!item_size[1]) //bad image? don't glitch out too much. + item_size = item_size[0] * '1 1 0'; + else + item_size = [item_size[0] * (y / item_size[1]), y, 0]; + } + } + }; + virtual void() item_remove = + { + strunzone(item_text); + if (item_text_mactive) + strunzone(item_text_mactive); + super::item_remove(); + }; +}; +#define menuitempic_spawn(img,sz) spawn(mitem_pic, item_text:img, item_size:sz) + +/*************************************************************************** +basic text item. +interactable - executes a given console command. +*/ +class mitem_text : mitem +{ + virtual void(vector pos) item_draw = + { + vector rgb = menuitem_textcolour(this); + float w; + if (item_flags & IF_CENTERALIGN) + { + w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); + ui.drawstring(pos + [(item_size_x-w)/2, 0], item_text, '1 1 0' * item_scale, rgb, item_alpha, 0); + } + else if (item_flags & IF_RIGHTALIGN) + { + w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); + ui.drawstring(pos + [(item_size_x-w), 0], item_text, '1 1 0' * item_scale, rgb, item_alpha, 0); + } + else + ui.drawstring(pos, item_text, '1 1 0' * item_scale, rgb, item_alpha, 0); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (this.item_command) + { + if (!down) + return FALSE; + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) + { + item_parent.item_execcommand(this, this.item_command); +// localcmd(strcat(this.item_command, "\n")); + return TRUE; + } + } + return FALSE; + }; + + //zone+unzone input strings as needed + virtual void() item_remove = + { + strunzone(item_text); + if (item_command) + strunzone(item_command); + super::item_remove(); + }; + void() mitem_text = + { + item_text = strzone(item_text); + if (item_command != "") + { + item_command = strzone(item_command); + item_flags |= IF_SELECTABLE; + } + }; +}; +mitem(string text, string command, float height) menuitemtext_spawn = +{ + return spawn(mitem_text, item_scale:height, item_text:text, item_command:command, item_size:[stringwidth(text, TRUE, '1 1 0'*height), height, 0]); +}; + + +/*************************************************************************** +basic text item. +identical to text, but includes a 3dish border. +*/ +class mitem_button : mitem +{ + virtual void(vector pos) item_draw = + { + ui.drawfill(pos, [this.item_size[0], 1], TD_TOP, this.item_alpha, 0); + ui.drawfill(pos, [1, this.item_size[1] - 1], TD_LFT, this.item_alpha, 0); + ui.drawfill(pos + [this.item_size[0]-1, 1], [1, this.item_size[1] - 1], TD_RGT, this.item_alpha, 0); + ui.drawfill(pos + [0, this.item_size[1]-1], [this.item_size[0], 1], TD_BOT, this.item_alpha, 0); + + pos_x += (this.item_size[0] - stringwidth(this.item_text, TRUE, '1 1 0'*this.item_scale)) * 0.5; + pos_y += (this.item_size[1] - this.item_scale)*0.5; + ui.drawstring(pos, this.item_text, '1 1 0' * this.item_scale, menuitem_textcolour(this), this.item_alpha, 0); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (!down) + return FALSE; + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) + item_parent.item_execcommand(this, item_command); + else + return FALSE; + return TRUE; + }; + virtual void() item_remove = + { + strunzone(item_text); + if (item_command) + strunzone(item_command); + super::item_remove(); + }; +}; +mitem_button(string text, string command, vector sz) menuitembutton_spawn = +{ + mitem_button n = spawn(mitem_button); + n.item_scale = sz_y - 4; + n.item_text = strzone(text); + n.item_size = sz; + + if (command != "") + { + n.item_command = strzone(command); + n.item_flags |= IF_SELECTABLE; + } + return n; +}; + diff --git a/menusys/readme.txt b/menusys/readme.txt new file mode 100644 index 000000000..2d8a3f3a8 --- /dev/null +++ b/menusys/readme.txt @@ -0,0 +1,220 @@ +integrating into an existing mod: +the csqc/menu needs to instanciate a mitem_desktop (or derived class). items_keypress+items_draw need to be called in the relevent places with the relevent arguments, then you can start creating children and throwing them at the screen. +the desktop is the root object, and tracks whether the mouse/keys should be grabbed. + +general overview: +The menu system is build from a heirachy/tree of mitem nodes. +To draw the menu, the tree is walked from the root (the 'desktop' object) through its children (pictures, text, or whatever). +Each mitem inherits its various properties from a parent object using classes. For instance, the desktop inherits from mitem_frame(read: a container object), which in turn inherits from mitem(the root type). +When a container is moved or resized, all of its children (and their children) will automatically be resized accordingly. Once the container is destroyed, the children will also be automatically destroyed. +Drawing items is fairly simple. The root node is told to draw itself. Once it has drawn its background, it walks through its children asking them to draw themselves (telling the child exactly where it is on the scree). Other containers(like windows) do the same. In this way the entire tree is drawn. +Input happens in a somewhat similar manner. The parent decides which child the mouse is over, and sends mouse or keyboard to the relevent focused child. As a special exception, if an element has set ui.*grabs to refer to itself, that object gets to snoop on input first before even the desktop gets a chance. +To make a quake-style menu, you should create an mitem_exmenu, and then call the desktop's .add method to insert it. Then you need to spawn things like cvar sliders/combos/etc and use one of the menu's add methods to place it on the menu/window. Check the example code. + +Items that don't specify a method will inherit that method from its parent. +If you want only a minor change, for instance drawing a background under the item, you can make an item_draw method which draws the background then calls super::item_draw() with the same arguments in order to provide the parent's normal behaviour. Doing this with a menu's get+set methods allows you to use slider/etc mitems with things other than direct cvars (if you want an 'apply changes' button or so). + + +globals: +autocvar_menu_font: defaults to 'cour', which will only work in windows, fallback font otherwise. The menu code will register+use a font with this name suitable for 8 vpixels high, and 16vpixels. +dp_workarounds: needed to work around DP bugs. Set to 1 if you detect that the code is running inside DP. +ui: a struct containing the current ui state. Can be temporarily overwritten for alternative menu systems (like ones drawn on walls or whatever). + probably would have been nicer if it was a pointer, but I didn't want to depend upon those. + Within the ui struct are a few functions which typically just map to the equivelent builtins. + Placing a copy of these in the ui struct allows them to be overridden in order to project a menu/ui upon a wall efficiently without extra code. + these are the functions available: setcliparea, drawpic, drawfill, drawcharacter, drawstring +ui.mousedown: the mouse buttons that are currently pressed. +ui.oldmousepos: the mouse position that was set in the previous frame. this can be used to detect whether the mouse has moved. +ui.mousepos: the current virtual screen position of the mouse cursor. +ui.screensize: the virtual resolution of the screen - the dimensions the UI needs to fill. +ui.drawrectmin: the current viewport scissor min position (so over-sized items can draw outside of their containing frames without issue). +ui.drawrectmax: the current viewport scissor max position (so over-sized items can draw outside of their containing frames without issue). +ui.havemouseworld: whether the mouseworld[near/far] globals are valid - ie: that the cursor is in a 3d view. only exists in csqc. +ui.mouseworldnear: position of the mouse cursor upon the near clip plane in world space. +ui.mouseworldfar: position of the mouse cursor upon the far(ish) clip plane in world space. You can trace a line between these two vectors to detect which entities the mouse should interact with. +ui.kgrabs: set this global in order to redirect all keyboard buttons to this object. +ui.mgrabs: set this global in order to redirect all mouse buttons to this object. If the object that has focus also has item_flags&IF_NOCURSOR, the mouse cursor will be hidden. + +helper functions: +mouseinbox: returns whether ui.mousepos is within the box specified by the min+size arguments. + +mitems.qc:class mitem + root menuitem object that all else inherits from + + EVENTS: + void item_draw(pos): override this to replace how the item displays. the default is quite lame. pos is the virtual screen position. + float item_keypress(vector pos,float scan,float char, float down): the object got a keyboard or mouse click. pos is the virtual screen position. scan is the K_FOO scancode constant. char is the unicode value of the codepoint. either may be 0. some systems might always use 0 for one of scan+char. down says that the key was just pressed, false means it just got released. + void item_focuschange(mitem newfocus, float changedflag): the object's focus changed. newfocus is the item that gained focus, changedflag is either IF_MFOCUSED, IF_KFOCUSED, or both. check this against newfocus to see if this object gained focus. + string(string key) get: helper function. calls the parent's get method. + void(string key, string newval) set: helper function. calls the parent's set method. + void() item_remove: the item got removed. this callback can be used to free memory. be sure to notify super. + void() item_resized: the item has been resized or moved. provided to resize+move helper objects to match a parent, or recalculate cached extents or whatever. + + FIELDS: + item_flags: various flags + IF_SELECTABLE: item can be selected, either with mouse or keyboard. + IF_INTERACT: flag reserved for child classes to use. + IF_RESIZABLE: item can be resized. menus will show resize corners. + IF_MFOCUSED: item has mouse focus (parents will retain the flag) + IF_KFOCUSED: item has keyboard focus (parents will retain the flag) + IF_NOKILL: item has been marked as resisting closure. This applies to menus and will disable the implicit 'x' button. + IF_NOCURSOR: if the item grabs the mouse, the cursor will be hidden, allowing mlook to still work when some widget is grabbing everything (eg: an exmenu using right-click to look). + item_position: the (virtual) position of the object relative to its parent. + item_size: the current (virtual) size of the object. + item_scale: typically serves to rescale text. not used by the menu framework itself. + item_rgb: the colour field for the item. typically defaults to '1 1 1' if not otherwise set. + item_alpha: the alpha value for the item. typically defaults to 1. + item_text: typically used as the caption. also used as a searchable item name. + item_command: the cvar or console command associated with the object. potentially other uses. yay for repurposing fields. + item_parent: the frame that contains this object. + item_next: the next sibling within the parent. + mins: the resize gravity bias for the 'min' pos. + maxs: the resize gravity bias for the 'max' pos. + resizeflags: controls the meaning of the mins+maxs positions. + RS_[X/Y]_[MIN/MAX]_PARENT_MIN: the min/max position is relative to the top/left of the parent. + RS_[X/Y]_[MIN/MAX]_PARENT_MID: the min/max position is relative to the center of the parent. + RS_[X/Y]_[MIN/MAX]_PARENT_MAX: the min/max position is relative to the bottom/right of the parent. + RS_[X/Y]_FRACTION: the min+max positions are 0-1 values scaled within the min/max position of the parent. + RS_[X/Y]_MAX_OWN_MIN: the max position is set relative to the objects own minimum size, giving an absolute object size. + + static functions: + totop(): moves the object to the top of the parent's z-order. drawn last, this object will now appear over everything else. does not affect focus. + + non-member functions (FIXME): + mitem_printtree: debug function to print out a list of the items children+siblings. + queryscreensize: updates the screensize global. + mitem_parentresized: updates an item's position and size according to its resizeflags. + menuitem_textcolour: helper function. determines the text colour to use for cvar widgets based upon selectable and mouse/keyboard focus. + +mitem_frame.qc:class mitem_frame : mitem + generic borderless container object for other items. + + FIELDS: + item_framesize_x: border width. children will not overlap this. + item_framesize_y: border at top. children will not overlap this. + item_framesize_z: border at bottom. children will not overlap this. + frame_hasscroll: if true, enables a vertical slider so the frame is still usable if the screen is too small. + item_children: the first child object of the frame + item_mactivechild: the current child that has the mouse over it. + item_kactivechild: the current child that has keyboard focus. + + static functions: + findchild(string title): scans through a frame's children looking for an item with a matching item_text. + add(mitem newitem, float resizeflags, vector originmin, vector originmax): adds an item to a frame, with specified gravity+position etc settings. see mitem::resizeflags for details. + adda(mitem newitem, vector pos): adds an item to a frame with preset item_size and position relative to the top-left of the parent frame. + addr(mitem newitem, float resizeflags, vector originmin, vector originmax): adds an item to a frame, with specified gravity+position etc settings, with reversed z order. this item will be drawn over the top of the previous objects, and is the more natural ordering. + addc(mitem newitem, float ypos): adds an item to a frame with a preset item_size at a specific y position. multiple objects with the same mins_y+maxs_y will automatically be spread across horizontally with a gap between each. + +mitem_bind.qc:class mitem_bind : mitem + a widget to change a key binding. up to two keys will be listed. additional keys will currently remain hidden. + item_text: the description of the key binding (ie: "Attack") + item_command: the console command to bind the key to (ie: "+attack") + +mitem_checkbox.qc:class mitem_check : mitem + a true/false cvar checkbox. + item_text: the description of the setting + item_command: the name of the cvar to toggle + + optional factory: mitem_check(string text, string command, vector sz) menuitemcheck_spawn; + +mitem_colours.qc:class mitem_colours : mitem + A simple colour picker. supports only hue. + item_text: the description of the setting + item_command: the name of the cvar to change (sets to 0xRRGGBB notation). + + factory: mitem_colours(string text, string command, vector sz) menuitemcolour_spawn; + +mitem_combo.qc:class mitem_combo : mitem + multiple choice widget. + item_text: the description of the setting + mstrlist: a list of the valid settings, in "\"value\" \"description\" \"value\" \"description\"" notation. + FIXME: must spawn through: mitem_combo(string text, string command, vector sz, string valuelist) menuitemcombo_spawn; + +mitem_combo.qc:class mitem_combo_popup : mitem + internal friend class of the combo. + this holds the list of options when clicked. + you should not use this class directly. + +mitem_desktop.qc:class mitem_desktop : mitem_frame + the root item that should be used as the parent of all other elements in order to be visible. + forces itself to fullscreen, handles grabs, etc. + in csqc, will display the default game view (including split screen views). + + csqc + float(mitem_desktop desktop, float evtype, float scanx, float chary, float devid) items_keypress + menuqc: + float(mitem_desktop desktop, float scan, float char, float down) items_keypress + both: + void(mitem_desktop desktop) items_draw + + +mitem_edittext.qc:class mitem_edit : mitem + text entry widget (typically) attached to a cvar. + item_text: the short description of the setting. + item_command: the name of the cvar to receive a new value/be displayed + +mitem_exmenu.qc:class mitem_exmenu : mitem_frame + 'exclusive' menu object. + this container has no borders. + does not provide a background. use a mitem_pic or mitem_fill for that. + not much more than a frame, but can take unhandled escape/right click to close the menu. + typically used fullscreen. + item_text: a searchable identifier for the object + item_command: the console command to execute when the item is removed. used to restore the parent menu. + +mitem_frame.qc:class mitem_vslider : mitem + vertical up/down slider not attached to a cvar. + used to scroll frames up+down for when you have too many items in there for the current video mode. + fixme: document + + factory macro: menuitemframe_spawn(sz) + typically created via inheritance. + +mitem_menu.qc:class mitem_menu : mitem_frame + movable resizable window + + item_flags: + IF_RESIZABLE: enables resize handles on left+bottom+right+lower corners. + IF_NOKILL: disables the 'x' button on the top-right. + non-member construtor: + menu_spawn(mitem_frame desktop, string mname, vector sz): centers and automatically adds the object. required for the default border size. do not call any of the add* methods yourself if you use this. + +mitem_slider.qc:class mitem_hslider : mitem + horizontal slider attached to a cvar. + item_text: the short description of the setting. + item_command: the name of the cvar to change. + item_slidercontrols_x: the minimum value. + item_slidercontrols_y: the maximum value. + item_slidercontrols_z: how much to change the cvar by each time the user uses the left/right arrows to change its value. + +mitem_tabs.qc:class mitem_tabs : mitem_frame + tabstrip object that appears presenting the various tab options. + acts as a container only for mitem_tab objects which are the real containers. + +mitem_tabs.qc:class mitem_tab : mitem_frame + child of a tabstrip that contains the various elements sited upon a single tab. + item_text: the caption displayed for the tab. + +mitems_common.qc:class mitem_fill : mitem + fills the screen region with a block of colour. + item_rgb: colour to fill it with. + item_alpha: alpha blended by this much. 1 for full block colour. + +mitems_common.qc:class mitem_pic : mitem + simple image display. + item_text: the default image to display. + item_text_mactive: if specified, the image to display when the object has mouse focus. + item_command: console command to execute when clicked. + +mitems_common.qc:class mitem_text : mitem + simple plain text + item_text: the text to display. + item_command: the console command to execute when clicked. + item_scale: the height of the text, in virtual pixels, so ideally 8 or more. + +mitems_common.qc:class mitem_button : mitem + centered text with a noticable border that just seems to say 'click me'... + item_text: the text to display. + item_command: the console command to execute when clicked. + item_scale: the height of the text, in virtual pixels, so ideally 8 or more. + diff --git a/player.qc b/player.qc deleted file mode 100644 index 235cea93b..000000000 --- a/player.qc +++ /dev/null @@ -1,1177 +0,0 @@ -// name = [framenum, nexttime, nextthink] {code} -// expands to: -// name () -// { -// self.frame=framenum; -// self.nextthink = time + nexttime; -// self.think = nextthink; -// -// }; - -void () bubble_bob; -void () T_Dispenser; -void () Headless_Think; - -void () player_touch = { - local entity Bio; - local entity te; - local float found; - te = world; - - if ((invis_only == 0) && - ((self.playerclass == 8) || (other.playerclass == 8))) { - if (other.classname == "player") { - if ((self.undercover_team != 0) || (self.undercover_skin != 0)) { - if (((other.playerclass == PC_SPY) || - (other.playerclass == PC_SCOUT)) - && (other.team_no != self.team_no)) { - TF_AddFrags(other, 1); - bprint(PRINT_MEDIUM, other.netname, - " uncovered an enemy spy!\n"); - Spy_RemoveDisguise(self); - } - } - if ((other.undercover_team != 0) || - (other.undercover_skin != 0)) { - if (((self.playerclass == PC_SPY) || - (self.playerclass == PC_SCOUT)) - && (self.team_no != other.team_no)) { - TF_AddFrags(self, 1); - bprint(PRINT_MEDIUM, self.netname, - " uncovered an enemy spy!\n"); - Spy_RemoveDisguise(other); - } - } - } - } - if ((self.tfstate & 16) && (cb_prematch_time < time)) { - if ((other.classname == "player") && (te.playerclass != 0)) { - if (!(other.tfstate & 16)) { - if (other.playerclass != 5) { - if (!((teamplay & 16) - && (self.owner.team_no == self.enemy.team_no) - && (self.owner.team_no != 0))) { - found = 0; - te = find(world, classname, "timer"); - while ((te != world) && (found == 0)) { - if ((te.owner == self) && - (te.think == BioInfection_Decay)) { - found = 1; - } else { - te = find(te, classname, "timer"); - } - } - Bio = spawn(); - Bio.nextthink = 2; - Bio.think = BioInfection_Decay; - Bio.owner = other; - Bio.classname = "timer"; - Bio.enemy = te.enemy; - other.tfstate = other.tfstate | 16; - other.infection_team_no = self.infection_team_no; - sprint(other, PRINT_MEDIUM, - "You have been infected by ", self.netname, - "\n"); - sprint(self, PRINT_MEDIUM, "You have infected ", - other.netname, "\n"); - } - } - } - } - } -}; - -void () player_stand1 =[17, player_stand1] { - self.weaponframe = 0; - if (self.velocity_x || self.velocity_y) { - self.walkframe = 0; - player_run(); - return; - } - if (self.current_weapon <= 16) { - if (self.walkframe >= 12) { - self.walkframe = 0; - } - self.frame = 17 + self.walkframe; - } else { - if (self.walkframe >= 5) { - self.walkframe = 0; - } - self.frame = 12 + self.walkframe; - } - self.walkframe = self.walkframe + 1; -}; - -void () player_run =[6, player_run] { - self.weaponframe = 0; - if (!self.velocity_x && !self.velocity_y) { - self.walkframe = 0; - player_stand1(); - return; - } - if (self.current_weapon <= 16) { - if (self.walkframe >= 6) { - self.walkframe = 0; - } - self.frame = 0 + self.walkframe; - } else { - if (self.walkframe >= 6) { - self.walkframe = 0; - } - self.frame = self.frame + self.walkframe; - } - self.walkframe = self.walkframe + 1; -}; - -void () player_shot1 =[113, player_shot2] { - self.weaponframe = 1; - muzzleflash(); -}; - -void () player_shot2 =[114, player_shot3] { - self.weaponframe = 2; -}; - -void () player_shot3 =[115, player_shot4] { - self.weaponframe = 3; -}; - -void () player_shot4 =[116, player_shot5] { - self.weaponframe = 4; -}; - -void () player_shot5 =[117, player_shot6] { - self.weaponframe = 5; -}; - -void () player_shot6 =[118, player_run] { - self.weaponframe = 6; -}; - -void () player_autorifle1 =[113, player_autorifle2] { - self.weaponframe = 1; - muzzleflash(); -}; - -void () player_autorifle2 =[114, player_autorifle3] { - self.weaponframe = 2; -}; - -void () player_autorifle3 =[118, player_run] { - self.weaponframe = 6; -}; - -void () player_axe1 =[119, player_axe2] { - self.weaponframe = 1; -}; - -void () player_axe2 =[120, player_axe3] { - self.weaponframe = 2; -}; - -void () player_axe3 =[121, player_axe4] { - self.weaponframe = 3; - if (self.current_weapon == WEAP_AXE) - W_FireAxe(); - else - W_FireSpanner(); -}; - -void () player_axe4 =[122, player_run] { - self.weaponframe = 4; -}; - -void () player_axeb1 =[125, player_axeb2] { - self.weaponframe = 5; -}; - -void () player_axeb2 =[126, player_axeb3] { - self.weaponframe = 6; -}; - -void () player_axeb3 =[127, player_axeb4] { - self.weaponframe = 7; - if (self.current_weapon == WEAP_AXE) - W_FireAxe(); - else - W_FireSpanner(); -}; - -void () player_axeb4 =[128, player_run] { - self.weaponframe = 8; -}; - -void () player_axec1 =[131, player_axec2] { - self.weaponframe = 1; -}; - -void () player_axec2 =[132, player_axec3] { - self.weaponframe = 2; -}; - -void () player_axec3 =[133, player_axec4] { - self.weaponframe = 3; - if (self.current_weapon == WEAP_AXE) - W_FireAxe(); - else - W_FireSpanner(); -}; - -void () player_axec4 =[134, player_run] { - self.weaponframe = 4; -}; - -void () player_axed1 =[137, player_axed2] { - self.weaponframe = 5; -}; - -void () player_axed2 =[138, player_axed3] { - self.weaponframe = 6; -}; - -void () player_axed3 =[139, player_axed4] { - self.weaponframe = 7; - if (self.current_weapon == WEAP_AXE) - W_FireAxe(); - else - W_FireSpanner(); -}; - -void () player_axed4 =[140, player_run] { - self.weaponframe = 8; -}; - -void () player_medikit1 =[119, player_medikit2] { - self.weaponframe = 1; -}; - -void () player_medikit2 =[120, player_medikit3] { - self.weaponframe = 2; -}; - -void () player_medikit3 =[121, player_medikit4] { - self.weaponframe = 3; - W_FireMedikit(); -}; - -void () player_medikit4 =[122, player_run] { - self.weaponframe = 4; -}; - -void () player_medikitb1 =[125, player_medikitb2] { - self.weaponframe = 5; -}; - -void () player_medikitb2 =[126, player_medikitb3] { - self.weaponframe = 6; -}; - -void () player_medikitb3 =[127, player_medikitb4] { - self.weaponframe = 7; - W_FireMedikit(); -}; - -void () player_medikitb4 =[128, player_run] { - self.weaponframe = 8; -}; - -void () player_medikitc1 =[131, player_medikitc2] { - self.weaponframe = 1; -}; - -void () player_medikitc2 =[132, player_medikitc3] { - self.weaponframe = 2; -}; - -void () player_medikitc3 =[133, player_medikitc4] { - self.weaponframe = 3; - W_FireMedikit(); -}; - -void () player_medikitc4 =[134, player_run] { - self.weaponframe = 4; -}; - -void () player_medikitd1 =[137, player_medikitd2] { - self.weaponframe = 5; -}; - -void () player_medikitd2 =[138, player_medikitd3] { - self.weaponframe = 6; -}; - -void () player_medikitd3 =[139, player_medikitd4] { - self.weaponframe = 7; - W_FireMedikit(); -}; - -void () player_medikitd4 =[140, player_run] { - self.weaponframe = 8; -}; - -void () player_nail1 =[103, player_nail2] { - muzzleflash(); - if (!self.button0 || intermission_running) { - player_run(); - return; - } - self.weaponframe = self.weaponframe + 1; - if (self.weaponframe == 9) { - self.weaponframe = 1; - } - SuperDamageSound(); - if (self.nailpos == 0) { - W_FireSpikes(4); - self.nailpos = 1; - } else { - W_FireSpikes(-4); - self.nailpos = 0; - } - Attack_Finished(0.2); -}; - -void () player_nail2 =[104, player_nail1] { - if (!self.button0 || intermission_running) { - player_run(); - return; - } - self.weaponframe = self.weaponframe + 1; - if (self.weaponframe == 9) { - self.weaponframe = 1; - } - Attack_Finished(0.2); -}; - -void () player_assaultcannonup1 =[103, player_assaultcannonup2] { - if ((!self.button0 || (self.ammo_shells < 1)) || intermission_running) { - self.tfstate = self.tfstate | TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - self.count = 1; - self.heat = 0; - player_assaultcannondown1(); - return; - } - self.fire_held_down = 1; - if (self.heat == 1) { - sound(self, 1, "weapons/asscan1.wav", 1, 1); - } - SuperDamageSound(); - Attack_Finished(0.1); - if ((self.heat != 2) && (self.heat != 4)) { - if (self.weaponframe >= 3) { - self.weaponframe = 0; - } else { - self.weaponframe = self.weaponframe + 1; - } - } - self.heat = self.heat + 1; - if (self.heat >= 7) { - self.heat = 0; - player_assaultcannon1(); - } -}; - -void () player_assaultcannonup2 =[103, player_assaultcannonup1] { - if ((!self.button0 || (self.ammo_shells < 1)) || intermission_running) { - self.tfstate = self.tfstate | TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - self.count = 1; - self.heat = 0; - player_assaultcannondown1(); - return; - } - SuperDamageSound(); - Attack_Finished(0.1); - if (((self.heat != 2) && (self.heat != 4)) && (self.heat != 7)) { - if ((self.weaponframe == 2) && (self.heat >= 9)) { - self.weaponframe = 0; - } else { - if (self.weaponframe >= 3) { - self.weaponframe = 0; - } else { - self.weaponframe = self.weaponframe + 1; - } - } - } - self.heat = self.heat + 1; - if (self.heat >= 13) { - self.heat = 0; - player_assaultcannon1(); - } -}; - -void () player_assaultcannon1 =[103, player_assaultcannon2] { - local string st; - - if (((vlen(self.velocity) <= 90 && !cannon_movespin) || vlen(self.velocity) <= 30) - && !(self.tfstate & TFSTATE_LOCK)) { - muzzleflash(); - sound(self, CHAN_WEAPON, "weapons/asscan2.wav", 1, ATTN_NORM); - - if (self.weaponframe == 2) - self.weaponframe = 4; - else - self.weaponframe = 2; - - SuperDamageSound(); - - self.tfstate = self.tfstate | TFSTATE_AIMING; - if (!cannon_move) - self.tfstate = self.tfstate | TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(self); - - W_FireAssaultCannon(); - stuffcmd(self, "v_idlescale "); - if (vlen(self.velocity) < 30) { - if (self.heat < 5) - st = ftos(self.heat * 4); - else - st = "20"; - } else { - if (self.heat < 5) - st = ftos(self.heat * 8); - else - st = "40"; - } - stuffcmd(self, st); - stuffcmd(self, "\n"); - } else { - sound(self, CHAN_WEAPON, "weapons/asscan4.wav", 0.5, ATTN_NORM); - - if (self.weaponframe == 2) - self.weaponframe = 0; - else - self.weaponframe = 2; - - stuffcmd(self, "v_idlescale 5\n"); - } - if ((!self.button0 || (self.ammo_shells < 1)) || intermission_running) { - stuffcmd(self, "v_idlescale 0\n"); - self.tfstate = self.tfstate | TFSTATE_AIMING; - if (!cannon_move) - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self); - self.weaponframe = 0; - self.count = 1; - player_assaultcannondown1(); - return; - } - Attack_Finished(0.1); -}; - -void () player_assaultcannon2 =[104, player_assaultcannon1] { - stuffcmd(self, "v_idlescale 0\n"); - if (vlen(self.velocity) < 30 && !(self.tfstate & TFSTATE_LOCK)) { - if (self.weaponframe == 2) { - self.weaponframe = 4; - } else { - self.weaponframe = 2; - } - SuperDamageSound(); - W_FireAssaultCannon(); - self.heat = self.heat + 0.1; - stuffcmd(self, "bf\n"); - } else { - if (self.weaponframe == 2) { - self.weaponframe = 0; - } else { - self.weaponframe = 2; - } - } - if ((!self.button0 || (self.ammo_shells < 1)) || intermission_running) { - stuffcmd(self, "v_idlescale 0\n"); - self.tfstate = self.tfstate | TFSTATE_AIMING; - if (!cannon_move) - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self); - self.weaponframe = 0; - self.count = 1; - player_assaultcannondown1(); - return; - } - Attack_Finished(0.1); -}; - -void () player_assaultcannondown1 =[103, player_assaultcannondown1] { - if (self.count == 1) { - sound(self, 1, "weapons/asscan3.wav", 0.8, 1); - } - if (self.count >= 15) { - self.heat = 0; - self.fire_held_down = 0; - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_AIMING); - if (!cannon_move) - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self); - if ((self.ammo_shells < 1) || (self.ammo_cells < 7)) { - self.current_weapon = W_BestWeapon(); - self.current_weaponslot = W_BestWeaponSlot(); - W_SetCurrentAmmo(self); - W_PrintWeaponMessage(); - return; - } - player_run(); - return; - } - if ((((self.count != 8) && (self.count != 10)) && (self.count != 12)) - && (self.count != 14)) { - if (self.weaponframe == 3) { - self.weaponframe = 0; - } else { - self.weaponframe = self.weaponframe + 1; - } - } - self.count = self.count + 1; - Attack_Finished(0.1); -}; - -void () player_light1 =[105, player_light2] { - muzzleflash(); - if (!self.button0 || intermission_running) { - player_run(); - return; - } - self.weaponframe = self.weaponframe + 1; - if (self.weaponframe == 5) { - self.weaponframe = 1; - } - SuperDamageSound(); - W_FireLightning(); - Attack_Finished(0.2); -}; - -void () player_light2 =[106, player_light1] { - if (!self.button0 || intermission_running) { - player_run(); - return; - } - self.weaponframe = self.weaponframe + 1; - if (self.weaponframe == 5) { - self.weaponframe = 1; - } - SuperDamageSound(); - W_FireLightning(); - Attack_Finished(0.2); -}; - -void () player_rocket1 =[107, player_rocket2] { - self.weaponframe = 1; - muzzleflash(); -}; - -void () player_rocket2 =[108, player_rocket3] { - self.weaponframe = 2; -}; - -void () player_rocket3 =[109, player_rocket4] { - self.weaponframe = 3; -}; - -void () player_rocket4 =[110, player_rocket5] { - self.weaponframe = 4; -}; - -void () player_rocket5 =[111, player_rocket6] { - self.weaponframe = 5; -}; - -void () player_rocket6 =[112, player_run] { - self.weaponframe = 6; -}; - -void (float num_bubbles) DeathBubbles; - -void () PainSound = { - local float rs; - - if (self.health < 0) { - return; - } - if (damage_attacker.classname == "teledeath") { - sound(self, 2, "player/teledth1.wav", 1, 0); - return; - } - if ((self.watertype == -3) && (self.waterlevel == 3)) { - DeathBubbles(1); - if (random() > 0.5) { - sound(self, 2, "player/drown1.wav", 1, 1); - } else { - sound(self, 2, "player/drown2.wav", 1, 1); - } - return; - } - if (self.watertype == -4) { - if (random() > 0.5) { - sound(self, 2, "player/lburn1.wav", 1, 1); - } else { - sound(self, 2, "player/lburn2.wav", 1, 1); - } - return; - } - if (self.watertype == -5) { - if (random() > 0.5) { - sound(self, 2, "player/lburn1.wav", 1, 1); - } else { - sound(self, 2, "player/lburn2.wav", 1, 1); - } - return; - } - if (self.pain_finished > time) { - self.axhitme = 0; - return; - } - self.pain_finished = time + 0.5; - if (self.axhitme == 1) { - self.axhitme = 0; - sound(self, 2, "player/axhit1.wav", 1, 1); - return; - } - rs = rint(((random() * 5) + 1)); - self.noise = ""; - if (rs == 1) { - self.noise = "player/pain1.wav"; - } else { - if (rs == 2) { - self.noise = "player/pain2.wav"; - } else { - if (rs == 3) { - self.noise = "player/pain3.wav"; - } else { - if (rs == 4) { - self.noise = "player/pain4.wav"; - } else { - if (rs == 5) { - self.noise = "player/pain5.wav"; - } else { - self.noise = "player/pain6.wav"; - } - } - } - } - } - sound(self, 2, self.noise, 1, 1); - return; -}; - -void () player_pain1 =[35, player_pain2] { - PainSound(); - self.weaponframe = 0; -}; - -void () player_pain2 =[36, player_pain3] { -}; - -void () player_pain3 =[37, player_pain4] { -}; - -void () player_pain4 =[38, player_pain5] { -}; - -void () player_pain5 =[39, player_pain6] { -}; - -void () player_pain6 =[40, player_run] { -}; - -void () player_axpain1 =[29, player_axpain2] { - PainSound(); - self.weaponframe = 0; -}; - -void () player_axpain2 =[30, player_axpain3] { -}; - -void () player_axpain3 =[31, player_axpain4] { -}; - -void () player_axpain4 =[32, player_axpain5] { -}; - -void () player_axpain5 =[33, player_axpain6] { -}; - -void () player_axpain6 =[34, player_run] { -}; - -void (entity et, float f) player_pain = { - if (self.weaponframe) { - if (deathmsg == 37) - PainSound(); - return; - } - if (self.invisible_finished > time) - return; - - if (self.is_feigning) { - PainSound(); - return; - } - if (self.button0 && (self.current_weapon == 32768)) - return; - - if (self.current_weapon <= 16) - player_axpain1(); - else - player_pain1(); -}; - -void () player_diea1; -void () player_dieb1; -void () player_diec1; -void () player_died1; -void () player_diee1; -void () player_die_ax1; - -void () DeathBubblesSpawn = { - if (self.owner.waterlevel != 3) - return; - - newmis = spawn(); - setmodel(newmis, "progs/s_bubble.spr"); - setorigin(newmis, (self.owner.origin + '0 0 24')); - newmis.movetype = 8; - newmis.solid = 0; - newmis.velocity = '0 0 15'; - newmis.nextthink = time + 0.5; - newmis.think = bubble_bob; - newmis.classname = "bubble"; - newmis.frame = 0; - newmis.cnt = 0; - setsize(newmis, '-8 -8 -8', '8 8 8'); - self.nextthink = time + 0.1; - self.think = DeathBubblesSpawn; - self.air_finished = self.air_finished + 1; - if (self.air_finished >= self.bubble_count) - dremove(self); -}; - -void (float num_bubbles) DeathBubbles = { - local entity bubble_spawner; - - bubble_spawner = spawn(); - setorigin(bubble_spawner, self.origin); - bubble_spawner.movetype = MOVETYPE_NONE; - bubble_spawner.solid = SOLID_NOT; - bubble_spawner.nextthink = time + 0.1; - bubble_spawner.think = DeathBubblesSpawn; - bubble_spawner.air_finished = 0; - bubble_spawner.owner = self; - bubble_spawner.bubble_count = num_bubbles; - return; -}; - -void () DeathSound = { - local float rs; - - if (self.waterlevel == 3) { - if (self.is_feigning) - DeathBubbles(2); - else - DeathBubbles(10); - - sound(self, 2, "player/h2odeath.wav", 1, 0); - return; - } - rs = rint(random() * 4 + 1); - if (rs == 1) - self.noise = "player/death1.wav"; - else if (rs == 2) - self.noise = "player/death2.wav"; - else if (rs == 3) - self.noise = "player/death3.wav"; - else if (rs == 4) - self.noise = "player/death4.wav"; - else - self.noise = "player/death5.wav"; - - sound(self, 2, self.noise, 1, 0); - return; -}; - -void () PlayerDead = { - self.nextthink = -1; - self.deadflag = 2; -}; - -vector(float dm) VelocityForDamage = -{ - local vector v; - - v_x = 100 * crandom(); - v_y = 100 * crandom(); - v_z = 200 + 100 * random(); - if (dm > -50) - v = v * 0.7; - else if (dm > -200) - v = v * 2; - else - v = v * 10; - return (v); -}; - -void (string gibname, float dm) ThrowGib = { - newmis = spawn(); - newmis.origin = self.origin; - - setmodel(newmis, gibname); - - setsize(newmis, '0 0 0', '0 0 0'); - newmis.velocity = VelocityForDamage(dm); - - newmis.movetype = MOVETYPE_BOUNCE; - newmis.solid = SOLID_NOT; - - newmis.avelocity_x = random() * 600; - newmis.avelocity_y = random() * 600; - newmis.avelocity_z = random() * 600; - - newmis.think = SUB_Remove; - newmis.ltime = time; - newmis.nextthink = time + 10 + random() * 10; - newmis.frame = 0; - newmis.flags = 0; -}; - -void (string gibname, float dm) ThrowHead = { - setmodel(self, gibname); - self.skin = 0; - self.frame = 0; - self.nextthink = -1; - - self.movetype = MOVETYPE_BOUNCE; - self.takedamage = DAMAGE_NO; - self.solid = SOLID_NOT; - - self.view_ofs = '0 0 8'; - setsize(self, '-16 -16 0', '16 16 56'); - self.velocity = VelocityForDamage(dm); - self.origin_z = self.origin_z - 24; - self.flags = self.flags - (self.flags & 512); - self.avelocity = crandom() * '0 600 0'; -}; - -void (string gibname) HeadShotThrowHead = { - setmodel(self, gibname); - self.frame = 0; - self.nextthink = -1; - - self.movetype = MOVETYPE_BOUNCE; - self.takedamage = DAMAGE_NO; - self.solid = SOLID_NOT; - - self.view_ofs = '0 0 8'; - setsize(self, '-16 -16 0', '16 16 56'); - self.velocity = normalize(self.head_shot_vector) * 600; - self.origin_z = self.origin_z + 24; - self.flags = self.flags - self.flags & 512; - self.avelocity = '0 0 0'; -}; - -void () KillPlayer = { - self.owner.deadflag = 2; - dremove(self); -}; - -void () GibPlayer = { - ThrowHead("progs/h_player.mdl", self.health); - ThrowGib("progs/gib1.mdl", self.health); - ThrowGib("progs/gib2.mdl", self.health); - ThrowGib("progs/gib3.mdl", self.health); - if (deathmsg == 36) { - newmis = spawn(); - newmis.owner = self; - newmis.think = KillPlayer; - newmis.nextthink = time + 1; - } else { - self.deadflag = 2; - } - TeamFortress_SetupRespawn(0); - if (damage_attacker.classname == "teledeath") { - sound(self, 2, "player/teledth1.wav", 1, 0); - self.respawn_time = (self.respawn_time + 2) + (random() * 2); - return; - } - if (damage_attacker.classname == "teledeath2") { - sound(self, 2, "player/teledth1.wav", 1, 0); - self.respawn_time = (self.respawn_time + 2) + (random() * 2); - return; - } - if (random() < 0.5) { - sound(self, 2, "player/gib.wav", 1, 0); - } else { - sound(self, 2, "player/udeath.wav", 1, 0); - } -}; - -void () PlayerDie = { - local float i; - local entity te; - - self.items = self.items - (self.items & 524288); - self.invisible_finished = 0; - self.invincible_finished = 0; - self.super_damage_finished = 0; - self.radsuit_finished = 0; - self.modelindex = modelindex_player; - - if ((self.tfstate & 16) && (self == self.enemy)) { - te = find(world, classname, "timer"); - while (te) { - if ((te.owner == self) && (te.think == BioInfection_Decay)) { - logfrag(te.enemy, self); - TF_AddFrags(te.enemy, 1); - } - te = find(te, classname, "timer"); - } - } - TeamFortress_RemoveTimers(); - - if (deathmatch || coop) - DropBackpack(); - - self.weaponmodel = ""; - self.view_ofs = '0 0 -8'; - self.deadflag = DEAD_DYING; - self.solid = SOLID_NOT; - self.flags = self.flags - (self.flags & 512); - self.movetype = MOVETYPE_TOSS; - - if (self.velocity_z < 10) - self.velocity_z = self.velocity_z + random() * 300; - - if (self.health < -40) { - GibPlayer(); - return; - } - DeathSound(); - self.angles_x = 0; - self.angles_z = 0; - if (self.current_weapon <= 16) { - player_die_ax1(); - TeamFortress_SetupRespawn(0); - return; - } - i = 1 + floor(random() * 6); - if (i == 1) - player_diea1(); - else if (i == 2) - player_dieb1(); - else if (i == 3) - player_diec1(); - else if (i == 4) - player_died1(); - else - player_diee1(); - - TeamFortress_SetupRespawn(0); -}; - -void () set_suicide_frame = { - if (self.model != "progs/player.mdl") - return; - - setmodel(self, string_null); - setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); -}; - -void () player_diea1 =[50, player_diea2] { -}; - -void () player_diea2 =[51, player_diea3] { -}; - -void () player_diea3 =[52, player_diea4] { -}; - -void () player_diea4 =[53, player_diea5] { -}; - -void () player_diea5 =[54, player_diea6] { -}; - -void () player_diea6 =[55, player_diea7] { -}; - -void () player_diea7 =[56, player_diea8] { -}; - -void () player_diea8 =[57, player_diea9] { -}; - -void () player_diea9 =[58, player_diea10] { -}; - -void () player_diea10 =[59, player_diea11] { -}; - -void () player_diea11 =[60, player_diea11] { - PlayerDead(); -}; - -void () player_dieb1 =[61, player_dieb2] { -}; - -void () player_dieb2 =[62, player_dieb3] { -}; - -void () player_dieb3 =[63, player_dieb4] { -}; - -void () player_dieb4 =[64, player_dieb5] { -}; - -void () player_dieb5 =[65, player_dieb6] { -}; - -void () player_dieb6 =[66, player_dieb7] { -}; - -void () player_dieb7 =[67, player_dieb8] { -}; - -void () player_dieb8 =[68, player_dieb9] { -}; - -void () player_dieb9 =[69, player_dieb9] { - PlayerDead(); -}; - -void () player_diec1 =[70, player_diec2] { -}; - -void () player_diec2 =[71, player_diec3] { -}; - -void () player_diec3 =[72, player_diec4] { -}; - -void () player_diec4 =[73, player_diec5] { -}; - -void () player_diec5 =[74, player_diec6] { -}; - -void () player_diec6 =[75, player_diec7] { -}; - -void () player_diec7 =[76, player_diec8] { -}; - -void () player_diec8 =[77, player_diec9] { -}; - -void () player_diec9 =[78, player_diec10] { -}; - -void () player_diec10 =[79, player_diec11] { -}; - -void () player_diec11 =[80, player_diec12] { -}; - -void () player_diec12 =[81, player_diec13] { -}; - -void () player_diec13 =[82, player_diec14] { -}; - -void () player_diec14 =[83, player_diec15] { -}; - -void () player_diec15 =[84, player_diec15] { - PlayerDead(); -}; - -void () player_died1 =[85, player_died2] { -}; - -void () player_died2 =[86, player_died3] { -}; - -void () player_died3 =[87, player_died4] { -}; - -void () player_died4 =[88, player_died5] { -}; - -void () player_died5 =[89, player_died6] { -}; - -void () player_died6 =[90, player_died7] { -}; - -void () player_died7 =[91, player_died8] { -}; - -void () player_died8 =[92, player_died9] { -}; - -void () player_died9 =[93, player_died9] { - PlayerDead(); -}; - -void () player_diee1 =[94, player_diee2] { -}; - -void () player_diee2 =[95, player_diee3] { -}; - -void () player_diee3 =[96, player_diee4] { -}; - -void () player_diee4 =[97, player_diee5] { -}; - -void () player_diee5 =[98, player_diee6] { -}; - -void () player_diee6 =[99, player_diee7] { -}; - -void () player_diee7 =[100, player_diee8] { -}; - -void () player_diee8 =[101, player_diee9] { -}; - -void () player_diee9 =[102, player_diee9] { - PlayerDead(); -}; - -void () player_die_ax1 =[41, player_die_ax2] { -}; - -void () player_die_ax2 =[42, player_die_ax3] { -}; - -void () player_die_ax3 =[43, player_die_ax4] { -}; - -void () player_die_ax4 =[44, player_die_ax5] { -}; - -void () player_die_ax5 =[45, player_die_ax6] { -}; - -void () player_die_ax6 =[46, player_die_ax7] { -}; - -void () player_die_ax7 =[47, player_die_ax8] { -}; - -void () player_die_ax8 =[48, player_die_ax9] { -}; - -void () player_die_ax9 =[49, player_die_ax9] { - PlayerDead(); -}; - -void () Headless_Think = { - self.frame = self.frame + 1; - if ((self.frame == 7) || (self.frame == 18)) { - self.nextthink = time + 10 + random() * 10; - self.think = SUB_Remove; - return; - } - self.nextthink = time + 0.1; -}; diff --git a/progs.src b/progs.src deleted file mode 100644 index 414663698..000000000 --- a/progs.src +++ /dev/null @@ -1,44 +0,0 @@ -qwprogs.dat - -defs.qc -qw.qc -debug.qc -status.qc -menu.qc -vote.qc -help.qc -subs.qc -combat.qc -items.qc -weapons.qc -world.qc -client.qc -player.qc -doors.qc -buttons.qc -triggers.qc -tforttm.qc -plats.qc -misc.qc -monsters.qc -flare.qc -sentry.qc -boss.qc -admin.qc -scout.qc -sniper.qc -tsoldier.qc -demoman.qc -medic.qc -pyro.qc -spy.qc -engineer.qc -camera.qc -clan.qc -tfort.qc -tforthlp.qc -tfortmap.qc -ctf.qc -coop.qc -actions.qc -spect.qc diff --git a/scout.qc b/scout.qc deleted file mode 100644 index c75b817f9..000000000 --- a/scout.qc +++ /dev/null @@ -1,691 +0,0 @@ -//======================================================== -// Functions for the SCOUT class and associated weaponry -//======================================================== - -void () CaltropTouch; -void () CaltropScatterThink; -void () ScatterCaltrops; -void () FlashGrenadeTouch; -void () FlashTimer; -void () FlashGrenadeExplode; -void () ConcussionGrenadeTouch; -void () ConcussionGrenadeExplode; - -void (entity inflictor, entity attacker, float bounce, - entity ignore) T_RadiusBounce; -entity(entity scanner, float scanrange, float enemies, - float friends) T_RadiusScan; - -void () CF_Scout_Dash = { - if (self.playerclass != PC_SCOUT) - return; - - // check if dash is allowed in rules and if cooldown has ended - if (!scoutdash || time < self.dash_cooldown) - return; - - // don't dash if scout is concussed or have damaged legs - if (self.leg_damage || self.is_concussed) - return; - - // only dash if walking or slower - if (vlen(self.velocity) <= 450 && (self.flags & FL_ONGROUND)) { - makevectors(self.angles); - self.velocity = v_forward * 540; - self.velocity_z = 190; - sound(self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); - } -} - -void () CanisterTouch = -{ - sound(self, 1, "weapons/tink1.wav", 1, 1); - if (self.velocity == '0 0 0') - self.avelocity = '0 0 0'; -}; - -void () CaltropTouch = { - if ((other.classname != "player") || !(other.flags & FL_ONGROUND) || - other.deadflag) - return; - - if ((teamplay & TEAMPLAY_NOEXPLOSIVE) && (other != self.owner) - && (other.team_no == self.owner.team_no) && - (self.owner.team_no != 0)) - return; - - sprint(other, PRINT_HIGH, "Ow, ow, ow! Caltrops!\n"); - other.leg_damage = other.leg_damage + 2; - TeamFortress_SetSpeed(other); - deathmsg = 41; - T_Damage(other, self, self.owner, 10); - dremove(self); -}; - -void () CaltropScatterThink = { - self.nextthink = time + 0.2; - if (self.velocity == '0 0 0') { - if (self.flags & FL_ONGROUND) { - self.nextthink = time + 10 + random() * 5; - self.think = SUB_Remove; - self.solid = SOLID_TRIGGER; - self.movetype = MOVETYPE_TOSS; - self.touch = CaltropTouch; - self.angles = '90 90 90'; - sound(self, CHAN_AUTO, "weapons/tink1.wav", 1, ATTN_NORM); - setorigin(self, self.origin); - return; - } else { - self.nextthink = time + 10 + random() * 5; - self.think = SUB_Remove; - self.solid = SOLID_TRIGGER; - self.movetype = MOVETYPE_TOSS; - self.touch = CanisterTouch; - setorigin(self, self.origin); - return; - } - } - self.heat = self.heat + 1; - if (self.heat > 50) { - remove(self); - return; - } - traceline(self.movedir, self.origin, 1, self); - if (trace_fraction == 1) { - self.movedir = self.origin; - return; - } - self.velocity = self.velocity * -1; -}; - -void () ScatterCaltrops = { - local float num; - local entity e; - - num = 6; - while (num > 0) { - e = spawn(); - e.classname = "grenade"; - e.weapon = 10; - e.owner = self.owner; - e.team_no = self.owner.team_no; - setmodel(e, "progs/caltrop.mdl"); - e.mins = '-4 -4 -8'; - e.maxs = '4 4 4'; - e.angles = '0 0 0'; - e.angles_x = random() * 360; - e.velocity_x = crandom() * 100; - e.velocity_y = crandom() * 100; - e.velocity_z = 200 + random() * 100; - e.avelocity_x = crandom() * 400; - e.avelocity_y = crandom() * 400; - e.avelocity_z = crandom() * 400; - setorigin(e, self.owner.origin); - e.movedir = e.origin; - e.heat = 0; - e.movetype = 10; - e.solid = 0; - e.nextthink = time + 0.2; - e.think = CaltropScatterThink; - num = num - 1; - } - dremove(self); -}; - -void () FlashGrenadeTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); - if (self.velocity == '0 0 0') - self.avelocity = '0 0 0'; -}; - -void () FlashTimer = { - local entity te; - local string st; - - te = self.owner; - te.FlashTime = te.FlashTime - 0.6; - if (te.FlashTime < 5) { - te.FlashTime = 0; - stuffcmd(te, "v_cshift; wait; bf\n"); - remove(self); - return; - } - st = ftos(te.FlashTime * 10); - stuffcmd(te, "v_cshift "); - stuffcmd(te, st); - stuffcmd(te, " "); - stuffcmd(te, st); - stuffcmd(te, " "); - stuffcmd(te, st); - stuffcmd(te, " "); - stuffcmd(te, st); - stuffcmd(te, "\n"); - self.nextthink = time + 0.6; -}; - -void () FlashGrenadeExplode = { - local entity te; - local string st; - - self.effects = self.effects | EF_BRIGHTLIGHT; - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_TAREXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); - - te = findradius(self.origin, 300); - while (te) { - if (te.classname == "player") { - traceline(self.origin, te.origin, 1, self); - if (trace_fraction == 1) { - if (vlen(self.origin - te.origin) <= 200) { - deathmsg = DMSG_GREN_FLASH; - TF_T_Damage(te, self, self.owner, 60, 2, 16 | 4); - } - if (te.health > 0) { - if (te.FlashTime == 0) { - newmis = spawn(); - newmis.classname = "timer"; - newmis.netname = "flashtimer"; - newmis.team_no = self.owner.team_no; - newmis.owner = te; - newmis.think = FlashTimer; - newmis.nextthink = time + 1; - } - if (te == self.owner) - te.FlashTime = 16; - else - te.FlashTime = 24; - - st = ftos(te.FlashTime * 10); - stuffcmd(te, "v_cshift "); - stuffcmd(te, st); - stuffcmd(te, " "); - stuffcmd(te, st); - stuffcmd(te, " "); - stuffcmd(te, st); - stuffcmd(te, " "); - stuffcmd(te, st); - stuffcmd(te, "\n"); - } - } - } - te = te.chain; - } - dremove(self); -}; - -void () ConcussionGrenadeTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); - if (self.velocity == '0 0 0') - self.avelocity = '0 0 0'; -}; - -void () ConcussionGrenadeExplode = { - T_RadiusBounce(self, self.owner, 240, world); - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); - dremove(self); -}; - -void () OldConcussionGrenadeTimer = { - local string st; - - if (self.owner.invincible_finished > time) { - stuffcmd(self.owner, "v_idlescale 0; wait; fov 90\n"); - dremove(self); - return; - } - newmis = spawn(); - setmodel(newmis, "progs/s_bubble.spr"); - setorigin(newmis, self.owner.origin); - newmis.movetype = 8; - newmis.solid = 0; - newmis.velocity = '0 0 15'; - newmis.nextthink = time + 0.5; - newmis.think = bubble_bob; - newmis.touch = bubble_remove; - newmis.classname = "bubble"; - newmis.frame = 0; - newmis.cnt = 0; - setsize(newmis, '-8 -8 -8', '8 8 8'); - - self.health = self.health - 20; - if (self.owner.playerclass == 5) - self.health = self.health - 20; - if (self.health < 0) - self.health = 0; - - self.nextthink = time + 5; - stuffcmd(self.owner, "v_iroll_cycle 0.5\n"); - stuffcmd(self.owner, "v_ipitch_cycle 1\n"); - stuffcmd(self.owner, "v_iyaw_cycle 2\n"); - - st = ftos(self.health); - stuffcmd(self.owner, "v_idlescale "); - stuffcmd(self.owner, st); - stuffcmd(self.owner, "\n"); - - st = ftos(90 + self.health / 2); - stuffcmd(self.owner, "fov "); - stuffcmd(self.owner, st); - stuffcmd(self.owner, "\n"); - if (self.health == 0) - dremove(self); -}; - -void () ConcussionGrenadeTimer = { - local vector src; - local float pos; - local float concadjust; - local float stumble; - - if (self.owner.invincible_finished > time) { - sprint(self.owner, PRINT_HIGH, "Your head feels better now\n"); - self.owner.fixangle = 0; - self.owner.is_concussed = 0; - dremove(self); - return; - } - if ((self.health == 200) || (self.health == 400) || - (self.health == 600) - || (self.health == 800) || (self.health == 1000)) { - newmis = spawn(); - setmodel(newmis, "progs/s_bubble.spr"); - setorigin(newmis, self.owner.origin); - newmis.movetype = MOVETYPE_NOCLIP; - newmis.solid = SOLID_NOT; - newmis.velocity = '0 0 15'; - newmis.nextthink = time + 0.5; - newmis.think = bubble_bob; - newmis.touch = bubble_remove; - newmis.classname = "bubble"; - newmis.frame = 0; - newmis.cnt = 0; - setsize(newmis, '-8 -8 -8', '8 8 8'); - } - self.health = self.health - 10; - if (self.owner.playerclass == 5) - self.health = self.health - 10; - - if (self.health < 0) - self.health = 0; - - concadjust = 1; - self.nextthink = time + 0.25 * concadjust; - - if (concadjust > 1) - self.health = self.health - concadjust; - - pos = pointcontents(self.owner.origin); - src_x = self.owner.origin_x + self.owner.maxs_x + 2; - src_y = self.owner.origin_y + self.owner.maxs_y + 2; - src_z = self.owner.origin_z; - pos = pointcontents(src); - - if ((self.owner.flags & 512) || (self.owner.flags & 16)) { - if (!self.owner.is_feigning && !(self.owner.current_weapon == WEAP_ASSAULT_CANNON && !cannon_conc && self.owner.button0)) { - makevectors(self.owner.v_angle); - stumble = crandom() * self.health; - if (!((pos == -2) && (self.owner.velocity == '0 0 0'))) { - if ((crandom() < 0)) { - self.owner.velocity_x = - self.owner.velocity_y + stumble; - self.owner.velocity_y = - self.owner.velocity_x + stumble; - } else { - self.owner.velocity_x = - -1 * self.owner.velocity_y + stumble; - self.owner.velocity_y = - -1 * self.owner.velocity_x + stumble; - } - } - } - } - if (self.health <= 0) { - sprint(self.owner, PRINT_HIGH, "Your head feels better now\n"); - self.owner.is_concussed = 0; - dremove(self); - } -}; - -void () ScannerSwitch = { - local entity te; - - if (self.ScannerOn != 1) { - te = spawn(); - te.nextthink = time + 2; - te.think = TeamFortress_Scan; - te.owner = self; - te.classname = "timer"; - te.netname = "scanner"; - sprint(self, PRINT_HIGH, "Scanner on\n"); - self.ScannerOn = 1; - - if (!(self.tf_items_flags & NIT_SCANNER_ENEMY)) - self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_ENEMY; - } else { - te = find(world, netname, "scanner"); - while (te) { - if (te.owner == self) - dremove(te); - - te = find(te, netname, "scanner"); - } - sprint(self, PRINT_HIGH, "Scanner off\n"); - self.ScannerOn = 0; - } - - Status_Refresh(self); -}; - -void () TeamFortress_Scan = { - local entity list; - local float scancost; - local float scanrange; - local float scen; - local float scfr; - local float num; - local vector lightningvec; - local float enemy_detected; - local float any_detected; - list = world; - - scanrange = 100; - self.owner.impulse = 0; - self.owner.last_impulse = 0; - if (self.owner.classname == "player") { - if (!(self.owner.tf_items & 1)) { - return; - } - scancost = 2; - if (self.owner.ammo_cells <= 0) { - sprint(self.owner, PRINT_HIGH, - "Not enough cells to run scanner\n"); - self.owner.ammo_cells = 0; - W_SetCurrentAmmo(self); - self.owner.ScannerOn = 0; - dremove(self); - return; - } - if (scancost > self.owner.ammo_cells) { - scanrange = self.owner.ammo_cells * 20; - scancost = self.owner.ammo_cells; - W_SetCurrentAmmo(self); - } - scen = 0; - scfr = 0; - if (self.owner.tf_items_flags & 1) { - scen = 1; - } - if (self.owner.tf_items_flags & 2) { - scfr = 1; - } - if ((scen == 0) && (scfr == 0)) { - sprint(self.owner, PRINT_HIGH, "No target specified\n"); - self.owner.ScannerOn = 0; - dremove(self); - return; - } - self.owner.ammo_cells = self.owner.ammo_cells - 2; - if (self.owner.ammo_cells < 0) { - self.owner.ammo_cells = 0; - } - W_SetCurrentAmmo(self); - scanrange = scanrange * 25; - list = T_RadiusScan(self.owner, scanrange, scen, scfr); - } - scen = 0; - scfr = 0; - makevectors(self.owner.v_angle); - if (list != world) { - any_detected = 1; - if ((((list.team_no > 0) && (self.owner.team_no > 0)) && - (list.team_no == self.owner.team_no)) && - ((list.classname == "player") || - (list.classname == "building_sentrygun"))) { - scfr = scfr + 1; - enemy_detected = 0; - } else { - if ((((list.goal_no > 0) && (self.owner.team_no > 0)) && - (list.goal_no == self.owner.team_no)) && - (list.classname == "item_tfgoal")) { - scfr = scfr + 1; - enemy_detected = 0; - } else { - scen = scen + 1; - enemy_detected = 1; - } - } - if (any_detected) { - lightningvec = normalize((list.origin - self.owner.origin)); - lightningvec = - lightningvec * (vlen((list.origin - self.owner.origin)) / - 5); - lightningvec = lightningvec + self.owner.origin; - msg_entity = self.owner; - - WriteByte(MSG_ONE, SVC_TEMPENTITY); - WriteByte(MSG_ONE, TE_LIGHTNING1); - WriteEntity(MSG_ONE, self.owner); - WriteCoord(MSG_ONE, self.owner.origin_x); - WriteCoord(MSG_ONE, self.owner.origin_y); - WriteCoord(MSG_ONE, self.owner.origin_z + 8); - WriteCoord(MSG_ONE, lightningvec_x); - WriteCoord(MSG_ONE, lightningvec_y); - WriteCoord(MSG_ONE, lightningvec_z + 8); - - if (self.owner.tf_items_flags & 4) { - stuffcmd(self.owner, "play misc/basekey.wav\n"); - } - num = vlen(list.origin - self.owner.origin); - num = num / 10; - num = num / 3; - num = rint(num); - self.health = num; - if (list.classname == "player") { - if ((list.playerclass == 8) && - (list.team_no != self.owner.team_no)) { - if (list.undercover_skin != 0) { - self.playerclass = list.undercover_skin; - } else { - self.playerclass = list.playerclass; - } - if (list.undercover_team != 0) { - self.team_no = list.undercover_team; - } else { - self.team_no = list.team_no; - } - } else { - self.playerclass = list.playerclass; - self.team_no = list.team_no; - } - } else { - if (list.classname == "building_sentrygun") { - self.playerclass = 13; - self.team_no = list.team_no; - } else { - if (list.classname == "item_tfgoal") { - self.playerclass = 14; - self.team_no = list.goal_no; - } - } - } - Status_Refresh(self.owner); - } - } - if ((scen == 0) && (scfr == 0)) { - self.health = 0; - Status_Refresh(self.owner); - self.nextthink = time + 2; - return; - } - W_SetCurrentAmmo(self); - self.nextthink = time + 2; - return; -}; - -void (entity inflictor, entity attacker, float bounce, - entity ignore) T_RadiusBounce = { - local float points; - local entity head; - local entity te; - local vector org; - - head = findradius(inflictor.origin, bounce + 40); - while (head) { - if (head != ignore) { - if (head.takedamage) { - org = head.origin + (head.mins + head.maxs) * 0.5; - points = 0.5 * vlen(org - inflictor.origin); - if (points < 0) - points = 0; - points = bounce - points; - - if ((head.classname != "building_dispenser") - && (head.classname != "building_sentrygun") - && (head.classname != "building_sentrygun_base") - && (points > 0)) { - head.velocity = org - inflictor.origin; - head.velocity = head.velocity * (points / 20); - if (head.classname != "player") { - if (head.flags & FL_ONGROUND) - head.flags = head.flags - FL_ONGROUND; - } else { - te = find(world, classname, "timer"); - if (old_grens == TRUE) - while (((te.owner != head) || - (te.think != - OldConcussionGrenadeTimer)) - && (te != world)) - te = find(te, classname, "timer"); - else - while (((te.owner != head) || - (te.think != ConcussionGrenadeTimer)) - && (te != world)) - te = find(te, classname, "timer"); - - if (te != world) { - if (old_grens == TRUE) { - stuffcmd(head, "v_idlescale 100\n"); - stuffcmd(head, "fov 130\n"); - te.health = 100; - te.nextthink = time + 5; - } else { - te.health = 750; - te.nextthink = time + 0.25; - } - } else { - if (old_grens == TRUE) { - stuffcmd(head, "v_idlescale 100\n"); - stuffcmd(head, "fov 130\n"); - stuffcmd(head, "bf\n"); - te = spawn(); - te.nextthink = time + 5; - te.think = OldConcussionGrenadeTimer; - te.team_no = attacker.team_no; - te.classname = "timer"; - te.owner = head; - te.health = 100; - } else { - te = spawn(); - te.nextthink = time + 0.25; - te.think = ConcussionGrenadeTimer; - te.team_no = attacker.team_no; - te.classname = "timer"; - te.owner = head; - te.health = 750; - self.owner.is_concussed = 1; - } - } - } - } - } - } - head = head.chain; - } -}; - -entity(entity scanner, float scanrange, float enemies, - float friends) T_RadiusScan = -{ - local entity head; - local float rangedist; - - rangedist = 0; - head = world; - while (rangedist <= scanrange) { - if (rangedist <= 0) { - rangedist = 1; - } - head = findradius(scanner.origin, rangedist); - while (head) { - if (head != scanner) { - if (((head.takedamage != 0) && (head.health > 0)) || - (head.classname == "item_tfgoal")) { - if (((head.classname == "player") - || (head.classname == "building_sentrygun")) - && (friends || enemies)) { - if (teamplay) { - if (((friends != 0) && (head.team_no > 0)) && - (scanner.team_no > 0)) { - if ((head.playerclass == 8) && - (head.team_no != scanner.team_no)) { - if ((head.is_feigning != 1) && - (invis_only != 1)) { - if (head.undercover_team == - scanner.team_no) { - return (head); - } - } - } else if (head.team_no == scanner.team_no) { - return (head); - } - } - if (((enemies != 0) && (head.team_no > 0)) && - (scanner.team_no > 0)) { - if ((head.playerclass == 8) && - (head.team_no != scanner.team_no)) { - if ((head.is_feigning != 1) && - (invis_only != 1)) { - if (head.undercover_team != - scanner.team_no) { - return (head); - } - } - } else if (head.team_no != scanner.team_no) { - return (head); - } - } - } else { - return (head); - } - } else if ((head.classname == "item_tfgoal") && - (friends || enemies)) { - if (teamplay) { - if ((friends != 0) && (head.goal_no > 0) - && (scanner.team_no > 0) && - (head.goal_no == scanner.team_no)) { - return (head); - } - if ((enemies != 0) && (head.team_no > 0) - && (scanner.team_no > 0) && - (head.goal_no != scanner.team_no)) { - return (head); - } - } - } - } - } - head = head.chain; - } - rangedist = rangedist + 100; - } - return (world); -}; diff --git a/share/animate.qc b/share/animate.qc new file mode 100644 index 000000000..3dbd8482b --- /dev/null +++ b/share/animate.qc @@ -0,0 +1,365 @@ +struct anim_t { + float num_frames, num_wf; + int frames[20]; + int weaponframes[9]; + int muzzle_flash; // muzzle flash on weaponframe index 0 + int loop; + int freeze_last_frame; // used for feigning death +}; + +void think_nop() {} + +void W_ChangeToBestWeapon(); +void Pred_Sound(SoundIndex snd, float vol = 1); + +#ifdef SSQC + +void W_PrintWeaponMessage(); +void W_FireAssaultCannon(); +int W_FireAxe(); +int W_FireKnife(); +int W_FireMedikit(); +int W_FireSpanner(); + +void SuperDamageSound(); + +void player_run(); +void player_stand1(); +void player_asscan_fire(); +void player_asscan_down1(); + +inline void *thinkindex() { return &self.client_thinkindex; } +static inline float is_attacking() { return self.button0; } +static inline float is_intermission() { return intermission_running; } +static inline vector get_velocity() { return self.velocity; } +static inline void set_weapon_frame(int f) { self.weaponframe = f; } +static inline float get_clip_fired() { + return self.clip_fired[SlotIndex(self.current_slot)]; +} + +void FO_SetClientThink(void() func, float offset, float override = TRUE) { + self.think = think_nop; + self.nextthink = 0; + + self.client_think = func; + self.client_nextthink = self.client_time + offset; +} + +void client_anim_frames(void() parent_thunk, void() extra, anim_t* anim) { + float tidx = *thinkindex()++ - 1; + + int wfi = tidx % anim->num_wf; + self.weaponframe = anim->weaponframes[wfi] ?: anim->weaponframes[0] + wfi; + if (wfi == 0 && anim->muzzle_flash) + muzzleflash(); + + int fi = tidx % anim->num_frames; + if (tidx >= anim->num_frames && anim->freeze_last_frame) + fi = anim->num_frames - 1; + self.frame = anim->frames[fi] ?: anim->frames[0] + fi; + + if (fi == anim->num_frames - 1 && !anim->loop && !anim->freeze_last_frame) + FO_SetClientThink(player_run, 0.1); + else + FO_SetClientThink(parent_thunk, 0.1); + + if (extra != think_nop) + extra(); +} + +static void axe_extra() { + float weapon = FO_CurrentWeapon(); + float* flag; + int() w_attack; + + // We'd use default below but compiler can't recognize that init is + // guaranteed then. + flag = &superaxe; + w_attack = W_FireAxe; + + switch (weapon) { + case WEAP_MEDIKIT: + flag = &supermedikit; + w_attack = W_FireMedikit; + break; + case WEAP_SPANNER: + flag = &superspanner; + w_attack = W_FireSpanner; + break; + case WEAP_KNIFE: + flag = &superknife; + w_attack = W_FireKnife; + break; + } + + if (*thinkindex() == 2) // 1-index and already incremented. + self.hit_in_current_animation = FALSE; + + if ((*flag && !self.hit_in_current_animation) || + (!*flag && self.weaponframe == 3)) + self.hit_in_current_animation = w_attack(); + + if (weapon == WEAP_KNIFE && superknife && superknife_multihit) + self.hit_in_current_animation = FALSE; +} + +void player_run() { + ASSERTD_EQ(self.tfstate & TFSTATE_AC_MASK, 0); + self.client_thinkindex = 0; + FO_SetClientThink(player_run, 0.1); + + self.frame = 6; + self.weaponframe = 0; + + if (!(self.velocity_x || self.velocity_y)) { + self.walkframe = 0; + player_stand1(); + return; + } + + // Note: Reverses animation when walking backwards + self.walkframe = (self.walkframe + (self.movement_x < 0 ? -1 : 1) + 6) % 6; + self.frame = (IsSlotMelee(self.current_slot) ? 0 : 6) + self.walkframe; +}; + +void player_stand1() { + self.client_thinkindex = 0; + FO_SetClientThink(player_stand1, 0.1); + + self.frame = 17; + self.weaponframe = 0; + + if (self.velocity_x || self.velocity_y) { + self.walkframe = 0; + player_run(); + return; + } + + if (IsSlotMelee(self.current_slot) || self.is_detpacking) { + if (self.walkframe >= 12) + self.walkframe = 0; + self.frame = 17 + self.walkframe; + } else { + if (self.walkframe >= 5) + self.walkframe = 0; + self.frame = 12 + self.walkframe; + } + self.walkframe++; +}; +#else /* CSQC */ +static inline void *thinkindex() { return &pstate_pred.client_thinkindex; } +static inline float is_attacking() { return input_buttons & BUTTON0; } +static inline float is_intermission() { return intermission; } +static void SuperDamageSound() {} // TODO +static inline vector get_velocity() { return PM_Vel(); } +static inline void set_weapon_frame(float f) { pstate_pred.weaponframe = f; } +static inline void muzzleflash() {} +float WP_CurrentClipFired(); +static inline float get_clip_fired() { return WP_CurrentClipFired(); } + +void player_run() { + pstate_pred.client_nextthink = 0; + pstate_pred.client_thinkindex = 0; + pstate_pred.weaponframe = 0; +} + +// When `extra()` is specified, it is responsible ending looped animations. +// [ Typically because +attack (button0) has been released. ] +void client_anim_frames(void() parent_thunk, void() extra, anim_t* anim) { + float tidx = *thinkindex()++ - 1; + + float loop = anim->loop; + if (loop && extra == think_nop && !(input_buttons & BUTTON0)) + loop = TRUE; + + if (tidx > anim->num_frames - 1 && !loop) { + player_run(); + return; + } else { + pstate_pred.client_nextthink = pstate_pred.client_time + 0.1; + } + + float fi = tidx % anim->num_frames; + float wfi = tidx % anim->num_wf; + + float wf = anim->weaponframes[wfi] ?: anim->weaponframes[0] + wfi; + pstate_pred.weaponframe = wf; + + if (extra != think_nop) + extra(); +} + +static void axe_extra() {} + +void player_asscan_fire(); +void player_asscan_upN(); +void player_asscan_downN(); + +void player_assault_cannon() { + switch (pstate_pred.tfstate & TFSTATE_AC_MASK) { + case TFSTATE_AC_SPINUP: player_asscan_upN(); break; + case TFSTATE_AC_SPINNING: player_asscan_fire(); break; + case TFSTATE_AC_SPINDOWN: player_asscan_downN(); break; + } +} + +void Attack_Finished(float attack_time); +float WP_ReloadCurrentIfNeeded(); +static inline float FO_CheckForReload() { return WP_ReloadCurrentIfNeeded(); } +#endif + +void W_FireSpikes(float ox); +// Shared by both client and server side. +void nail_extra_csqc_ssqc() { + if (!is_attacking() || is_intermission() || !IsSlotNull(self_queue_slot())) { + player_run(); + return; + } + + FO_WeapInfo* wi = FO_GetWeapInfo(FO_CurrentWeapon()); + if (wi->needs_reload && FO_CheckForReload()) { + player_run(); + return; + } + + float tidx = *thinkindex() % 2; // 1 based, and incremented. + if (tidx == 0 || !OldNgRof()) { + SuperDamageSound(); + W_FireSpikes(tidx ? 4 : -4); + } +} + +void player_asscan_down1(); +static float CheckNeedAssaultCannonDown() { + FO_WeapInfo* wi = FO_GetWeapInfo(WEAP_ASSAULT_CANNON); + + if ((!is_attacking() || (get_shells() < 1)) || is_intermission() || + get_clip_fired() == wi->clip_size) { + player_asscan_down1(); + return TRUE; + } + return FALSE; +} + +void asscan_up_extra() { + float tidx = *thinkindex() - 1; // Already incremented. + *self_tf_state() = (*self_tf_state() & ~TFSTATE_AC_MASK) | + (TFSTATE_AC_SPINUP | TFSTATE_AIMING); + + if (CheckNeedAssaultCannonDown()) + return; + + if (tidx == 1) + Pred_Sound(SND_ASSCAN_UP); + SuperDamageSound(); + + FO_WeapInfo* wi = FO_GetWeapInfo(WEAP_ASSAULT_CANNON); + if (tidx == 6) // 600ms spin up + player_asscan_fire(); + else + Attack_Finished(wi->attack_time); +} + +void W_FireAssaultCannon(); +void asscan_fire_extra() { + float tidx = *thinkindex() - 1; // Already incremented. + *self_tf_state() = (*self_tf_state() & ~TFSTATE_AC_MASK) | + (TFSTATE_AC_SPINNING | TFSTATE_AIMING); + + if (CheckNeedAssaultCannonDown()) + return; + + FO_WeapInfo* wi = FO_GetWeapInfo(WEAP_ASSAULT_CANNON); + + if (vlen(get_velocity()) <= 90 && !(*self_tf_state () & TFSTATE_LOCK)) { + if (tidx % 2 == 1) { // 1-based + muzzleflash(); + Pred_Sound(SND_ASSCAN_FIRE); + } + + SuperDamageSound(); + + set_weapon_frame(tidx % 2 ? 2 : 4); + // Halve firing speed when moving too fast. + if (vlen(get_velocity()) < 30 || tidx % 2 == 1) + W_FireAssaultCannon(); + } else { + Pred_Sound(SND_ASSCAN_SPIN, 0.5); + set_weapon_frame(tidx % 2 ? 2 : 0); + } + Attack_Finished(wi->attack_time); +} + +void asscan_down_extra() { + float tidx = *thinkindex() - 1; // Already incremented. + FO_WeapInfo* wi = FO_GetWeapInfo(WEAP_ASSAULT_CANNON); + *self_tf_state() = (*self_tf_state() & ~TFSTATE_AC_MASK) | + TFSTATE_AIMING | TFSTATE_AC_SPINDOWN; + + if (tidx == 1) + Pred_Sound(SND_ASSCAN_DOWN, 0.8); + + if (tidx >= 15) { // 1.5s down + *self_tf_state() &= ~(TFSTATE_AC_MASK | TFSTATE_AIMING); + player_run(); + if (self.ammo_cells < 7 || !FO_CheckForReload()) + W_ChangeToBestWeapon(); + } + Attack_Finished(wi->attack_time); +}; + +anim_t anim_rocket = { 6, 6, {107}, {1}, TRUE }; +void player_rocketN() { client_anim_frames(player_rocketN, think_nop, &anim_rocket); } +void player_rocket1() { *thinkindex() = 1; player_rocketN(); } + +anim_t anim_shotgun = { 6, 6, {113}, {1}, TRUE }; +void player_shotN() { client_anim_frames(player_shotN, think_nop, &anim_shotgun); } +void player_shot1() { *thinkindex() = 1; player_shotN(); } + +anim_t anim_autorifle = { 3, 3, {113, 114, 118}, {1, 2, 6}, TRUE }; +void player_autorifleN() { client_anim_frames(player_autorifleN, think_nop, &anim_autorifle); } +void player_autorifle1() { *thinkindex() = 1; player_autorifleN(); } + +anim_t anim_axea = { 4, 4, {119}, {1}, FALSE }; +void player_axeN() { client_anim_frames(player_axeN, axe_extra, &anim_axea); } +void player_axe1() { *thinkindex() = 1; player_axeN(); } + +anim_t anim_axeb = { 4, 4, {124}, {5}, FALSE }; +void player_axebN() { client_anim_frames(player_axebN, axe_extra, &anim_axeb); } +void player_axeb1() { *thinkindex() = 1; player_axebN(); } + +void player_spannerN() { client_anim_frames(player_spannerN, axe_extra, &anim_axea); } +void player_spanner1() { *thinkindex() = 1; player_spannerN(); } + +void player_knifeN() { client_anim_frames(player_knifeN, axe_extra, &anim_axea); } +void player_knife1() { *thinkindex() = 1; player_knifeN(); } + +void player_knifebN() { client_anim_frames(player_knifebN, axe_extra, &anim_axeb); } +void player_knifeb1() { *thinkindex() = 1; player_knifebN(); } + +void player_medikitN() { client_anim_frames(player_medikitN, axe_extra, &anim_axea); } +void player_medikit1() { *thinkindex() = 1; player_medikitN(); } + +void player_medikitbN() { client_anim_frames(player_medikitbN, axe_extra, &anim_axeb); } +void player_medikitb1() { *thinkindex() = 1; player_medikitbN(); } + +anim_t anim_nailgun = { 2, 8, {103, 104}, {1}, TRUE, TRUE }; +void player_nailN() { client_anim_frames(player_nailN, nail_extra_csqc_ssqc, &anim_nailgun); } +void player_nail1() { *thinkindex() = 1; player_nailN(); } + +anim_t anim_flamethrower = { 6, 6, {113}, {1}, TRUE, FALSE }; +void player_flamethrowerN() { + client_anim_frames(player_flamethrowerN, think_nop, &anim_flamethrower); +} +void player_flamethrower1() { *thinkindex() = 1; player_flamethrowerN(); } + +anim_t anim_asscan_up = { 1, 4, {103}, {1, 1, 2, 2}, FALSE, TRUE}; +anim_t anim_asscan_fire = { 1, 2, {103}, {2, 4}, FALSE, TRUE}; +anim_t anim_asscan_down = { 1, 6, {103}, {1, 1, 2, 2, 3, 3}, FALSE, TRUE}; + +void player_asscan_upN() { client_anim_frames(player_asscan_upN, asscan_up_extra, &anim_asscan_up); } +void player_asscan_up1() { *thinkindex() = 1; player_asscan_upN(); } + +void player_asscan_fire() { client_anim_frames(player_asscan_fire, asscan_fire_extra, &anim_asscan_fire); } +void player_asscan_downN() { client_anim_frames(player_asscan_downN, asscan_down_extra, &anim_asscan_down); } +void player_asscan_down1() { *thinkindex() = 1; client_anim_frames(player_asscan_downN, asscan_down_extra, &anim_asscan_down); } diff --git a/share/classes.qc b/share/classes.qc new file mode 100644 index 000000000..49441d688 --- /dev/null +++ b/share/classes.qc @@ -0,0 +1,215 @@ +#ifdef SSQC +inline int* prng_state(int type) { return &self.prng_base[type]; } +#else +inline int* prng_state(int type) { return &pstate_pred.prng_base[type]; } +#endif + +int lfsr_prng_raw(int v) { // A 16-bit xor-shift LFSR. + v ^= (v >> 7); + v ^= (v << 9); + v &= 65535; + v ^= (v >> 13); + return v; +} + +float lsfr_prng(int prev) { + return lfsr_prng_raw(prev) / 65535.0; +} + +float shared_prng(int type) { + return (*prng_state(type) = lfsr_prng_raw(*prng_state(type))) / 65535.0; +} + +float shared_crandom(int type) { + return 2 * (shared_prng(type) - 0.5); +} + +#ifdef CSQC +float WP_GetAmmo(float ammo_type); +float get_shells() { return WP_GetAmmo(AMMO_SHELLS); } +static inline vector get_origin() { return PM_Org(); } +#else +float get_shells() { return self.ammo_shells; } +static inline vector get_origin() { return self.origin; } +#endif + +// Randomize by ammo index for consistency even if a packet/prediction misses. +static float hwguy_random_index; + +void reset_hwguy_random() { + hwguy_random_index = (*prng_state(PRNG_HWGUY) + get_shells() * 211) & 65535; + if (hwguy_random_index == 0) + hwguy_random_index = (get_shells() + 1) * 211; +} + +float hwguy_random() { + float index = hwguy_random_index; + float r = (hwguy_random_index = lfsr_prng_raw(hwguy_random_index)) / 65535.0; + return r; +} + +float hwguy_crandom() { + return 2 * (hwguy_random() - 0.5); +} + +void FO_FireAssCanPellet(vector org, vector spread_dir, float var_speed, int index); + +void WeapPred_FireAssCan(vector vangle, float shotcount, + vector spread) { + vector bullet_dir, spread_dir, rand_dir, org; + float bullet_speed, var_speed; + + reset_hwguy_random(); + + makevectors(vangle); + + // In front of player model and down towards gun + bullet_speed = FPP_Get(FPP_ASSAULT_CANNON)->speed; + bullet_dir = v_forward; + + for (int i = 0; i < shotcount; i++) { + var_speed = hwguy_crandom()*10 + bullet_speed; // Slight speed variance + rand_dir = (hwguy_crandom()*spread_x) * v_right + + (hwguy_crandom()*spread_y) * v_up; + spread_dir = bullet_dir + rand_dir; + org = get_origin() + '0 0 10' + rand_dir; + + FO_FireAssCanPellet(org, spread_dir, var_speed, i); + } +} + +float Class_MaxSpeed(float playerclass) { + switch (playerclass) { + case PC_UNDEFINED: return SPEC_MAXSPEED; + case PC_SCOUT: return PC_SCOUT_MAXSPEED; + case PC_SNIPER: return PC_SNIPER_MAXSPEED; + case PC_SOLDIER: return PC_SOLDIER_MAXSPEED; + case PC_DEMOMAN: return PC_DEMOMAN_MAXSPEED; + case PC_MEDIC: return PC_MEDIC_MAXSPEED; + case PC_HVYWEAP: return PC_HVYWEAP_MAXSPEED; + case PC_PYRO: return PC_PYRO_MAXSPEED; + case PC_SPY: return PC_SPY_MAXSPEED; + case PC_ENGINEER: return PC_ENGINEER_MAXSPEED; + case PC_CIVILIAN: return PC_CIVILIAN_MAXSPEED; + default: return 0; + } +} + +static float NextToWall(entity ent) { + if ((pointcontents(ent.origin + [ent.maxs_x + 2, 0, 0]) == CONTENT_SOLID) || + (pointcontents(ent.origin + [ent.mins_x - 2, 0, 0]) == CONTENT_SOLID) || + (pointcontents(ent.origin + [0, ent.maxs_y + 2, 0]) == CONTENT_SOLID) || + (pointcontents(ent.origin + [0, ent.mins_y - 2, 0]) == CONTENT_SOLID)) + return TRUE; + return FALSE; +} + +const float CONC_P = 0.25; +const float CONC_HZ = 1/CONC_P; + +void Conc_Stumble(entity ent, float stumble, float flip) { + if ((ent.flags & (FL_INWATER | FL_ONGROUND) == 0) || + (*self_tf_state() & TFSTATE_FEIGNED)) + return; + + if (NextToWall(ent) && ent.velocity == '0 0 0') + return; + + // [*]: This preserves what looks to be a long-standing bug where y-vel ends + // up propagating due to missed temporary to hold vel_x. + ent.velocity_x = flip * ent.velocity_y + stumble; + ent.velocity_y = flip * ent.velocity_x + stumble; +} + +void Conc_Jump(ConcState* cs, entity ent) { + float stumble = shared_crandom(PRNG_CONC) * cs->mag / 10; + float flip = (shared_prng(PRNG_CONC) < 0.5) ? -1 : 1; + + // See [*] above. + ent.velocity_x = flip * ent.velocity_y + stumble; + ent.velocity_y = flip * ent.velocity_x + stumble; +} + +#ifdef SSQC +static inline void ConcAction(entity ent, float itime, float mag, float flip) { + Conc_Stumble(ent, mag, flip); +} +#else +void PM_AddNudgeConc(float itime, float mag, float flip); +static inline void ConcAction(entity ent, float itime, float mag, float flip) { + PM_AddNudgeConc(itime, mag, flip); +} +#endif + +float Conc_Update(ConcState *cs, entity ent, float ctime) { + if (!cs->mag || ctime < cs->next) + return FALSE; + + while (ctime >= cs->next) { + cs->mag = max(cs->mag - 1, 0); + if (!cs->mag) { + *self_tf_state() &= ~TFSTATE_CONC; + break; + } + + float mag = shared_crandom(PRNG_CONC) * cs->mag * 10; + float flip = (shared_prng(PRNG_CONC) < 0.5) ? -1 : 1; + ConcAction(ent, cs->next, mag, flip); + cs->next += CONC_P; + } + + return TRUE; +} + +float Class_ScaleMoment(float playerclass, float damage) { + if (playerclass != PC_HVYWEAP) + return damage; + return damage <= 50 ? 0 : damage / 4; +} + +enum { + kLaunch, + kLand +}; + +float NB_UseNewConc() { + return fo_config.new_balance_flags & NBF_CONC_NEW_CAP; +} + +float NB_NoCap() { + return fo_config.new_balance_flags & NBF_NO_CAP; +} + + +void NB_ConcCap(entity ent, float speed) { + vector planar_vel = [ent.velocity_x, ent.velocity_y, 0]; + + if (vlen(planar_vel) > speed) { + planar_vel = normalize(planar_vel) * speed; + } + + ent.velocity_x = planar_vel.x; + ent.velocity_y = planar_vel.y; +} + +void NB_ConcCapAction(entity ent, float player_class, float* tfstate, + float gtime, float* cap_time, float action) { + if (!NewBalanceActive() || !NB_UseNewConc()) + return; + + const float kLockout = 0.5; + + if (action == kLaunch) { + if (player_class == PC_MEDIC && !NB_NoCap()) { + *tfstate |= TFSTATE_CONC_CAP; + *cap_time = gtime + kLockout; + } + + NB_ConcCap(ent, NB_CONC_CAP_AIR); + } else if (action == kLand && (*tfstate & TFSTATE_CONC_CAP) && + gtime > *cap_time) { + float cap = *cap_time; + *tfstate &= ~TFSTATE_CONC_CAP; + NB_ConcCap(ent, NB_CONC_CAP_LAND); + } +} diff --git a/share/common_helpers.qc b/share/common_helpers.qc new file mode 100644 index 000000000..b2d0b55e2 --- /dev/null +++ b/share/common_helpers.qc @@ -0,0 +1,35 @@ +string ClassToString(float num) +{ + if (num == 1) return "Scout"; + if (num == 2) return "Sniper"; + if (num == 3) return "Soldier"; + if (num == 4) return "Demoman"; + if (num == 5) return "Medic"; + if (num == 6) return "HWGuy"; + if (num == 7) return "Pyro"; + if (num == 8) return "Spy"; + if (num == 9) return "Engineer"; + if (num == 11) return "Civilian"; + if (num == 13) return "Sentry Gun"; + if (num == 14) return "Goal Item"; + return ""; +} + +string TeamToString(float num) +{ + if (num == 1) return "Blue"; + if (num == 2) return "Red"; + if (num == 3) return "Yellow"; + if (num == 4) return "Green"; + return " "; +} + +void FO_Sound(entity e, float chan, string samp, float vol, float atten); + +#ifdef SSQC + +inline float ClientPred_Enabled(entity client, float pred_flag) { + return infokeyf(client, "fo_wpp_status") & pred_flag; +} + +#endif diff --git a/share/common_vote.qc b/share/common_vote.qc new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/share/common_vote.qc @@ -0,0 +1 @@ + diff --git a/share/commondefs.qc b/share/commondefs.qc new file mode 100644 index 000000000..59129bb33 --- /dev/null +++ b/share/commondefs.qc @@ -0,0 +1,322 @@ +struct FPPState { + int index; + float expires_at; + +#ifdef SSQC + int flags; + float dynamic_dt; +#endif + + union { + int aux; + int gren_type; + int ammo_index; + }; +}; + +.FPPState fpp; + +struct ConcState { + float next, mag; +}; +.ConcState conc_state; + + +enumflags { + FPF_NO_REWIND, +}; + +var struct { + float min_ping_ms; + + float qc_physics; + float static_newmis_ms; + float dynamic_newmis_ms; + float rewind_flags; + float max_rewind_ms; + float max_rewind_slow_projectile_ms; + float max_rewind_fast_projectile_ms; + float max_rewind_grenade_ms; + float rewind_fast_projectile_thresh; + float wp_default_min_ping_ms; + float clown_flags; + float clown_grav; + + float old_ng_rof; + + float predict_flags; + float tfx_flags; + + float wp_global_disable; + float gren_beta_disable; + + float fo_concuss; + float rj; + + float new_balance; + float new_balance_flags; +} fo_config; + +enumflags { + REWIND_PROJ_FIRE, + REWIND_PROJ_TRAVEL, + REWIND_GRENADES, + REWIND_SENDEVENT, + REWIND_FORWARD_PROJ_SELFKNOCK, + REWIND_DOUBLE_COL, + REWIND_FORWARD_DOORS, +}; + +string REWIND_DESC[] = { + "rewind projectile on firing", + "rewind on projectile travel", + "rewind grenade throws", + "use sendevent augmentation (allows race w/ death rewind)", + "forward projectile self-knockback", + "additional collision checks", + "open doors earlier for hpbs [requires rfd=1]", +}; + +const float REWIND_DEFAULT_FLAGS = REWIND_PROJ_FIRE | + REWIND_PROJ_TRAVEL | + REWIND_FORWARD_PROJ_SELFKNOCK | + REWIND_SENDEVENT | + REWIND_DOUBLE_COL; + + +float RewindFlagEnabled(float flag) { + return fo_config.rewind_flags & flag; +} + +enumflags { + TFX_PREMATCH_EVERYTHING, + TFX_SPEC_OUTLINE, + TFX_SPEC_SEEFLAG, + TFX_SPEC_GRENTIMER, + TFX_OFFENSE_SEEFLAG, + TFX_DEFENSE_SEEFLAG, + TFX_OFFENSE_OUTLINE, + TFX_DEFENSE_OUTLINE, + TFX_OFFENSE_GRENTIMER, + TFX_DEFENSE_GRENTIMER, +}; + +string TFX_DESC[] = { + "Prematch see everything", + "Spectators see outlines", + "Spectators see flags", + "Spectators see grenade timers", + "Offense sees flag (always)", + "Defense sees flag, when not carried", + "Offense sees grentimers", + "Defense sees grentimers", + "Offense sees outlines", + "Defense sees outlines", +}; + +float TFxEnabled(float flag) { + return fo_config.tfx_flags & flag; +} + +const float TFX_DEFAULT_FLAGS = 0; + +enumflags { + FOC_ON_DEF, // apply foc_defaults + FOC_ON_NODEF, + FOC_AFF_MED, + FOC_EASY_AIR, + FOC_EASY_GROUND, + FOC_DISTANCE, + FOC_RED_MED, +}; + +const float foc_defaults = FOC_AFF_MED | FOC_EASY_AIR; + +string FO_CONC[] = { + "on (w/ defaults)", + "on (no defaults)", + "affects medic", + "easy air control", + "easy ground control", + "distance based", + "reduce medic effect", +}; + +enumflags { + CLOWN_FAST_PROJECTILES, + CLOWN_RUBBERGREN, + CLOWN_PROJ_GRAVITY, + CLOWN_LETHAL_TRANQ, + CLOWN_CONC, + CLOWN_STICKY_GRENS, + CLOWN_STICKY_PIPES, + CLOWN_SPAM_GRENADES, +}; + +string CLOWN_DESC[] = { + "fast projectiles", + "rubber grenades", + "projectile gravity", + "elephant tranq", + "perma conc", + "sticky grenades", + "sticky pipes", + "no grenade prime-time", +}; + +inline float IsClownMode(int flag) { + return fo_config.clown_flags & flag; +} + +const float SERVER_FPS = 77; +const float SERVER_FRAME_DT = 1/SERVER_FPS; +const float SERVER_FRAME_MS = SERVER_FRAME_DT * 1000.0; + +.vector s_origin; + +#define MAX_FLAGINFO_LINES 10 +#define ENG_BUILDING_DISMANTLE_DISTANCE 100 +#define ENG_BUILDING_MAINT_DISTANCE 80 +#define ENG_DISPENSER_COST 100 +#define ENG_SENTRY_COST 130 + +#define GAMEMODE_CLAN 1 +#define GAMEMODE_QUAD 2 +#define GAMEMODE_DUEL 4 +#define GAMEMODE_VOTE 8 + +#define MSG_FLAGINFOINIT 1 +#define MSG_FLAGINFO 2 +// 3 +#define MSG_SBAR 4 +#define MSG_GRENPRIMED 5 +#define MSG_CLIENT_MENU 6 +#define MSG_TEAMS_UPDATE 7 +#define MSG_CLASSES_UPDATE 8 +#define MSG_SENTRY_POS 9 +#define MSG_DISPENSER_POS 10 +#define MSG_SERVER_ADMIN_INFO 11 +#define MSG_CAPTAINS 12 +#define MSG_MOTD 13 +#define MSG_PREMATCH 14 +#define MSG_GRENTHROWN 15 +#define MSG_ID 16 +#define MSG_TEAM_SCORES 17 +#define MSG_VOTE_MAPS 18 +#define MSG_VOTE_UPDATE 19 +#define MSG_VOTE_MAP_ADD 20 +#define MSG_VOTE_MAP_DELETE 21 +#define MSG_PAUSE 22 +#define MSG_UNPAUSE 23 +#define MSG_TFX_GRENTIMER 24 +#define MSG_QUAD_ROUND_BEGIN 25 +#define MSG_LOGIN 26 +#define MSG_HITFLAG 27 +#define MSG_RELOADSOUND 28 +#define MSG_FLAG_PICKUP 29 +#define MSG_FLAG_DROP 30 +#define MSG_BUILDING 31 +#define MSG_PLAYER_DIE 32 +#define MSG_PLAYER_SPAWN 33 + +#define FLAGINFO_HOME 1 +#define FLAGINFO_CARRIED 2 +#define FLAGINFO_DROPPED 3 +#define FLAGINFO_RETURNING 4 +#define FLAGINFO_LOCATION 5 +#define FLAGINFO_NOLOCATION 6 + +#define FLAGINFO_ICON_FLAG 0 +#define FLAGINFO_ICON_BUTTON 1 + +#define MSG_MENU_SPY_FLAG_IVIS_ONLY 1 +#define MSG_MENU_SPY_FLAG_UNDERCOVER 2 +#define MSG_MENU_SPY_FLAG_FEIGNING 4 + +#define CLIENT_MENU_TEAM 1 +#define CLIENT_MENU_CLASS 2 +#define CLIENT_MENU_DROPAMMO 3 +#define CLIENT_MENU_SCOUT 4 +#define CLIENT_MENU_SPY 5 +#define CLIENT_MENU_SPY_SKIN 6 +#define CLIENT_MENU_SPY_TEAM 7 +#define CLIENT_MENU_DETPACK 8 +#define CLIENT_MENU_BUILD 9 +#define CLIENT_MENU_ROTATE_SENTRY 10 +#define CLIENT_MENU_FIX_DISPENSER 11 +#define CLIENT_MENU_USE_DISPENSER 12 +#define CLIENT_MENU_ADMIN 13 +#define CLIENT_MENU_ADMIN_KICK 14 +#define CLIENT_MENU_ADMIN_BAN 15 +#define CLIENT_MENU_ADMIN_FORCE_SPEC 16 +#define CLIENT_MENU_ADMIN_QUAD_TIMELIMIT 17 +#define CLIENT_MENU_ADMIN_TIMELIMIT 18 +#define CLIENT_MENU_ADMIN_FRAGLIMIT 19 +#define CLIENT_MENU_VOTE 20 +#define CLIENT_MENU_CAPTAIN_1 21 +#define CLIENT_MENU_CAPTAIN_2 22 +#define CLIENT_MENU_CAPTAIN_PICK 23 +#define CLIENT_MENU_MAPS 24 + +#define BUTTON0 1 +/* BUTTON1 not supported */ +#define BUTTON2 2 +#define BUTTON3 4 +#define BUTTON4 8 +#define BUTTON5 16 +#define BUTTON6 32 +#define BUTTON7 64 +#define BUTTON8 128 +// #define BUTTON9 2048 +// #define BUTTON10 4096 +// #define BUTTON11 8192 +// #define BUTTON12 16384 +// #define BUTTON13 32768 +// #define BUTTON14 65536 +// #define BUTTON15 131072 +// #define BUTTON16 262144 + +enumflags { + HITFLAG_NOARMOUR, + HITFLAG_KILLINGBLOW, + HITFLAG_MEATSHOT, + HITFLAG_HEADSHOT, + HITFLAG_SELF, + HITFLAG_FRIENDLY, + HITFLAG_CUSS, + HITFLAG_FEIGNEDENEMY, + HITFLAG_NOAUDIO, + HITFLAG_NOTEXT, + HITFLAG_IMFLASHED, + HITFLAG_KILLEDUNDERCOVERSPY, +}; + +enumflags { + CSQC_WEAP_PRED, + CSQC_PROJ_PRED, + CSQC_PMOVE, + CSQC_FORCE_POS, + CSQC_SNIPER_SIGHT, +}; + +#ifdef SSQC +float new_balance; +#endif + +float NewBalanceActive() { +#ifdef CSQC + return fo_config.new_balance; +#else + return new_balance & 1; +#endif +} + +enumflags { + NBF_CONC_NEW_CAP, + NBF_NO_CAP, +}; + +string NBF_DESC[] = { + "Reworked conc speed cap", + "No conc speed cap", +}; diff --git a/share/debug.qc b/share/debug.qc new file mode 100644 index 000000000..2093491e6 --- /dev/null +++ b/share/debug.qc @@ -0,0 +1,77 @@ +#ifdef SSQC + +#define IS_CSQC 0 +#define IS_SSQC 1 + +// printf that works in any context +#define printf(...) bprint(PRINT_HIGH, sprintf(__VA_ARGS__)) +#define printd(...) dprint(sprintf(__VA_ARGS__)) + +#elif defined(CSQC) + +#define IS_CSQC 1 +#define IS_SSQC 0 + +#define printf(...) print(sprintf(__VA_ARGS__)) + +#endif + +inline string qc_prefix() { +#ifdef SSQC + return "SSQC"; +#else + return "CSQC"; +#endif +} + +#define MSEC 0.001 +#define SEC 1 + +#define errors(fmt, ...) error(sprintf("%s:%d Error> " fmt, __FILE__, __LINE__)) +#define errorf(fmt, ...) error(sprintf("%s:%d Error> " fmt, __FILE__, __LINE__, __VA_ARGS__)) + +#define _ASSERT_OP(_op, _invop, _fmt, _val1, _v2) \ + if (!((_val1) _op (_v2))) \ + errorf("Expected: %s " #_op " %s, but: " #_fmt " " #_invop " " #_fmt "\n", #_val1, #_v2, _val1, _v2) + +#define __ASSERT_OP(_op, _invop, _fmt, _v1, _v2, __v1, __v2) \ + if (!((__v1) _op (__v2))) \ + errorf("Expected: %s " #_op " %s, but: " #_fmt " " #_invop " " #_fmt "\n", #_v1, #_v2, __v1, __v2) + +#define ASSERTF_EQ(_v1, _v2) _ASSERT_OP(==, !=, %0.2f, _v1, _v2) +#define ASSERTF_NE(_v1, _v2) _ASSERT_OP(!=, ==, %0.2f, _v1, _v2) +#define ASSERTF_GT(_v1, _v2) _ASSERT_OP( >, <=, %0.2f, _v1, _v2) +#define ASSERTF_GE(_v1, _v2) _ASSERT_OP(>=, <, %0.2f, _v1, _v2) +#define ASSERTF_LT(_v1, _v2) _ASSERT_OP( <, >=, %0.2f, _v1, _v2) +#define ASSERTF_LE(_v1, _v2) _ASSERT_OP(<=, <, %0.2f, _v1, _v2) + + +// Work around general weird behaviors with __int +float __as_float(float v) { return v; } + +#define ASSERTD_EQ(_v1, _v2) __ASSERT_OP(==, !=, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) +#define ASSERTD_NE(_v1, _v2) __ASSERT_OP(!=, ==, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) +#define ASSERTD_GT(_v1, _v2) __ASSERT_OP( >, <=, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) +#define ASSERTD_GE(_v1, _v2) __ASSERT_OP(>=, <, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) +#define ASSERTD_LT(_v1, _v2) __ASSERT_OP( <, >=, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) +#define ASSERTD_LE(_v1, _v2) __ASSERT_OP(<=, <, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) + +#define ASSERTS_EQ(_v1, _v2) _ASSERT_OP(==, !=, %s, _v1, _v2) +#define ASSERTS_NE(_v1, _v2) _ASSERT_OP(!=, ==, %s, _v1, _v2) + +#define ASSERTF_TRUE(_v1) ASSERTF_EQ(_v1, TRUE) +#define ASSERTF_FALSE(_v1) ASSERTF_EQ(_v1, FALSE) + + +#define __CONCAT(_a, _b) _a ## _b +#define _PRINT_ONCE(_n, ...) \ + do { static float _n; if (!_n) { _n = TRUE; printf(__VA_ARGS__); } } while (0) +#define _PRINT_EVERY(_n, _p, ...) \ + do { static float _n; if (time > _n + _p) { _n = time + _p; printf(__VA_ARGS__); } } while (0) + +#define PRINT_ONCE(...) _PRINT_ONCE(__CONCAT(__once, __LINE__), __VA_ARGS__) +#define PRINT_EVERY(_p, ...) _PRINT_EVERY(__CONCAT(__every, __LINE_), _p, __VA_ARGS__) + +#define EPS 0.001 +#define FEQ(_v1, _v2) (fabs(_v1 - _v2) < EPS) +#define FNEQ(_v1, _v2) (fabs(_v1 - _v2) >= EPS) diff --git a/share/defs.h b/share/defs.h new file mode 100644 index 000000000..d56dbd4b8 --- /dev/null +++ b/share/defs.h @@ -0,0 +1,1680 @@ +//============================================================================ +// VARS NOT REFERENCED BY C CODE +//============================================================================ + +#ifndef VER +#define VER "unknown" +#endif + +#ifndef REV +#define REV "unk" +#endif + +// +// constants +// + +#define FALSE 0 +#define TRUE 1 + +// edict.flags +#define FL_FLY 1 +#define FL_SWIM 2 +#define FL_CLIENT 8 // set for all client edicts +#define FL_INWATER 16 // for enter / leave water splash +#define FL_MONSTER 32 +#define FL_GODMODE 64 // player cheat +#define FL_NOTARGET 128 // player cheat +#define FL_ITEM 256 // extra wide size for bonus items +#define FL_ONGROUND 512 // standing on something +#define FL_PARTIALGROUND 1024 // not all corners are valid +#define FL_WATERJUMP 2048 // player jumping out of water + +#define FL_JUMPRELEASED 4096 // for jump debouncing + +// edict.movetype values +#define MOVETYPE_NONE 0 // never moves +//#define MOVETYPE_ANGLENOCLIP 1 +//#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // players only +#define MOVETYPE_STEP 4 // discrete, not real time unless fall +#define MOVETYPE_FLY 5 +#define MOVETYPE_TOSS 6 // gravity +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 +#define MOVETYPE_FLYMISSILE 9 // fly with extra size against monsters +#define MOVETYPE_BOUNCE 10 +#define MOVETYPE_BOUNCEMISSILE 11 // bounce with extra size + +// edict.solid values +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block + +// range values +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +// deadflag values +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +// takedamage values +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// items +#define IT_AXE 4096 +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_EXTRA_WEAPON 128 + +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 + +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 + +#define IT_KEY1 131072 +#define IT_KEY2 262144 + +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_HOOK 8388608 + +// point content values +#define CONTENT_EMPTY -1 +#define CONTENT_SOLID -2 +#define CONTENT_WATER -3 +#define CONTENT_SLIME -4 +#define CONTENT_LAVA -5 +#define CONTENT_SKY -6 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +// q3 states +#define STATE_NONE 0 +#define STATE_DISABLED 4 +#define STATE_INACTIVE 5 +#define STATE_ACTIVE 6 +#define STATE_INVISIBLE 7 + +#define VEC_ORIGIN '0 0 0' +#define VEC_HULL_MIN '-16 -16 -24' +#define VEC_HULL_MAX '16 16 32' + +#define VEC_HULL2_MIN '-32 -32 -24' +#define VEC_HULL2_MAX '32 32 64' + +// protocol bytes +#define SVC_TEMPENTITY 23 +#define SVC_KILLEDMONSTER 27 +#define SVC_FOUNDSECRET 28 +#define SVC_INTERMISSION 30 +#define SVC_FINALE 31 +#define SVC_CDTRACK 32 +#define SVC_SELLSCREEN 33 + +#define SVC_SMALLKICK 34 +#define SVC_BIGKICK 35 +#define SVC_UPDATEPING 36 +#define SVC_UPDATETIME 37 +#define SVC_MUZZLEFLASH 39 +#define SVC_UPDATEUSERINFO 40 +#define SVC_PLAYERINFO 42 +#define SVC_PACKETENTITIES 47 +#define SVC_DELTAPACKETENTITIES 48 +#define SVC_SETINFO 51 +#define SVC_UPDATEPL 53 + +#define TE_SPIKE 0 +#define TE_SUPERSPIKE 1 +#define TE_GUNSHOT 2 +#define TE_EXPLOSION 3 +#define TE_TAREXPLOSION 4 +#define TE_LIGHTNING1 5 +#define TE_LIGHTNING2 6 +#define TE_WIZSPIKE 7 +#define TE_KNIGHTSPIKE 8 +#define TE_LIGHTNING3 9 +#define TE_LAVASPLASH 10 +#define TE_TELEPORT 11 +#define TE_BLOOD 12 +#define TE_LIGHTNINGBLOOD 13 + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel + +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_NO_PHS_ADD 8 + +// We can overlap these with regular channels since they play on the world ent. +#define NUM_GREN_TIMERS 5 // We overlap channels when >3 active. +#define CHAN_GREN_START 9 +#define CHAN_GREN_END 13 +// #define CHAN_GREN_END (CHAN_GREN_START + NUM_GREN_TIMERS - 1) + +#define ATTN_NONE 0 +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 + +// update types + +#define UPDATE_GENERAL 0 +#define UPDATE_STATIC 1 +#define UPDATE_BINARY 2 +#define UPDATE_TEMP 3 + +// entity effects +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 +#define EF_FLAG1 16 +#define EF_FLAG2 32 +// GLQuakeWorld Stuff +#define EF_BLUE 64 // Blue Globe effect for Quad +#define EF_RED 128 // Red Globe effect for Pentagram + +// messages +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_MULTICAST 4 + +// message levels +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +#define PRINT_CHAT 3 // also goes to chat console + +// multicast sets +#define MULTICAST_ALL 0 // every client +#define MULTICAST_PHS 1 // within hearing +#define MULTICAST_PVS 2 // within sight +#define MULTICAST_ALL_R 3 // every client, reliable +#define MULTICAST_PHS_R 4 // within hearing, reliable +#define MULTICAST_PVS_R 5 // within sight, reliable + +// attack_state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// stat_flags +#define PLAYER_READY 1 +#define ENEMY_TEAM_READY 2 +#define LAST_NOT_READY 4 +#define CEASEFIRE 8 + +//=========================================================================== +// TEAMFORTRESS Defs +//=========================================================================== + +// TeamFortress State Flags +enumflags { + TFSTATE_RELOADING, // Whether the player is reloading + TFSTATE_GREN1_PRIMED, // Whether the player has a primed gren 1 + TFSTATE_GREN2_PRIMED, // Whether the player has a primed gren 2 + TFSTATE_GRENTHROWING, // is throwing a grenade + TFSTATE_AIMING, // is using the laser sight or spinning + TFSTATE_CANT_MOVE, + TFSTATE_CONC, + TFSTATE_CONC_CAP, // Speed-cap next jump + TFSTATE_NO_WEAPON, // Weapon is disabled and not visible (e.g. detpack) + // (Note: We don't use NO_WEAPON for reloading + // as it could result in stacked no-weapon states.) + TFSTATE_FLASHED, + TFSTATE_AC_SPINUP, // These cover the 3 assault cannon states. + TFSTATE_AC_SPINNING, + TFSTATE_AC_SPINDOWN, + TFSTATE_LOCK, // assault cannon locked + TFSTATE_INFECTED, // set when player is infected by the bioweapon + TFSTATE_BURNING, // Is on fire + TFSTATE_FEIGNED, // Is feigned + TFSTATE_AIMING, // is using the laser sight or spinning cannon + TFSTATE_RESPAWN_READY, // is waiting for respawn, and has pressed fire, + // as sentry gun,indicate it needs to die + TFSTATE_HALLUCINATING, // set when player is hallucinating + + TFSTATE_FLAMES_MAX, // Peak burnination. + TFSTATE_TRANQUILISED, // set when player is tranquilised + TFSTATE_RANDOMPC, + // QC/Compiler limitation: Bits past-24 unsafe with bitops +}; + +enumflags { + PSTATE_INVINCIBLE, // Player has permanent Invincibility (Usually by GoalItem) + PSTATE_INVISIBLE, // Player has permanent Invisibility (Usually by GoalItem) + PSTATE_QUAD, // Player has permanent Quad Damage (Usually by GoalItem) + PSTATE_RADSUIT, // Player has permanent Radsuit (Usually by GoalItem) +}; + +#define TFSTATE_GREN_MASK_PRIMED (TFSTATE_GREN1_PRIMED|TFSTATE_GREN2_PRIMED) +#define TFSTATE_GREN_MASK_ALL (TFSTATE_GREN_MASK_PRIMED|TFSTATE_GRENTHROWING) + +#define TFSTATE_AC_MASK (TFSTATE_AC_SPINUP|TFSTATE_AC_SPINNING|TFSTATE_AC_SPINDOWN) +#define TFSTATE_AC_FIRING (TFSTATE_AC_SPINUP|TFSTATE_AC_SPINNING) + +enum { + PRNG_WEAP, + PRNG_HWGUY, + PRNG_CONC, + PRNG_NUM_STATES, +}; + +// Defines used by TF_T_Damage (see combat.qc) +#define TF_TD_IGNOREARMOR 1 // Bypasses the armor of the target +#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) +#define TF_TD_NOTSELF 4 // Doesn't damage self + +#define TF_TD_OTHER 0 // Ignore armorclass +#define TF_TD_SHOT 1 // Bullet damage +#define TF_TD_NAIL 2 // Nail damage +#define TF_TD_EXPLOSION 4 // Explosion damage +#define TF_TD_ELECTRICITY 8 // Electric damage +#define TF_TD_FIRE 16 // Fire damage +#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc +#define TF_TD_NOMOMENTUM 512 // Special damage, don't change momentum + +// Classic Fortress stuff +#define CF_MAPVOTE_FORCESHOW 10 // Seconds to force the mapvote menu to be open +#define CF_MAPVOTE_FINISH 5 // Seconds before timelimit to close all voting +#define CF_MAPVOTE_DURATION 180 // Seconds to show map vote menu +#define CF_MAPVOTE_DURATION_DECIDER 90 // Seconds to show map decider menu + +// team play vals +#define TP_HALFDIRECT 128 +#define TP_NODIRECT 256 +#define TP_HALFAOE 512 +#define TP_NOAOE 1024 +#define TP_HALFMIRRORDIRECT 2048 +#define TP_FULLMIRRORDIRECT 4096 +#define TP_HALFMIRRORAOE 8192 +#define TP_FULLMIRRORAOE 16384 + +/*======================================================*/ +/* Toggleable Game Settings */ +/*======================================================*/ + +// Opaque token that encapsulates slots for correctness. +struct Slot { int id; }; + +// Some of the toggleflags aren't used anymore, but the bits are still +// there to provide compatability with old maps +#define TFLAG_CLASS_PERSIST 1 // Persistent Classes Bit +#define TFLAG_CHEATCHECK 2 // Cheatchecking Bit +#define TFLAG_RESPAWNDELAY 4 // RespawnDelay bit +#define TFLAG_UN 8 // NOT USED ANYMORE +#define TFLAG_UN2 16 // NOT USED ANYMORE +#define TFLAG_UN3 32 // NOT USED ANYMORE +#define TFLAG_AUTOTEAM 64 // sets whether players are automatically placed in teams +#define TFLAG_TEAMFRAGS 128 // Individual Frags, or Frags = TeamScore +#define TFLAG_FIRSTENTRY 256 // Used to determine the first time toggleflags is set + // in a map. Cannot be toggled by players. +// unused 512 +#define TFLAG_GRAPPLE 1024 // Grapple on/off +#define TFLAG_FULLTEAMSCORE 2048 +#define TFLAG_FLAGEMU 4096 +#define TFLAG_WARSTANDARD 8192 + +#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn + +#define TEAMPLAY_NORMAL 1 +#define TEAMPLAY_HALFDIRECT 2 +#define TEAMPLAY_NODIRECT 4 +#define TEAMPLAY_HALFEXPLOSIVE 8 +#define TEAMPLAY_NOEXPLOSIVE 16 +#define TEAMPLAY_LESSPLAYERSHELP 32 +#define TEAMPLAY_LESSSCOREHELP 64 + +// FortressMap stuff +#define TEAM1_CIVILIANS 1 +#define TEAM2_CIVILIANS 2 +#define TEAM3_CIVILIANS 4 +#define TEAM4_CIVILIANS 8 + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 + +// Insert new class definitions here + +// PC_RANDOM _MUST_ be the third last class +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +// admin menu page consts +#define ADMIN_MENU_TYPE_MAIN 0 +#define ADMIN_MENU_TYPE_KICK 1 +#define ADMIN_MENU_TYPE_BAN 2 +#define ADMIN_MENU_TYPE_CAPTAINTEAMONE 3 +#define ADMIN_MENU_TYPE_CAPTAINTEAMTWO 4 +#define ADMIN_MENU_TYPE_CAPTAINSELECT 5 +#define ADMIN_MENU_TYPE_QUADMODE 6 +#define ADMIN_MENU_TYPE_QUAD_ROUNDNUM 7 +#define ADMIN_MENU_TYPE_QUAD_ROUNDTL 8 +#define ADMIN_MENU_TYPE_CLASSES 9 +#define ADMIN_MENU_TYPE_FORCESPEC 10 + + +/*======================================================*/ +/* Impulse Defines */ +/*======================================================*/ +#define TF_IMPULSE1 1 // Ambiguous impulses that can be either +#define TF_IMPULSE2 2 // slots or classical weapons. Prefer +#define TF_IMPULSE3 3 // TF_SLOT1 .. TF_SLOT4 +#define TF_IMPULSE4 4 +#define TF_NUM_SLOTS 4 + +#define TF_CLASSMENU 5 // Brings up class menu +// unused 6 +// unused 7 +// unused 8 +// unused 9 +// unused 10 +#define TF_WEAPNEXT 11 // Selects the next weapon slot +#define TF_WEAPPREV 12 // Selects the previous weapon slot +#define TF_WEAPLAST 13 // Selects the last used weapon slot +#define TF_GRENADE_1 14 // Prime grenade type 1 +#define TF_GRENADE_2 15 // Prime grenade type 2 +#define TF_GRENADE_T 16 // Throw primed grenade +#define TF_GRENADE_PT_1 17 // Prime and throw grenade type 1 (two clicks) +#define TF_GRENADE_PT_2 18 // Prime and throw grenade type 2 (two clicks) +// unused +#define TF_RELOAD_SLOT1 25 // Reload weapon slot 1 +#define TF_RELOAD_SLOT2 26 // Reload weapon slot 2 +#define TF_RELOAD_SLOT3 27 // Reload weapon slot 3 +#define TF_RELOAD 28 // Reload current weapon +#define TF_RELOAD_NEXT 29 // Reload next weapon with a non-full clip +#define TF_SPECIAL_SKILL 30 // Class special +#define TF_DROPFLAG 31 // Drop flag +#define TF_DROPKEY 32 // Drop key +#define TF_DISCARD 33 // Discard useless ammo +#define TF_DROP_AMMO 34 // Drop an ammo box on the ground +#define TF_MEDIC_HELPME 35 // Alert players around you that you are in need of medical attention +#define TF_INVENTORY 36 // Displays current inventory +#define FLAG_INFO 37 // Displays current flag location +#define TF_ID 38 // Identify player/object in front of player +#define TF_ID_TEAM 39 // Identify team player/object in front of player +#define TF_ID_ENEMY 40 // Identify enemy player/object in front of player +#define TF_DASH 41 // Scout: Initialize a forward bunnyhop +#define TF_SCAN 42 // Scout: Toggle Scanner on/off +#define TF_SCAN_ENEMY 43 // Scout: Toggle scanning of enemies +#define TF_SCAN_FRIENDLY 44 // Scout: Toggle scanning of friendlies +#define TF_SCAN_SOUND 45 // Scout: Toggle scanner sound +// 46 +// 47 +// 48 +#define TF_DEMOMAN_DETPACK 49 // Demoman: Bring up detpack menu +#define TF_DETPACK 50 // Demoman: Detpack Pre-Impulse +#define TF_DETPACK_STOP 51 // Demoman: Impulse to stop setting detpack +#define TF_DETPACK_5 52 // Demoman: Detpack set to 5 seconds +#define TF_DETPACK_20 53 // Demoman: Detpack set to 20 seconds +#define TF_DETPACK_50 54 // Demoman: Detpack set to 50 seconds +#define TF_PB_DETONATE 55 // Demoman: Detonate Pipebombs +#define TF_MEDIC_AURA_TOGGLE 56 // Medic: Toggle Healing Aura on/off +#define TF_HVYWEAP_LOCK_TOGGLE 57 // Medic: Toggle Healing Aura on/off +#define TF_LOCKON 58 // HWGuy: Turn Assault Cannon fire on +#define TF_LOCKOFF 59 // HWGuy: Turn Assault Cannon fire off +#define TF_AIRBLAST 60 // Pyro: Air blast +#define TF_SPY_DIE 61 // Spy: Feign death +#define TF_SPY_DIE_ON 62 // Spy: Feign death next damage +#define TF_SPY_DIE_OFF 63 // Spy: Unfeign +#define TF_SPY_SILENT_DIE 64 // Spy: Silent feign death +#define TF_SPY_SPY 65 // Spy: Bring up disguise menu +#define TF_DISGUISE_ENEMY 66 // Spy: Disguise as enemy team +#define TF_DISGUISE_LAST 67 // Spy: Use last disguise +#define TF_DISGUISE_RESET 68 // Spy: Reset disguise +#define TF_DISGUISE_SCOUT 69 // Spy: Disguise as Scout +#define TF_DISGUISE_SNIPER 70 // Spy: Disguise as Sniper +#define TF_DISGUISE_SOLDIER 71 // Spy: Disguise as Soldier +#define TF_DISGUISE_DEMOMAN 72 // Spy: Disguise as Demoman +#define TF_DISGUISE_MEDIC 73 // Spy: Disguise as Medic +#define TF_DISGUISE_HWGUY 74 // Spy: Disguise as HWGuy +#define TF_DISGUISE_PYRO 75 // Spy: Disguise as Pyro +#define TF_DISGUISE_ENGINEER 76 // Spy: Disguise as Engineer +#define TF_DISGUISE_BLUE 77 // Spy: Disguise as blue team +#define TF_DISGUISE_RED 78 // Spy: Disguise as red team +#define TF_DISGUISE_YELLOW 79 // Spy: Disguise as yellow team +#define TF_DISGUISE_GREEN 80 // Spy: Disguise as green team +#define TF_ENGINEER_BUILD 81 // Engineer: Bring up build menu for Engineer +#define TF_ENGINEER_DETDISP 82 // Engineer: Detonate dispenser for Engineer +#define TF_ENGINEER_DETSENTRY 83 // Engineer: Detonate sentry gun for Engineer +#define TF_DISCARD_DROP_AMMO 84 +#define TF_PRACSPAWN_PLACE 85 +#define TF_PRACSPAWN_REMOVE 86 +#define TF_DISGUISE_LAST_SPAWNED 87 // Spy: Disguise as last enemy to spawn +#define TF_SPECIAL_SKILL_2 88 // Class special 2 +#define TF_ENGINEER_TOGGLESENTRY 89 // Engineer: Build or detonate sentry +#define TF_ENGINEER_TOGGLEDISPENSER 90 // Engineer: Build or detonate dispenser +// unused 91 +// unused 92 +// unused 93 +// unused 94 +// unused 95 +// unused 96 +// unused 97 +// unused 98 +// unused 99 +// unused 100 +#define TF_CHANGETEAM 101 // Bring up team selection menu +#define TF_TEAM_1 102 // Join team 1 +#define TF_TEAM_2 103 // Join team 2 +#define TF_TEAM_3 104 // Join team 3 +#define TF_TEAM_4 105 // Join team 4 +#define TF_DISPLAYLOCATION 106 // Displays current location and angles (for developers) +#define TF_SHOWTF 107 // Displays server settings and mod version +#define TF_SHOWLEGALCLASSES 108 // Show what classes are allowed by current map +#define TF_SHOW_IDS 109 // Show ids of connected players +// unused 110 +#define TF_CHANGECLASS 111 // Bring up class selection menu +#define TF_CHANGEPC_SCOUT 112 // Change class to Scout +#define TF_CHANGEPC_SNIPER 113 // Change class to Sniper +#define TF_CHANGEPC_SOLDIER 114 // Change class to Soldier +#define TF_CHANGEPC_DEMOMAN 115 // Change class to Demoman +#define TF_CHANGEPC_MEDIC 116 // Change class to Medic +#define TF_CHANGEPC_HVYWEAP 117 // Change class to HWGuy +#define TF_CHANGEPC_PYRO 118 // Change class to Pyro +#define TF_CHANGEPC_SPY 119 // Change class to Spy +#define TF_CHANGEPC_ENGINEER 120 // Change class to Engineer +#define TF_CHANGEPC_RANDOM 121 // Change class to RandomPC +#define TF_HELP_MAP 122 // Displays current map objectives +#define TF_CLASSHELP 123 // Class help alias +#define TF_TEAM_CLASSES 124 // Display team classes +#define TF_TEAM_LIST 125 // Display the players in each team +#define TF_TEAM_SCORES 126 // Display team scores +#define TF_STATUS_QUERY 127 // Displays current team balance and equilization ratios +#define TF_NEXTTIP 128 // Shows the next general/class tip +// unused 129 +// unused 130 +#define TF_TOGGLEVOTE 131 // Toggle vote menu on/off +#define TF_VOTENEXT 132 // Vote to start voting for next map +#define TF_VOTETRICK 133 // Vote to start voting for a trick map +#define TF_VOTERACE 134 // Vote to start voting for a race map +#define TF_FORCENEXT 135 // Vote to force a change to voted map +#define TF_PLAYER_READY 136 +#define TF_PLAYER_NOT_READY 137 +#define TF_ADMIN_FORCESTARTMATCH 138 +#define TF_ADMIN_READYSTATUS 139 +// unused 140 +#define TF_NAILGREN_INFO 141 +// unused 142 +// unused 143 +// unused 144 +// unused 145 +// unused 146 +// unused 147 +// unused 148 +// unused 149 +// unused 150 +// unused 151 +// unused 152 +// unused 153 +// unused 154 +// unused 155 +// unused 156 +// unused 157 +// unused 158 +// unused 159 +// unused 160 +// unused 161 +// unused 162 +// unused 163 +// unused 164 +// unused 165 +// unused 166 +// unused 167 +// unused 168 +// unused 169 +// unused 170 +// unused 171 +// unused 172 +// unused 173 +// unused 174 +// unused 175 +// unused 176 +// unused 177 +// unused 178 +// unused 179 +// unused 180 +// unused 181 +// unused 182 +// unused 183 +// unused 184 +// unused 185 +// unused 186 +// unused 187 +// unused 188 +// unused 189 +#define TF_ADMIN_CYCLEDEAL 190 +#define TF_ADMIN_KICK 191 +#define TF_ADMIN_BAN 192 +#define TF_ADMIN_COUNTPLAYERS 193 +#define TF_ADMIN_CEASEFIRE 194 +// unused 195 +#define TF_ADMIN_NEXT 196 +// unused 197 +// unused 198 +#define TF_ADMIN_LISTIPS 199 +#define TF_SLOT1 200 // Changes weapon to slot 1 (primary weapon) +#define TF_SLOT2 201 // Changes weapon to slot 2 (secondary weapon) +#define TF_SLOT3 202 // Changes weapon to slot 3 (tertiary weapon) +#define TF_SLOT4 203 // Changes weapon to slot 4 (melee weapon) +// unused 200 +// unused 201 +// unused 202 +// unused 203 +// unused 204 +// unused 205 +// unused 206 +// unused 207 +#define TF_ADMIN_CLANMODE 208 +#define TF_ADMIN_QUADMODE 209 +#define TF_ADMIN_DUELMODE 210 +// unused 211 +// unused 212 +// unused 213 +// unused 214 +// unused 215 +// unused 216 +// unused 217 +// unused 218 +// unused 219 +// unused 220 +// unused 221 +// unused 222 +// unused 223 +// unused 224 +// unused 225 +// unused 226 +// unused 227 +// unused 228 +// unused 229 +// unused 230 +// unused 231 +// unused 232 +// unused 233 +// unused 234 +// unused 235 +// unused 236 +// unused 237 +// unused 238 +// unused 239 +#define TF_ADMIN_ADMINMENU 240 +// unused 241 +// unused 242 +// unused 243 +// unused 244 +// unused 245 +// unused 246 +// unused 247 +// unused 248 +// unused 249 +#define TF_DEBUG_CSQC 250 +// unused 251 +// unused 252 +// unused 253 +// unused 254 +// unused 255 + +/*======================================================*/ +/* Colors */ +/*======================================================*/ +#define WHITE 1 +#define BROWN 2 +#define BLUE 3 +#define GREEN 4 +#define RED 5 +#define TAN 6 +#define PINK 7 +#define ORANGE 8 +#define PURPLE 9 +#define DARKPURPLE 10 +#define GREY 11 +#define DARKGREEN 12 +#define YELLOW 13 +#define DARKBLUE 14 + +#define NOTEAMCOLOR "0xffffff" +#define BLUETEAMCOLOR "0x00aaff" +#define REDTEAMCOLOR "0xff3333" +#define YELLOWTEAMCOLOR "0xffdd00" +#define GREENTEAMCOLOR "0x00ff44" + +/*======================================================*/ +/* Defines for the ENGINEER's Building ability */ +/*======================================================*/ +// Ammo costs +#define AMMO_COST_SHELLS 3 // Metal needed to make 1 shell +#define AMMO_COST_NAILS 2 +#define AMMO_COST_ROCKETS 5 +#define AMMO_COST_CELLS 5 + +// Building types +#define BUILD_DISPENSER 1 +#define BUILD_SENTRYGUN 2 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 +#define BUILD_COST_SENTRYGUN 130 + +// Building times +#define BUILD_TIME_DISPENSER 2 // 2 seconds to build +#define BUILD_TIME_SENTRYGUN 5 // 5 seconds to build + +// Building health levels +#define BUILD_HEALTH_DISPENSER 150 +#define BUILD_HEALTH_SENTRYGUN 150 + +#define SPAWNFLAG_BLOCK_EMP 4096 + +// Dispenser's maximum carrying capability +#define BUILD_DISPENSER_MAX_SHELLS 400 +#define BUILD_DISPENSER_MAX_NAILS 600 +#define BUILD_DISPENSER_MAX_ROCKETS 300 +#define BUILD_DISPENSER_MAX_CELLS 400 +#define BUILD_DISPENSER_MAX_ARMOR 500 + +#define BUILD_SENTRYGUN_MAX_DISTANCE 128 +#define BUILD_SENTRYGUN_MAX_DISTANCE_ENGMOVE 1024 + +/*======================================================*/ +/* Ammo quantities for dropping */ +/*======================================================*/ +#define DROP_SHELLS 20 +#define DROP_NAILS 20 +#define DROP_ROCKETS 10 +#define DROP_CELLS 10 +#define DROP_ARMOR 40 + +/*======================================================*/ +/* Team Defines */ +/*======================================================*/ +#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. + // A new global to hold new team colors is needed, and more flags + // in the spawnpoint spawnflags may need to be used. + // Basically, don't change this unless you know what you're doing :) + +/*======================================================*/ +/* New Weapon Defines */ +/*======================================================*/ + +#define WEAP_NONE 0 +enum { + WEAP_HOOK = 1, + WEAP_KNIFE, + WEAP_MEDIKIT, + WEAP_SPANNER, + WEAP_AXE, + WEAP_SNIPER_RIFLE, + WEAP_AUTO_RIFLE, + WEAP_SHOTGUN, + WEAP_SUPER_SHOTGUN, + WEAP_NAILGUN, + WEAP_SUPER_NAILGUN, + WEAP_GRENADE_LAUNCHER, + WEAP_PIPE_LAUNCHER, + WEAP_FLAMETHROWER, + WEAP_ROCKET_LAUNCHER, + WEAP_INCENDIARY, + WEAP_ASSAULT_CANNON, + WEAP_LIGHTNING, + WEAP_DETPACK, + WEAP_TRANQ, + WEAP_RAILGUN, + WEAP_IMPELLER, + WEAP_LAST = WEAP_IMPELLER, +}; + +// still room for 12 more weapons +// but we can remove some by giving the weapons +// a weapon mode (like the rifle) + +/*======================================================*/ +/* New Weapon Related Defines */ +/*======================================================*/ +// shots per reload +#define RE_SHOTGUN 8 +#define RE_SUPER_SHOTGUN 16 // 8 shots +#define RE_GRENADE_LAUNCHER 6 +#define RE_ROCKET_LAUNCHER 4 + +// reload times +#define RE_SHOTGUN_TIME 2 +#define RE_SUPER_SHOTGUN_TIME 3 +#define RE_GRENADE_LAUNCHER_TIME 4 +#define RE_ROCKET_LAUNCHER_TIME 5 + +// Maximum velocity you can move and fire the Sniper Rifle +#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 + +// Medikit +#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit +#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense + +// Spanner +#define WEAP_SPANNER_REPAIR 10 + +// Detpack +#define WEAP_DETPACK_DISARMTIME 3 // Time it takes to disarm a Detpack +#define WEAP_DETPACK_SETTIME 4 // Time it takes to set a Detpack +#define WEAP_DETPACK_SIZE 1500 +#define WEAP_DETPACK_BITS_NO 12 // Bits that detpack explodes into + +// Tranquiliser Gun +#define TRANQ_TIME 15 + +// Defines for NailGren Types +#define NGR_TYPE_NAIL 0 +#define NGR_TYPE_LASER 1 +#define NGR_TYPE_BURST 2 + +// NailGren Default Settings +#define NGR_LASER_DEFAULT_ROTATIONCOUNT 2 +#define NGR_LASER_DEFAULT_ROTATIONTIME 1 +#define NGR_LASER_DEFAULT_DAMAGE 20 +#define NGR_LASER_DEFAULT_RANGE 150 + +#define NGR_BURST_DEFAULT_COUNT 2 +#define NGR_BURST_DEFAULT_INTERVAL 0.7 +#define NGR_BURST_DEFAULT_RANGE 0.3 + +// Defines for Medic type +#define MEDIC_TYPE_DEFAULT 0 +#define MEDIC_TYPE_BLAST 1 + +// Defines for BlastGren Settings +#define BLASTGREN_DEFAULT_VELOCITY_MULTIPLIER 1 + +// Defines for WeaponMode +#define GL_NORMAL 0 +#define GL_PIPEBOMB 1 + +// Defines for Concussion Grenade +#define GR_CONCUSS_TIME 5 +#define GR_CONCUSS_DEC 20 + +// Defines for the Gas Grenade +#define GR_HALLU_TIME 0.5 +#define GR_HALLU_DEC 2.5 + +/*======================================================*/ +/* New Items */ +/*======================================================*/ +#define NIT_SCANNER 1 + +#define NIT_SILVER_DOOR_OPENED IT_KEY1 /* 131072 */ +#define NIT_GOLD_DOOR_OPENED IT_KEY2 /* 262144 */ + +/*======================================================*/ +/* New Item Flags */ +/*======================================================*/ +#define NIT_SCANNER_ENEMY 1 // Detect enemies +#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) +//#define NIT_SCANNER_MOVEMENT 4 // Motion detection. Only report moving entities. +#define NIT_SCANNER_SOUND 4 // Scanner makes a sound + +/*======================================================*/ +/* New Item Related Defines */ +/*======================================================*/ +#define NIT_SCANNER_POWER 100 // The amount of power spent on a scan with the scanner + // is multiplied by this to get the scanrange. +#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan +#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected + // by scanners that only detect movement + +/*======================================================*/ +/* Variables used for New Weapons and Reloading */ +/*======================================================*/ + +// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. +#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% +#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% +#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% +#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% +#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% + +/*======================================================================*/ +/* TEAMFORTRESS CLASS DETAILS */ +/*======================================================================*/ +// Class Details for SCOUT +#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. +#define PC_SCOUT_MAXHEALTH 100 // Maximum Health Level +#define PC_SCOUT_MAXSPEED 450 // Maximum movement speed +#define PC_SCOUT_MAXARMOR 25 // Maximum Armor Level, of any armor class +#define PC_SCOUT_INITARMOR 0 // Armor level when respawned +#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption +#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned +#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class +#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned +#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry +#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry +#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry +#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry +#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned +#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned +#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned +#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned +#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned +#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned +#define PC_SCOUT_GRENADE_MAX_1 3 +#define PC_SCOUT_GRENADE_MAX_2 4 +#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has + +#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range +#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector + +// Class Details for SNIPER +#define PC_SNIPER_SKIN 5 +#define PC_SNIPER_MAXHEALTH 100 +#define PC_SNIPER_MAXSPEED 300 +#define PC_SNIPER_MAXARMOR 40 +#define PC_SNIPER_INITARMOR 0 +#define PC_SNIPER_MAXARMORTYPE 0.3 +#define PC_SNIPER_INITARMORTYPE 0.3 +#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL +#define PC_SNIPER_INITARMORCLASS 0 +#define PC_SNIPER_MAXAMMO_SHOT 75 +#define PC_SNIPER_MAXAMMO_NAIL 100 +#define PC_SNIPER_MAXAMMO_CELL 50 +#define PC_SNIPER_MAXAMMO_ROCKET 25 +#define PC_SNIPER_INITAMMO_SHOT 60 +#define PC_SNIPER_INITAMMO_NAIL 50 +#define PC_SNIPER_INITAMMO_CELL 0 +#define PC_SNIPER_INITAMMO_ROCKET 0 +#define PC_SNIPER_GRENADE_INIT_1 2 +#define PC_SNIPER_GRENADE_INIT_2 3 +#define PC_SNIPER_GRENADE_MAX_1 4 +#define PC_SNIPER_GRENADE_MAX_2 4 +#define PC_SNIPER_TF_ITEMS 0 + +#define PC_SNIPER_MAXDAM 401 + +// Class Details for SOLDIER +#define PC_SOLDIER_SKIN 6 +#define PC_SOLDIER_MAXHEALTH 100 +#define PC_SOLDIER_MAXSPEED 240 +#define PC_SOLDIER_MAXARMOR 200 +#define PC_SOLDIER_INITARMOR 100 +#define PC_SOLDIER_MAXARMORTYPE 0.8 +#define PC_SOLDIER_INITARMORTYPE 0.8 +#define PC_SOLDIER_ARMORCLASSES 31 // ALL +#define PC_SOLDIER_INITARMORCLASS 0 +#define PC_SOLDIER_MAXAMMO_SHOT 100 +#define PC_SOLDIER_MAXAMMO_NAIL 100 +#define PC_SOLDIER_MAXAMMO_CELL 50 +#define PC_SOLDIER_MAXAMMO_ROCKET 50 +#define PC_SOLDIER_INITAMMO_SHOT 50 +#define PC_SOLDIER_INITAMMO_NAIL 0 +#define PC_SOLDIER_INITAMMO_CELL 0 +#define PC_SOLDIER_INITAMMO_ROCKET 10 +#define PC_SOLDIER_GRENADE_INIT_1 4 +#define PC_SOLDIER_GRENADE_INIT_2 1 +#define PC_SOLDIER_GRENADE_MAX_1 4 +#define PC_SOLDIER_GRENADE_MAX_2 1 +#define PC_SOLDIER_ROCKET_SPEED 900 +#define PC_SOLDIER_TF_ITEMS 0 + +// Class Details for DEMOLITION MAN +#define PC_DEMOMAN_SKIN 1 +#define PC_DEMOMAN_MAXHEALTH 100 +#define PC_DEMOMAN_MAXSPEED 280 +#define PC_DEMOMAN_MAXARMOR 110 +#define PC_DEMOMAN_INITARMOR 40 +#define PC_DEMOMAN_MAXARMORTYPE 0.6 +#define PC_DEMOMAN_INITARMORTYPE 0.6 +#define PC_DEMOMAN_ARMORCLASSES 31 // ALL +#define PC_DEMOMAN_INITARMORCLASS 0 //4 // AT_SAVEEXPLOSION +#define PC_DEMOMAN_MAXAMMO_SHOT 75 +#define PC_DEMOMAN_MAXAMMO_NAIL 50 +#define PC_DEMOMAN_MAXAMMO_CELL 50 +#define PC_DEMOMAN_MAXAMMO_ROCKET 50 +#define PC_DEMOMAN_MAXAMMO_DETPACK 1 +#define PC_DEMOMAN_INITAMMO_SHOT 30 +#define PC_DEMOMAN_INITAMMO_NAIL 0 +#define PC_DEMOMAN_INITAMMO_CELL 0 +#define PC_DEMOMAN_INITAMMO_ROCKET 20 +#define PC_DEMOMAN_INITAMMO_DETPACK 1 +#define PC_DEMOMAN_GRENADE_INIT_1 4 +#define PC_DEMOMAN_GRENADE_INIT_2 4 +#define PC_DEMOMAN_GRENADE_MAX_1 4 +#define PC_DEMOMAN_GRENADE_MAX_2 1 +#define PC_DEMOMAN_TF_ITEMS 0 + +// Class Details for COMBAT MEDIC +#define PC_MEDIC_SKIN 3 +#define PC_MEDIC_MAXHEALTH 100 +#define PC_MEDIC_MAXSPEED 320 +#define PC_BLASTMEDIC_MAXSPEED 280 +#define PC_MEDIC_MAXARMOR 90 +#define PC_MEDIC_INITARMOR 40 +#define PC_MEDIC_MAXARMORTYPE 0.6 +#define PC_MEDIC_INITARMORTYPE 0.3 +#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION +#define PC_MEDIC_INITARMORCLASS 0 +#define PC_MEDIC_MAXAMMO_SHOT 75 +#define PC_MEDIC_MAXAMMO_NAIL 150 +#define PC_MEDIC_MAXAMMO_CELL 50 +#define PC_MEDIC_MAXAMMO_ROCKET 25 +#define PC_MEDIC_MAXAMMO_MEDIKIT 100 +#define PC_MEDIC_INITAMMO_SHOT 50 +#define PC_MEDIC_INITAMMO_NAIL 50 +#define PC_MEDIC_INITAMMO_CELL 0 +#define PC_MEDIC_INITAMMO_ROCKET 0 +#define PC_MEDIC_INITAMMO_MEDIKIT 50 +#define PC_MEDIC_GRENADE_INIT_1 3 +#define PC_MEDIC_GRENADE_INIT_2 3 +#define PC_MEDIC_GRENADE_MAX_1 4 +#define PC_MEDIC_GRENADE_MAX_2 2 +#define PC_MEDIC_TF_ITEMS 0 +#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. +#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. +#define PC_MEDIC_AURA_HEAL_TIME 1 // Number of seconds between each aura heal. +#define PC_MEDIC_AURA_HEAL_AMOUNT 5 // Amount of health given per aura heal. +#define PC_MEDIC_AURA_RANGE 120 // The aura's range +#define PC_MEDIC_CELL_REGEN_TIME 1 // Number of seconds between each cell regen. +#define PC_MEDIC_CELL_REGEN_PERCENT 10 // Percentage of max cells regenerated each cell regen. +#define PC_MEDIC_CELL_REGEN_CD 5 // Seconds to cooldown cell regeneration after healing with medikit. +#define PC_MEDIC_SAVEME_GRACE 5 // Seconds after which /saveme gives grace period to medikit (no cell regeneration cooldown) +#define PC_MEDIC_AURA_ACTIVE 1 +#define PC_MEDIC_AURA_RECHARGING 2 +#define PC_MEDIC_AURA_OUTOFPOWER 3 + +// Class Details for HVYWEAP +#define PC_HVYWEAP_SKIN 2 +#define PC_HVYWEAP_MAXHEALTH 100 +#define PC_HVYWEAP_MAXSPEED 230 +#define PC_HVYWEAP_MAXARMOR 300 +#define PC_HVYWEAP_INITARMOR 150 +#define PC_HVYWEAP_MAXARMORTYPE 0.8 +#define PC_HVYWEAP_INITARMORTYPE 0.8 +#define PC_HVYWEAP_ARMORCLASSES 31 // ALL +#define PC_HVYWEAP_INITARMORCLASS 0 +#define PC_HVYWEAP_MAXAMMO_SHOT 200 +#define PC_HVYWEAP_MAXAMMO_NAIL 200 +#define PC_HVYWEAP_MAXAMMO_CELL 50 +#define PC_HVYWEAP_MAXAMMO_ROCKET 25 +#define PC_HVYWEAP_INITAMMO_SHOT 200 +#define PC_HVYWEAP_INITAMMO_NAIL 0 +#define PC_HVYWEAP_INITAMMO_CELL 30 +#define PC_HVYWEAP_INITAMMO_ROCKET 0 +#define PC_HVYWEAP_GRENADE_INIT_1 4 +#define PC_HVYWEAP_GRENADE_INIT_2 1 +#define PC_HVYWEAP_GRENADE_MAX_1 4 +#define PC_HVYWEAP_GRENADE_MAX_2 1 +#define PC_HVYWEAP_TF_ITEMS 0 +#define PC_HVYWEAP_CELL_FIRE 7 + +// Class Details for PYRO +#define PC_PYRO_SKIN 21 +#define PC_PYRO_MAXHEALTH 100 +#define PC_PYRO_MAXSPEED 300 +#define PC_PYRO_MAXARMOR 150 +#define PC_PYRO_INITARMOR 50 +#define PC_PYRO_MAXARMORTYPE 0.6 +#define PC_PYRO_INITARMORTYPE 0.6 +#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_PYRO_INITARMORCLASS 16 // AT_SAVEFIRE +#define PC_PYRO_MAXAMMO_SHOT 40 +#define PC_PYRO_MAXAMMO_NAIL 50 +#define PC_PYRO_MAXAMMO_CELL 200 +#define PC_PYRO_MAXAMMO_ROCKET 60 +#define PC_PYRO_INITAMMO_SHOT 20 +#define PC_PYRO_INITAMMO_NAIL 0 +#define PC_PYRO_INITAMMO_CELL 120 +#define PC_PYRO_INITAMMO_ROCKET 15 +#define PC_PYRO_GRENADE_INIT_1 1 +#define PC_PYRO_GRENADE_INIT_2 4 +#define PC_PYRO_GRENADE_MAX_1 4 +#define PC_PYRO_GRENADE_MAX_2 1 +#define PC_PYRO_TF_ITEMS 0 +#define PC_PYRO_AIRBLAST_RANGE 400 +#define PC_PYRO_AIRBLAST_CELLS 55 +#define PC_PYRO_AIRBLASTJUMP_CELLS 75 +#define PC_PYRO_LAVA_LIFETIME 3 +#define PC_PYRO_LAVA_RETICK 1.2 +#define PC_PYRO_FLAMETHROWER_DAM 15 +#define PC_PYRO_NAPALM_INIT_DAM 40 +#define PC_PYRO_NAPALM_MAX_EXPLOSIONS 4 +#define PC_PYRO_INIT_BURN_DAM 12 +#define PC_PYRO_BURN_MULTIPLIER 1 +#define PC_PYRO_BURN_DAMAGE_AMP 1.2 + +// Class Details for SPY +#define PC_SPY_SKIN 22 +#define PC_SPY_MAXHEALTH 100 +#define PC_SPY_MAXSPEED 300 +#define PC_SPY_MAXARMOR 90 +#define PC_SPY_INITARMOR 15 +#define PC_SPY_MAXARMORTYPE 0.6 +#define PC_SPY_INITARMORTYPE 0.6 +#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_SPY_INITARMORCLASS 0 +#define PC_SPY_MAXAMMO_SHOT 40 +#define PC_SPY_MAXAMMO_NAIL 100 +#define PC_SPY_MAXAMMO_CELL 30 +#define PC_SPY_MAXAMMO_ROCKET 15 +#define PC_SPY_INITAMMO_SHOT 40 +#define PC_SPY_INITAMMO_NAIL 50 +#define PC_SPY_INITAMMO_CELL 10 +#define PC_SPY_INITAMMO_ROCKET 0 +#define PC_SPY_GRENADE_INIT_1 2 +#define PC_SPY_GRENADE_INIT_2 2 +#define PC_SPY_GRENADE_MAX_1 4 +#define PC_SPY_GRENADE_MAX_2 1 +#define PC_SPY_TF_ITEMS 0 +#define PC_SPY_CELL_REGEN_TIME 5 +#define PC_SPY_CELL_REGEN_AMOUNT 1 +#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible +#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover +#define PC_SPY_TRANQSPEED 1500 + +// Class Details for ENGINEER +#define PC_ENGINEER_SKIN 22 // Not used anymore +#define PC_ENGINEER_MAXHEALTH 100 +#define PC_ENGINEER_MAXSPEED 300 +#define PC_ENGINEER_MAXARMOR 30 +#define PC_ENGINEER_INITARMOR 5 +#define PC_ENGINEER_MAXARMORTYPE 0.6 +#define PC_ENGINEER_INITARMORTYPE 0.3 +#define PC_ENGINEER_ARMORCLASSES 31 // ALL +#define PC_ENGINEER_INITARMORCLASS 0 +#define PC_ENGINEER_MAXAMMO_SHOT 50 +#define PC_ENGINEER_MAXAMMO_NAIL 50 +#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal +#define PC_ENGINEER_MAXAMMO_ROCKET 30 +#define PC_ENGINEER_INITAMMO_SHOT 20 +#define PC_ENGINEER_INITAMMO_NAIL 25 +#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal +#define PC_ENGINEER_INITAMMO_ROCKET 0 +#define PC_ENGINEER_GRENADE_INIT_1 2 +#define PC_ENGINEER_GRENADE_INIT_2 2 +#define PC_ENGINEER_GRENADE_MAX_1 4 +#define PC_ENGINEER_GRENADE_MAX_2 2 +#define PC_ENGINEER_TF_ITEMS 0 +#define PC_ENGINEER_RAILSPEED 1500 + +// Class Details for CIVILIAN +#define PC_CIVILIAN_SKIN 22 +#define PC_CIVILIAN_MAXHEALTH 50 +#define PC_CIVILIAN_MAXSPEED 240 +#define PC_CIVILIAN_MAXARMOR 0 +#define PC_CIVILIAN_INITARMOR 0 +#define PC_CIVILIAN_MAXARMORTYPE 0 +#define PC_CIVILIAN_INITARMORTYPE 0 +#define PC_CIVILIAN_ARMORCLASSES 0 +#define PC_CIVILIAN_INITARMORCLASS 0 +#define PC_CIVILIAN_MAXAMMO_SHOT 0 +#define PC_CIVILIAN_MAXAMMO_NAIL 0 +#define PC_CIVILIAN_MAXAMMO_CELL 0 +#define PC_CIVILIAN_MAXAMMO_ROCKET 0 +#define PC_CIVILIAN_INITAMMO_SHOT 0 +#define PC_CIVILIAN_INITAMMO_NAIL 0 +#define PC_CIVILIAN_INITAMMO_CELL 0 +#define PC_CIVILIAN_INITAMMO_ROCKET 0 +#define PC_CIVILIAN_GRENADE_INIT_1 0 +#define PC_CIVILIAN_GRENADE_INIT_2 0 +#define PC_CIVILIAN_GRENADE_MAX_1 0 +#define PC_CIVILIAN_GRENADE_MAX_2 0 +#define PC_CIVILIAN_TF_ITEMS 0 + + +/*======================================================================*/ +/* TEAMFORTRESS GOALS */ +/*======================================================================*/ +// For all these defines, see the tfortmap.txt that came with the zip +// for complete descriptions. +// Defines for Goal Activation types : goal_activation (in goals) +#define TFGA_TOUCH 1 // Activated when touched +#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion +#define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met +#define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 + +// Defines for Goal Group Result types : goal_group +// None! +// But I'm leaving this variable in there, since it's fairly likely +// that some will show up sometime. + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOFLOOR 2048 // If this bit is set, the GoalItem drops to the ground when it first spawns. +#define TFGI_ALLOWTHROW 4096 // Item can be thrown with 'dropitems' command +#define TFGI_SOLID 8192 // Item is solid + +// Defines for TeamSpawnpoints : goal_activation (in team spawns) +#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times +#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times + +// Defines for TeamSpawnpoints : goal_effects (in teamspawns) +#define TFSP_REMOVESELF 1 // Remove itself after being spawned on + +// Defines for Goal States +#define TFGS_NONE 0 +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// spawnflags +#define TFGI_NOGLOW 1 // stop the automatic glow applied in fortressone to goals with a .mdl set based on team +#define TFGI_CB_IGNORE 2 // Allow this goal to work even in clan battle/quadmode prematch + +// spawnflags - fo_misc_info +#define TFGI_NOGLOW 1 // we reuse some tfgi functions, so keep this spawnflag +#define TFGI_CB_IGNORE 2 // Allow this goal to work even in clan battle/quadmode prematch +#define FO_MISC_INFO_NO_Z_TEST 4 // Draw this "through" walls - fake it in ez, csqc do it locally + +/*======================================================================*/ +/* Flamethrower */ +/*======================================================================*/ + +#define FLAME_PLYRMAXTIME 45 // lifetime in 0.1 sec of a flame on a player +#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) +#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade +#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player +#define FLAME_NUMLIGHTS 1 // maximum number of light flame +#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' +#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode + +/*======================================================*/ +/* CTF Support defines */ +/*======================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + +/*======================================================*/ +/* Death Message defines */ +/*======================================================*/ + +#define DMSG_SHOTGUN 1 +#define DMSG_SSHOTGUN 2 +#define DMSG_NAILGUN 3 +#define DMSG_SNAILGUN 4 +#define DMSG_GRENADEL 5 +#define DMSG_ROCKETL 6 +#define DMSG_LIGHTNING 7 +#define DMSG_GREN_HAND 8 +#define DMSG_GREN_NAIL 9 +#define DMSG_GREN_MIRV 10 +#define DMSG_GREN_PIPE 11 +#define DMSG_DETPACK 12 +#define DMSG_BIOWEAPON 13 +#define DMSG_BIOWEAPON_ATT 14 +#define DMSG_FLAME 15 +#define DMSG_DETPACK_DIS 16 +#define DMSG_AXE 17 +#define DMSG_SNIPERRIFLE 18 +#define DMSG_AUTORIFLE 19 +#define DMSG_ASSAULTCANNON 20 +#define DMSG_HOOK 21 +#define DMSG_BACKSTAB 22 +#define DMSG_MEDIKIT 23 +#define DMSG_GREN_GAS 24 +#define DMSG_TRANQ 25 +#define DMSG_LASERBOLT 26 +#define DMSG_SENTRYGUN_BULLET 27 +#define DMSG_SNIPERLEGSHOT 28 +#define DMSG_SNIPERHEADSHOT 29 +#define DMSG_GREN_EMP 30 +#define DMSG_GREN_EMP_AMMO 31 +#define DMSG_SPANNER 32 +#define DMSG_INCENDIARY 33 +#define DMSG_SENTRYGUN_ROCKET 34 +#define DMSG_GREN_FLASH 35 +#define DMSG_TRIGGER 36 +#define DMSG_TEAMKILL 37 +#define DMSG_SENTRYGUN_EXPLODE 38 +#define DMSG_DISPENSER_EXPLODE 39 +#define DMSG_GREN_PIPE_AIR 40 +#define DMSG_GREN_CALTROP 41 +#define DMSG_GREN_SHOCK 42 +#define DMSG_GREN_BURST 43 +#define DMSG_KNIFE 44 +#define DMSG_IMPELLER 45 + +/*======================================================*/ +/* Menus */ +/*======================================================*/ + +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_DROP 4 +#define MENU_INTRO 5 +#define MENU_DEMOMAN 6 +#define MENU_DEMOMAN_CANCEL 7 +#define MENU_REPEATHELP 8 +#define MENU_SPY_SKIN_1 9 +#define MENU_SPY_SKIN_2 10 +#define MENU_SPY_SKIN_3 11 +#define MENU_SPY 12 +#define MENU_SPY_SKIN 13 +#define MENU_SPY_COLOR 14 +#define MENU_ENGINEER 15 +#define MENU_ENGINEER_FIX_DISPENSER 16 +#define MENU_ENGINEER_FIX_SENTRYGUN 17 +#define MENU_SCOUT 18 +#define MENU_DISPENSER 19 + +#define MENU_REFRESH_RATE 25 + + +/*======================================================*/ +/* Misc defines */ +/*======================================================*/ + +#define MAX_WORLD_FLAMES 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. +#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams + // - this is the most you should have on a net server +#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams + // - this is the most you should have on a net server +#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into +#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) + +#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. + // 60 is a mild setting, and won't make too much difference + // increasing it _decreases_ the amount of help the losing team gets + // Minimum setting is 1, which would really help the losing team + +#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds + +#define RESPAWN_DELAY_TIME 5 // this is the respawn delay, if the RESPAWN_DELAY option is + // turned on with temp1. QuakeWorld servers can use + // serverinfo respawn_delay to set their own time. + + +// CSQC defines +#define FO_HUD_GROW_UP 1 +#define FO_HUD_GROW_DOWN 2 +#define FO_HUD_GROW_LEFT 3 +#define FO_HUD_GROW_RIGHT 4 + +#define FO_HUD_CONFIG_PATH "fortressone_hud.cfg" +#define FO_CONFIG_PATH "fortressone_csqc.cfg" +#define FO_TOKEN_PATH "fortressone_token.cfg" + +#define HUD_ICON_SIZE_X 24 +#define HUD_ICON_SIZE_Y 24 +#define FO_HUD_CLIPSIZE_PANEL_X 125 +#define FO_HUD_CLIPSIZE_PANEL_Y 50 +#define FO_HUD_INSERT_BEFORE 0 +#define FO_HUD_INSERT_AFTER 1 +#define FO_HUD_INSERT_MIDDLE 2 + +#define FO_HUD_CLIPSIZE_NAME "Clip Size" +#define FO_HUD_FRAGSTREAK_NAME "Frag Streak" +#define FO_HUD_CAPS_NAME "Caps" +#define FO_HUD_IDENTIFY_NAME "ID" +#define FO_HUD_GREN1_NAME "Gren 1" +#define FO_HUD_GREN2_NAME "Gren 2" +#define FO_HUD_SPECIAL_NAME "Class Special" +#define FO_HUD_GRENTIMER_NAME "Grenade Timer" +#define FO_HUD_OPTIONS_NAME "Options" +#define FO_HUD_FLAGINFO_NAME "Flag Status" +#define FO_HUD_MOTD_NAME "MOTD" +#define FO_HUD_MENU_HINT_NAME "Menu Hints" +#define FO_HUD_GAME_MODE_NAME "Game Mode" +#define FO_HUD_NOTIFICATION_NAME "Notification" +#define FO_HUD_SHOWSCORES_NAME "Show Scores" +#define FO_HUD_MAP_MENU_NAME "Map Menu" +#define FO_HUD_HEALTH_NAME "Health" +#define FO_HUD_FACE_NAME "Face" +#define FO_HUD_AMMO_NAME "Ammo" +#define FO_HUD_AMMO_ICON_NAME "Ammo Icon" +#define FO_HUD_ARMOUR_NAME "Armour" +#define FO_HUD_ARMOUR_ICON_NAME "Armour Icon" +#define FO_HUD_INV_SHELLS_NAME "Shells" +#define FO_HUD_INV_NAILS_NAME "Nails" +#define FO_HUD_INV_ROCKETS_NAME "Rockets" +#define FO_HUD_INV_CELLS_NAME "Cells" +#define FO_HUD_TEAM_SCORE_NAME "Team Score" + + +#define ICON_CLIPSIZE "textures/wad/clipsize.png" +#define ICON_FRAGSTREAK "textures/wad/fragstreak.png" +#define ICON_CAPS "textures/wad/caps.png" +#define ICON_IDENTIFY "textures/wad/identify.png" +#define ICON_GREN_NONE "textures/wad/gren_none.png" +#define ICON_GREN_NORMAL "textures/wad/gren_normal.png" +#define ICON_GREN_NAIL "textures/wad/gren_nail.png" +#define ICON_GREN_CONCUSSION "textures/wad/gren_concussion.png" +#define ICON_GREN_FLASH "textures/wad/gren_flash.png" +#define ICON_GREN_EMP "textures/wad/gren_emp.png" +#define ICON_GREN_MIRV "textures/wad/gren_mirv.png" +#define ICON_GREN_NAPALM "textures/wad/gren_napalm.png" +#define ICON_GREN_GAS "textures/wad/gren_gas.png" +#define ICON_GREN_FLARE "textures/wad/gren_flare.png" +#define ICON_GREN_CALTROP "textures/wad/gren_caltrop.png" +#define ICON_GREN_BLAST "textures/wad/gren_blast.png" +#define ICON_GREN_SHOCK "textures/wad/gren_nail.png" +#define ICON_GREN_BURST "textures/wad/gren_nail.png" +#define ICON_SCOUT "textures/wad/scout.png" +#define ICON_SNIPER "textures/wad/sniper.png" +#define ICON_SOLDIER "textures/wad/soldier.png" +#define ICON_DEMOMAN "textures/wad/demoman.png" +#define ICON_MEDIC "textures/wad/medic.png" +#define ICON_HWGUY "textures/wad/hwguy.png" +#define ICON_PYRO "textures/wad/pyro.png" +#define ICON_SPY "textures/wad/spy.png" +#define ICON_ENGINEER_SG "textures/wad/engineer_sg.png" +#define ICON_ENGINEER_DISP "textures/wad/engineer_disp.png" +#define ICON_SECURITY_BUTTON_ANY "textures/wad/off_icon_glow_0.png" +#define ICON_SECURITY_BUTTON_BLUE "textures/wad/off_icon_glow_1.png" +#define ICON_SECURITY_BUTTON_RED "textures/wad/off_icon_glow_2.png" +#define ICON_SECURITY_BUTTON_YELLOW "textures/wad/off_icon_glow_3.png" +#define ICON_SECURITY_BUTTON_GREEN "textures/wad/off_icon_glow_4.png" +#define ICON_FLAG_ANY "textures/wad/flag_0.png" +#define ICON_FLAG_BLUE "textures/wad/flag_1.png" +#define ICON_FLAG_RED "textures/wad/flag_2.png" +#define ICON_FLAG_YELLOW "textures/wad/flag_3.png" +#define ICON_FLAG_GREEN "textures/wad/flag_4.png" + +// stats +// first 32 are reserved +#define STAT_TEAMNO 33 +#define STAT_FLAGS 34 +#define STAT_PAUSED 35 +#define STAT_NOFIRE 36 +#define STAT_TEAMNO_ATTACK 37 +#define STAT_ALL_TIME 38 +#define STAT_SPAWN_GEN 39 +#define STAT_ROUND_END 40 +#define STAT_HAS_SENTRY 41 + +// Dimensions +#define DMN_FLASH 1 // when flashed, we set dimension see to this +// all bits between 1 and 255 are reserved for flash +#define DMN_NOFLASH 256 // see all the things +#define DMN_TEAMBLUE 512 +#define DMN_TEAMRED 1024 +#define DMN_TEAMYELL 2048 +#define DMN_TEAMGREN 4096 +#define DMN_INVISIBLE 8192 // special dimension to hide stuff in +#define DMN_HIDDEN 16384 // put an entity here instead of noflash, then remove DMN_HIDDEN from a player's dimension_see to hide it from that player +#define DMN_GHOST 32768 // put ghosts in here + +// trigger_push +#define PUSH_ONCE 1 +#define PUSH_INCLUDETFITEM 2 +#define PUSH_EXCLUDEOTHER 4 // bad names, bad bits, bad coder - use in conjunction with includetfitem to exclude all but tfitem +#define PUSH_NONOISE 8 +#define PUSH_MEGAJUMPER 16 +#define PUSH_EXCLUDEGRENADE 32 + +// func_wall +#define WALL_SOLID_NOT 1 +#define WALL_HIDE_ON_USE 2 +#define WALL_SOLID_NOT_ON_USE 4 + +// teams +#define TEAM_BLUE 1 +#define TEAM_RED 2 +#define TEAM_YELL 3 +#define TEAM_GREN 4 + +// all time teams +#define ALL_TIME_COLOUR 0 +#define ALL_TIME_ATTACK 1 +#define ALL_TIME_DEFENCE 2 + +// web request index +#define BR_LOGIN_REQUEST 1 +#define FO_QUAD_STARTED_REQUEST 2 +#define FO_QUAD_FINISHED_REQUEST 3 +#define FO_LOGIN_REQUEST 4 + +#define SPEC_MAXSPEED 1000 + +struct TFAlias { + string alias; + float impulse; + string cmd; + float nocsqc_impulse; + string nocsqc_cmd; +}; + +TFAlias client_aliases[] = { + {"slot1", TF_SLOT1}, + {"slot2", TF_SLOT2}, + {"slot3", TF_SLOT3}, + {"slot4", TF_SLOT4}, + {"+slot1", 0, "+slot 1"}, + {"-slot1", 0, "-slot 1"}, + {"+slot2", 0, "+slot 2"}, + {"-slot2", 0, "-slot 2"}, + {"+slot3", 0, "+slot 3"}, + {"-slot3", 0, "-slot 3"}, + {"+slot4", 0, "+slot 4"}, + {"-slot4", 0, "-slot 4"}, + {"+quick1", 0, "slot1;+attack"}, + {"-quick1", 0, "-attack"}, + {"+quick2", 0, "slot2;+attack"}, + {"-quick2", 0, "-attack"}, + {"+quick3", 0, "slot3;+attack"}, + {"-quick3", 0, "-attack"}, + {"+quick4", 0, "slot4;+attack"}, + {"-quick4", 0, "-attack"}, + {"menu", 0, "fo_menu_special", 0, "cmd menu"}, + {"changeteam", 0, "fo_menu_team", TF_CHANGETEAM}, + {"teamblue", 0, "cmd changeteam 1", TF_TEAM_1}, + {"teamred", 0, "cmd changeteam 2", TF_TEAM_2}, + {"teamyellow", 0, "cmd changeteam 3", TF_TEAM_3}, + {"teamgreen", 0, "cmd changeteam 4", TF_TEAM_4}, + {"changeclass", 0, "fo_menu_class", TF_CHANGECLASS}, + {"scout", 0, "cmd changeclass 1", TF_CHANGEPC_SCOUT}, + {"sniper", 0, "cmd changeclass 2", TF_CHANGEPC_SNIPER}, + {"soldier", 0, "cmd changeclass 3", TF_CHANGEPC_SOLDIER}, + {"demoman", 0, "cmd changeclass 4", TF_CHANGEPC_DEMOMAN}, + {"medic", 0, "cmd changeclass 5", TF_CHANGEPC_MEDIC}, + {"hwguy", 0, "cmd changeclass 6", TF_CHANGEPC_HVYWEAP}, + {"pyro", 0, "cmd changeclass 7", TF_CHANGEPC_PYRO}, + {"spy", 0, "cmd changeclass 8", TF_CHANGEPC_SPY}, + {"engineer", 0, "cmd changeclass 9", TF_CHANGEPC_ENGINEER}, + {"randompc", 0, "cmd changeclass 10", TF_CHANGEPC_RANDOM}, + {"showclasses", TF_TEAM_CLASSES}, + {"legalclasses", TF_SHOWLEGALCLASSES}, + {"classhelp", TF_CLASSHELP}, + {"query", TF_STATUS_QUERY}, + {"inv", TF_INVENTORY}, + {"showtf", TF_SHOWTF}, + {"showscores", TF_TEAM_SCORES}, + {"flaginfo", FLAG_INFO}, + {"maphelp", TF_HELP_MAP}, + {"showids", TF_SHOW_IDS}, + {"id", TF_ID}, + {"idteam", TF_ID_TEAM}, + {"idenemy", TF_ID_ENEMY}, + {"nexttip", TF_NEXTTIP}, + {"votenext", TF_VOTENEXT}, + {"votetrick", TF_VOTETRICK}, + {"voterace", TF_VOTERACE}, + {"forcenext", TF_FORCENEXT}, + {"togglevote", TF_TOGGLEVOTE}, + {"dropkey", TF_DROPKEY}, + {"dropammo", 0, "fo_menu_dropammo", TF_DROP_AMMO}, + {"dropflag", TF_DROPFLAG}, + {"dropitems", TF_DROPFLAG}, + {"showloc", TF_DISPLAYLOCATION}, + {"special", TF_SPECIAL_SKILL}, + {"special2", TF_SPECIAL_SKILL_2}, + {"saveme", TF_MEDIC_HELPME}, + {"discard", TF_DISCARD}, + {"discard_drop_ammo", TF_DISCARD_DROP_AMMO}, + {"weapnext", TF_WEAPNEXT}, + {"weapprev", TF_WEAPPREV}, + {"weaplast", TF_WEAPLAST}, + {"reload", TF_RELOAD}, + {"reload1", TF_RELOAD_SLOT1}, + {"reload2", TF_RELOAD_SLOT2}, + {"reload3", TF_RELOAD_SLOT3}, + {"reloadnext", TF_RELOAD_NEXT}, + {"throwgren", TF_GRENADE_T}, + {"primeone", TF_GRENADE_1}, + {"primetwo", TF_GRENADE_2}, + {"+gren1", TF_GRENADE_1}, + {"-gren1", TF_GRENADE_T}, + {"+gren2", TF_GRENADE_2}, + {"-gren2", TF_GRENADE_T}, + {"gren1", TF_GRENADE_PT_1}, + {"gren2", TF_GRENADE_PT_2}, + {"dash", TF_DASH}, + {"autoscan", TF_SCAN}, + {"scansound", TF_SCAN_SOUND}, + {"scanf", TF_SCAN_FRIENDLY}, + {"scane", TF_SCAN_ENEMY}, + {"detpipe", TF_PB_DETONATE}, + {"+det5", TF_DETPACK_5}, + {"-det5", TF_DETPACK_STOP}, + {"+det20", TF_DETPACK_20}, + {"-det20", TF_DETPACK_STOP}, + {"+det50", TF_DETPACK_50}, + {"-det50", TF_DETPACK_STOP}, + {"+det255", TF_DETPACK}, + {"-det255", TF_DETPACK_STOP}, + {"aura", TF_MEDIC_AURA_TOGGLE}, + {"locktoggle", TF_HVYWEAP_LOCK_TOGGLE}, + {"lock", TF_LOCKON}, + {"unlock", TF_LOCKOFF}, + {"+lock", TF_LOCKON}, + {"-lock", TF_LOCKOFF}, + {"airblast", TF_AIRBLAST}, + {"disguise", 0, "fo_menu_disguise", TF_SPY_SPY}, + {"+feign", TF_SPY_DIE_ON}, + {"-feign", TF_SPY_DIE_OFF}, + {"feign", TF_SPY_DIE}, + {"sfeign", TF_SPY_SILENT_DIE}, + {"dreset", TF_DISGUISE_RESET}, + {"dscout", TF_DISGUISE_SCOUT}, + {"dsniper", TF_DISGUISE_SNIPER}, + {"dsoldier", TF_DISGUISE_SOLDIER}, + {"ddemoman", TF_DISGUISE_DEMOMAN}, + {"dmedic", TF_DISGUISE_MEDIC}, + {"dhwguy", TF_DISGUISE_HWGUY}, + {"dpyro", TF_DISGUISE_PYRO}, + {"dengineer", TF_DISGUISE_ENGINEER}, + {"dblue", TF_DISGUISE_BLUE}, + {"dred", TF_DISGUISE_RED}, + {"dyellow", TF_DISGUISE_YELLOW}, + {"dgreen", TF_DISGUISE_GREEN}, + {"denemy", TF_DISGUISE_ENEMY}, + {"dlast", TF_DISGUISE_LAST}, + {"dlastspawn", TF_DISGUISE_LAST_SPAWNED}, + {"build", 0, "fo_menu_build", TF_ENGINEER_BUILD}, + {"detsentry", TF_ENGINEER_DETSENTRY}, + {"detdispenser", TF_ENGINEER_DETDISP}, + {"toggledispenser", TF_ENGINEER_TOGGLEDISPENSER}, + {"togglesentry", TF_ENGINEER_TOGGLESENTRY}, + {"ready", TF_PLAYER_READY}, + {"notready", TF_PLAYER_NOT_READY}, + {"nginfo", TF_NAILGREN_INFO}, + {"break", 0, "cmd break"}, + {"voteyes", 0, "cmd voteyes"}, + {"yes", 0, "cmd voteyes"}, + {"fo_settings_status", 0, "cmd fo_settings_status"}, + {"placepracspawn", TF_PRACSPAWN_PLACE}, + {"removepracspawn", TF_PRACSPAWN_REMOVE}, + {"tracktarget", 0, "cmd tracktarget", 0, "cmd track target"}, +}; + +TFAlias csqc_aliases[] = { + {"+special", 0, "+button3"}, + {"-special", 0, "-button3"}, + {"+special2", 0, "+button4"}, + {"-special2", 0, "-button4"}, + {"+grenade1", 0, "+button5"}, + {"-grenade1", 0, "-button5"}, + {"+grenade2", 0, "+button6"}, + {"-grenade2", 0, "-button6"}, + {"+dropflag", 0, "+button7"}, + {"-dropflag", 0, "-button7"}, +}; + +#define NB_CONC_CAP_AIR 1100 +#define NB_CONC_CAP_LAND 950 diff --git a/share/engineer.qc b/share/engineer.qc new file mode 100644 index 000000000..01bb70837 --- /dev/null +++ b/share/engineer.qc @@ -0,0 +1,22 @@ +float PlaceSentry(entity sentry, vector builder_org) { + tracebox(sentry.origin, sentry.mins, sentry.maxs, sentry.origin, MOVE_NORMAL, sentry); + + if (trace_inopen) + return (1); + + local vector start = sentry.origin; + start_z = builder_org_z + 64; + + tracebox(start, sentry.mins, sentry.maxs, sentry.origin, MOVE_NORMAL, sentry); + + if (trace_fraction < 1.0) { + tracebox(trace_endpos, sentry.mins, sentry.maxs, trace_endpos, MOVE_NORMAL, sentry); + + if (trace_inopen) { + sentry.origin = trace_endpos; + return (1); + } + } + + return (0); +} diff --git a/share/fteextensions.qc b/share/fteextensions.qc new file mode 100644 index 000000000..a47a9767f --- /dev/null +++ b/share/fteextensions.qc @@ -0,0 +1,3840 @@ +/* +This file was generated by FTE Quake 6306, dated 2022-08-19T13:30:16.464410Z. +This file can be regenerated by issuing the following command: +pr_dumpplatform -o fteextensions +(Use the -help arg for a list of available args) +*/ +#pragma noref 1 +//#pragma flag enable logicops +#pragma warning error Q101 /*too many parms. The vanilla qcc didn't validate properly, hence why fteqcc normally treats it as a warning.*/ +#pragma warning error Q105 /*too few parms. The vanilla qcc didn't validate properly, hence why fteqcc normally treats it as a warning.*/ +#pragma warning error Q106 /*assignment to constant/lvalue. Define them as var if you want to initialise something.*/ +#pragma warning error Q208 /*system crc unknown. Compatibility goes out of the window if you disable this.*/ +#pragma warning enable F301 /*non-utf-8 strings. Think of the foreigners! Also think of text editors that insist on screwing up your char encodings.*/ +#pragma warning enable F302 /*uninitialised locals. They usually default to 0 in qc (except in recursive functions), but its still probably a bug*/ +#if !defined(CSQC) && !defined(NQSSQC) && !defined(QWSSQC)&& !defined(MENU) + #ifdef QUAKEWORLD + #define QWSSQC + #else + #define NQSSQC + #endif +#endif +#if !defined(SSQC) && (defined(QWSSQC) || defined(NQSSQC)) + #define SSQC +#endif +#if defined(CSQC) || defined(MENU) + #define DEP_CSQC DEP +#else + #define DEP_CSQC __deprecated("Use CSQC for this") +#endif +#ifndef DEP + #define DEP __deprecated //predefine this if you want to avoid our deprecation warnings. +#endif +#ifndef FTEDEP + #define FTEDEP(reason) //for symbols deprecated in FTE that may still be useful/required for other engines +#endif +#define BX_COLOREDTEXT +#define DP_CON_SET /* The 'set' console command exists, and can be used to create/set cvars. */ +#define DP_CON_SETA /* The 'seta' console command exists, like the 'set' command, but also marks the cvar for archiving, allowing it to be written into the user's config. Use this command in your default.cfg file. */ +#define DP_CSQC_ROTATEMOVES +#define DP_EF_ADDITIVE +#define DP_ENT_ALPHA +#define DP_EF_BLUE +#define DP_EF_FULLBRIGHT +#define DP_EF_NODEPTHTEST +#define DP_EF_NODRAW +#define DP_EF_NOGUNBOB +#define DP_EF_NOSHADOW +#define DP_EF_RED +#define DP_ENT_COLORMOD +#define DP_ENT_CUSTOMCOLORMAP +#define DP_ENT_EXTERIORMODELTOCLIENT +#define DP_ENT_SCALE +#define DP_ENT_TRAILEFFECTNUM /* self.traileffectnum=particleeffectnum("myeffectname"); can be used to attach a particle trail to the given server entity. This is equivelent to calling trailparticles each frame. */ +#define DP_ENT_VIEWMODEL +#define DP_GECKO_SUPPORT +#define DP_GFX_FONTS +#define DP_GFX_QUAKE3MODELTAGS +#define DP_GFX_SKINFILES +#define DP_GFX_SKYBOX +#define DP_HALFLIFE_MAP +#define DP_HALFLIFE_MAP_CVAR +#define DP_INPUTBUTTONS +#define DP_LIGHTSTYLE_STATICVALUE +#define DP_LITSUPPORT +#define DP_MONSTERWALK /* MOVETYPE_WALK is valid on non-player entities. Note that only players receive acceleration etc in line with none/bounce/fly/noclip movetypes on the player, thus you will have to provide your own accelerations (incluing gravity) yourself. */ +#define DP_MOVETYPEBOUNCEMISSILE +#define DP_MOVETYPEFOLLOW +#define DP_QC_ASINACOSATANATAN2TAN +#define DP_QC_CHANGEPITCH +#define DP_QC_COPYENTITY +#define DP_QC_CRC16 +#define DP_QC_CVAR_DEFSTRING +#define DP_QC_CVAR_STRING +#define DP_QC_CVAR_TYPE +#define DP_QC_DIGEST_SHA256 +#define DP_QC_EDICT_NUM +#define DP_QC_ENTITYDATA +#define DP_QC_ETOS +#define DP_QC_FINDCHAIN +#define DP_QC_FINDCHAINFLOAT +#define DP_QC_FINDFLAGS +#define DP_QC_FINDCHAINFLAGS +#define DP_QC_FINDFLOAT +#define DP_QC_FS_SEARCH +#define DP_QC_FS_SEARCH_PACKFILE +#define DP_QC_GETLIGHT +#define DP_QC_GETSURFACE +#define DP_QC_GETSURFACEPOINTATTRIBUTE +#define DP_QC_GETTAGINFO +#define DP_QC_I18N /* Specifies that the engine uses $MODULE.dat.$LANG.po files that translates the dotranslate_* globals on load - these are usually created via the _("foo") qcc intrinsic. */ +#define DP_QC_MINMAXBOUND +#define DP_QC_MULTIPLETEMPSTRINGS /* Superseded by DP_QC_UNLIMITEDTEMPSTRINGS. Functions that return a temporary string will not overwrite/destroy previous temporary strings until at least 16 strings are returned (or control returns to the engine). */ +#define DP_QC_RANDOMVEC +#define DP_QC_RENDER_SCENE /* clearscene+addentity+setviewprop+renderscene+setmodel are available to menuqc. WARNING: DP advertises this extension without actually supporting it, FTE does actually support it. */ +#define DP_QC_SINCOSSQRTPOW +#define DP_QC_SPRINTF /* Provides the sprintf builtin, which allows for rich formatting along the lines of C's function with the same name. Not to be confused with QC's sprint builtin. */ +#define DP_QC_STRFTIME +#define DP_QC_STRING_CASE_FUNCTIONS +#define DP_QC_STRINGBUFFERS +#define DP_QC_STRINGCOLORFUNCTIONS +#define DP_QC_STRREPLACE +#define DP_QC_TOKENIZEBYSEPARATOR +#define DP_QC_TRACEBOX +#define DP_QC_TRACETOSS +#define DP_QC_TRACE_MOVETYPE_HITMODEL +#define DP_QC_TRACE_MOVETYPE_WORLDONLY +#define DP_QC_TRACE_MOVETYPES +#define DP_QC_UNLIMITEDTEMPSTRINGS /* Supersedes DP_QC_MULTIPLETEMPSTRINGS, superseded by FTE_QC_PERSISTENTTEMPSTRINGS. Specifies that all temp strings will be valid at least until the QCVM returns. */ +#define DP_QC_URI_ESCAPE +#define DP_QC_URI_GET +#define DP_QC_URI_POST +#define DP_QC_VECTOANGLES_WITH_ROLL +#define DP_QC_VECTORVECTORS +#define DP_QC_WHICHPACK +#define DP_QUAKE2_MODEL +#define DP_QUAKE2_SPRITE +#define DP_QUAKE3_MAP +#define DP_QUAKE3_MODEL +#define DP_REGISTERCVAR +#define DP_SND_SOUND7_WIP2 +#define DP_SND_STEREOWAV +#define DP_SND_OGGVORBIS +#define DP_SOLIDCORPSE +#define DP_SPRITE32 +#define DP_SV_BOTCLIENT +#define DP_SV_CLIENTCAMERA /* Works like svc_setview except also handles pvs. */ +#define DP_SV_CLIENTCOLORS /* Provided only for compatibility with DP. */ +#define DP_SV_CLIENTNAME /* Provided only for compatibility with DP. */ +#define DP_SV_CUSTOMIZEENTITYFORCLIENT /* Deprecated feature for compat with DP. Can be slow, incompatible with splitscreen, usually malfunctions with mvds. */ +#define DP_SV_DRAWONLYTOCLIENT +#define DP_SV_DROPCLIENT /* Equivelent to quakeworld's stuffcmd(self,"disconnect\n"); hack */ +#define DP_SV_EFFECT +#define DP_SV_EXTERIORMODELFORCLIENT +#define DP_SV_NODRAWTOCLIENT +#define DP_SV_PLAYERPHYSICS /* Allows reworking parts of NQ player physics. USE AT OWN RISK - this necessitates NQ physics and is thus guarenteed to break prediction. */ +#define DP_SV_POINTSOUND +#define DP_SV_PRECACHEANYTIME /* Specifies that the various precache builtins can be called at any time. WARNING: precaches are sent reliably while sound events, modelindexes, and particle events are not. This can mean sounds and particles might not work the first time around, or models may take a while to appear (after the reliables are received and the model is loaded from disk). Always attempt to precache a little in advance in order to reduce these issues (preferably at the start of the map...) */ +#define DP_SV_PRINT /* Says that the print builtin can be used from nqssqc (as well as just csqc), bypassing the developer cvar issues. */ +#define DP_SV_ROTATINGBMODEL /* Engines that support this support avelocity on MOVETYPE_PUSH entities, pushing entities out of the way as needed. */ +#define DP_SV_SETCOLOR +#define DP_SV_SHUTDOWN +#define DP_SV_SPAWNFUNC_PREFIX +#define DP_SV_WRITEPICTURE +#define DP_SV_WRITEUNTERMINATEDSTRING +#define DP_TE_BLOOD +#define DP_TE_CUSTOMFLASH +#define DP_TE_EXPLOSIONRGB +#define DP_TE_PARTICLECUBE +#define DP_TE_PARTICLERAIN +#define DP_TE_PARTICLESNOW +#define DP_TE_SMALLFLASH +#define DP_TE_SPARK +#define DP_TE_STANDARDEFFECTBUILTINS +#define DP_VIEWZOOM +#define EXT_BITSHIFT +#define EXT_CSQC +#define EXT_CSQC_SHARED +#define EXT_DIMENSION_VISIBILITY +#define EXT_DIMENSION_PHYSICS +#define EXT_DIMENSION_GHOST +#define FRIK_FILE +#define FTE_CALLTIMEOFDAY /* Replication of mvdsv functionality (call calltimeofday to cause 'timeofday' to be called, with arguments that can be saved off to a global). Generally strftime is simpler to use. */ +#define FTE_CSQC_ALTCONSOLES /* The engine tracks multiple consoles. These may or may not be directly visible to the user. */ +#define FTE_CSQC_BASEFRAME /* Specifies that .basebone, .baseframe2, .baselerpfrac, baseframe1time, etc exist in csqc. These fields affect all bones in the entity's model with a lower index than the .basebone field, allowing you to give separate control to the legs of a skeletal model, without affecting the torso animations. */ +#define FTE_CSQC_HALFLIFE_MODELS +#define FTE_CSQC_SERVERBROWSER /* Provides builtins to query the engine's serverbrowser servers list from ssqc. Note that these builtins are always available in menuqc. */ +#define FTE_CSQC_SKELETONOBJECTS /* Provides container objects for skeletal bone data, which can be modified on a per bone basis if needed. This allows you to dynamically generate animations (or just blend them with greater customisation) instead of being limited to a single animation or two. */ +#define FTE_CSQC_RAWIMAGES /* Provides raw rgba image access to csqc. With this, the csprogs can read textures into qc-accessible memory, modify it, and then upload it to the renderer. */ +#define FTE_CSQC_RENDERTARGETS /* VF_RT_DESTCOLOUR exists and can be used to redirect any rendering to a texture instead of the screen. */ +#define FTE_CSQC_REVERB /* Specifies that the mod can create custom reverb effects. Whether they will actually be used or not depends upon the sound driver. */ +#define FTE_CSQC_WINDOWCAPTION /* Provides csqc with the ability to change the window caption as displayed when running windowed or in the task bar when switched out. */ +#define FTE_ENT_SKIN_CONTENTS /* self.skin = CONTENTS_WATER; makes a brush entity into water. use -16 for a ladder. */ +#define FTE_ENT_UNIQUESPAWNID +#define FTE_EXTENDEDTEXTCODES +#define FTE_FORCESHADER /* Allows csqc to override shaders on models with an explicitly named replacement. Also allows you to define shaders with a fallback if it does not exist on disk. */ +#define FTE_FORCEINFOKEY /* Provides an easy way to change a user's userinfo from the server. */ +#define FTE_GFX_QUAKE3SHADERS /* specifies that the engine has full support for vanilla quake3 shaders */ +#define FTE_GFX_REMAPSHADER /* With the raw power of stuffcmds, the r_remapshader console command is exposed! This mystical command can be used to remap any shader to another. Remapped shaders that specify $diffuse etc in some form will inherit the textures implied by the surface. */ +#define FTE_GFX_IQM_HITMESH /* Supports hitmesh iqm extensions. Also supports geomsets and embedded events. */ +#define FTE_GFX_MODELEVENTS /* Provides a query for per-animation events in model files, including from progs/foo.mdl.events files. */ +#define FTE_ISBACKBUFFERED /* Allows you to check if a client has too many reliable messages pending. */ +#define FTE_MEMALLOC /* Allows dynamically allocating memory. Use pointers to access this memory. Memory will not be saved into saved games. */ +#define FTE_MEDIA_CIN /* playfilm command supports q2 cin files. */ +#define FTE_MEDIA_ROQ /* playfilm command supports q3 roq files. */ +#define FTE_MULTIPROGS /* Multiple progs.dat files can be loaded inside the same qcvm. Insert new ones with addprogs inside the 'init' function, and use externvalue+externset to rewrite globals (and hook functions) to link them together. Note that the result is generally not very clean unless you carefully design for it beforehand. */ +#define FTE_MULTITHREADED /* Faux multithreading, allowing multiple contexts to run in sequence. */ +#define FTE_MVD_PLAYERSTATS /* In csqc, getplayerstat can be used to query any player's stats when playing back MVDs. isdemo will return 2 in this case. */ +#define FTE_PART_SCRIPT /* Specifies that the r_particledesc cvar can be used to select a list of particle effects to load from particles/foo.cfg, the format of which is documented elsewhere. */ +#define FTE_PART_NAMESPACES /* Specifies that the engine can use foo.bar to load effect foo from particle description bar. When used via ssqc, this should cause the client to download whatever effects as needed. */ +#define FTE_PART_NAMESPACE_EFFECTINFO /* Specifies that effectinfo.bar can load effects from effectinfo.txt for DP compatibility. */ +#define FTE_PEXT_SETVIEW /* NQ's svc_setview works correctly even in quakeworld */ +#define FTE_PEXT_LIGHTSTYLECOL +#define FTE_PEXT_VIEW2 +#define FTE_PEXT_FATNESS +#define FTE_PEXT_TE_BULLET +#define FTE_PEXT_FLOATCOORDS +#define FTE_PEXT_Q2BSP /* Specifies that the client supports q2bsps. */ +#define FTE_PEXT_Q3BSP /* Specifies that the client supports q3bsps. */ +#define FTE_HEXEN2 +#define FTE_PEXT_SPAWNSTATIC +#define FTE_PEXT_CUSTOMTENTS +#define FTE_PEXT_256PACKETENTITIES /* Specifies that the client is not limited to vanilla's limit of only 64 ents visible at once. */ +#define FTE_QC_BASEFRAME /* Specifies that .basebone and .baseframe exist in ssqc. These fields affect all bones in the entity's model with a lower index than the .basebone field, allowing you to give separate control to the legs of a skeletal model, without affecting the torso animations, from ssqc. */ +#define FTE_QC_FILE_BINARY /* Extends FRIK_FILE with binary read+write, as well as allowing seeking. Requires pointers. */ +#define FTE_QC_CHANGELEVEL_HUB /* Adds an extra argument to changelevel which is carried over to the next map in the 'spawnspot' global. Maps will be saved+reloaded until the extra argument is omitted again, purging all saved maps. Saved games will contain a copy of each preserved map. parm1-parm64 globals can be used, giving more space to transfer more player data. */ +#define FTE_QC_CHECKCOMMAND /* Provides a way to test if a console command exists, and whether its a command/alias/cvar. Does not say anything about the expected meanings of any arguments or values. */ +#define FTE_QC_CHECKPVS +#define FTE_QC_CROSSPRODUCT +#define FTE_QC_CUSTOMSKINS /* The engine supports the use of q3 skins, as well as the use of such skin 'files' to specify rich top+bottom colours, qw skins, geomsets, or texture composition even on non-players.. */ +#define FTE_QC_DIGEST_SHA1 /* The digest_hex builtin supports 160-bit sha1 hashes. */ +#define FTE_QC_DIGEST_SHA224 /* The digest_hex builtin supports 224-bit sha2 hashes. */ +#define FTE_QC_DIGEST_SHA384 /* The digest_hex builtin supports 384-bit sha2 hashes. */ +#define FTE_QC_DIGEST_SHA512 /* The digest_hex builtin supports 512-bit sha2 hashes. */ +#define FTE_QC_FS_SEARCH_SIZEMTIME +#define FTE_QC_HARDWARECURSORS /* setcursormode exists in both csqc+menuqc, and accepts additional arguments to specify a cursor image to use when this module has focus. If the image exceeds hardware limits (or hardware cursors are unsupported), it will be emulated using regular draws - this at least still avoids conflicting cursors as only one will ever be used, even if console+menu+csqc are all overlayed. */ +#define FTE_QC_HASHTABLES /* Provides efficient string-based lookups. */ +#define FTE_QC_INFOKEY /* QuakeWorld's infokey builtin works, and reports at least name+topcolor+bottomcolor+ping(in ms)+ip(unmasked, but not always ipv4)+team(aka bottomcolor in nq). Does not require actual localinfo/serverinfo/userinfo, but they're _highly_ recommended to any engines with csqc */ +#define FTE_QC_INTCONV /* Provides string<>int conversions, including hex representations. */ +#define FTE_QC_MATCHCLIENTNAME +#define FTE_QC_MULTICAST /* QuakeWorld's multicast builtin works along with MSG_MULTICAST, but also with unicast support. */ +#define FTE_QC_PAUSED +#define FTE_QC_PERSISTENTTEMPSTRINGS /* Supersedes DP_QC_MULTIPLETEMPSTRINGS. Temp strings are garbage collected automatically, and do not expire while they're still in use. This makes strzone redundant. */ +#define FTE_QC_RAGDOLL_WIP +#define FTE_QC_SENDPACKET /* Allows the use of out-of-band udp packets to/from other hosts. Includes the SV_ParseConnectionlessPacket event. */ +#define FTE_QC_STUFFCMDFLAGS /* Variation on regular stuffcmd that gives control over how spectators/mvds should be treated. */ +#define FTE_QC_TRACETRIGGER +#define FTE_QUAKE2_CLIENT /* This engine is able to act as a quake2 client */ +#define FTE_QUAKE2_SERVER /* This engine is able to act as a quake2 server */ +#define FTE_QUAKE3_CLIENT /* This engine is able to act as a quake3 client */ +#define FTE_QUAKE3_SERVER /* This engine is able to act as a quake3 server */ +#define FTE_SOLID_BSPTRIGGER /* Allows for mappers to use shaped triggers instead of being limited to axially aligned triggers. */ +#define FTE_SOLID_LADDER /* Allows a simple trigger to remove effects of gravity (solid 20). obsolete. will prolly be removed at some point as it is not networked properly. Use FTE_ENT_SKIN_CONTENTS */ +#define FTE_SPLITSCREEN /* Client supports splitscreen, controlled via cl_splitscreen. Servers require allow_splitscreen 1 if splitscreen is to be used over the internet. Mods that use csqc will need to be aware for this to work properly. per-client networking may be problematic. */ +#define FTE_SQL /* Provides sql* builtins which can be used for sql database access */ +#define FTE_SQL_SQLITE /* SQL functionality is able to utilise sqlite databases */ +#define FTE_STRINGS /* Extra builtins (and additional behaviour) to make string manipulation easier */ +#define FTE_SV_POINTPARTICLES /* Specifies that particleeffectnum, pointparticles, and trailparticles exist in ssqc as well as csqc. particleeffectnum acts as a precache, allowing ssqc values to be networked up with csqc for use. Use in combination with FTE_PART_SCRIPT+FTE_PART_NAMESPACES to use custom effects. This extension is functionally identical to the DP version, but avoids any misplaced assumptions about the format of the client's particle descriptions. */ +#define FTE_SV_REENTER +#define FTE_TE_STANDARDEFFECTBUILTINS /* Provides builtins to replace writebytes, with a QW compatible twist. */ +#define FTE_TERRAIN_MAP /* This engine supports .hmp files, as well as terrain embedded within bsp files. */ +#define FTE_RAW_MAP /* This engine supports directly loading .map files, as well as realtime editing of the various brushes. */ +#define FTE_INFOBLOBS /* Removes the length limits on user/server/local info strings, and allows embedded nulls and other otherwise-reserved characters. This can be used to network avatar images and the like, or other binary data. */ +#define FTE_VRINPUTS /* input_weapon, input_left_*, input_right_*, input_head_* work, both in csqc (as inputs with suitable plugin/hardware support) and ssqc (available in PlayerPreThink). */ +#define KRIMZON_SV_PARSECLIENTCOMMAND /* SSQC's SV_ParseClientCommand function is able to handle client 'cmd' commands. The tokenizing parts also work in csqc. */ +#define NEH_CMD_PLAY2 +#define NEH_RESTOREGAME +#define QSG_CVARSTRING +#define QW_ENGINE +#define QWE_MVD_RECORD /* You can use the easyrecord command to record MVD demos serverside. */ +#define TEI_MD3_MODEL +#define TEI_SHOWLMP2 +#define TENEBRAE_GFX_DLIGHTS /* Allows ssqc to attach rtlights to entities with various special properties. */ +#define ZQ_MOVETYPE_FLY /* MOVETYPE_FLY works on players. */ +#define ZQ_MOVETYPE_NOCLIP /* MOVETYPE_NOCLIP works on players. */ +#define ZQ_MOVETYPE_NONE /* MOVETYPE_NONE works on players. */ +#define ZQ_VWEP +#define ZQ_QC_STRINGS /* The strings-only subset of FRIK_FILE is supported. */ + +#ifdef _ACCESSORS +accessor strbuf : float; +accessor searchhandle : float; +accessor hashtable : float; +accessor infostring : string; +accessor filestream : float; +accessor filestream : float; +#else +#define strbuf float +#define searchhandle float +#define hashtable float +#define infostring string +#define filestream float +#endif + +entity self; /* The magic me */ +#if defined(CSQC) || defined(SSQC) +entity other; /* Valid in touch functions, this is the entity that we touched. */ +entity world; /* The null entity. Hurrah. Readonly after map spawn time. */ +float time; /* The current game time. Stops when paused. */ +#endif +#ifdef CSQC +float cltime; /* A local timer that ticks relative to local time regardless of latency, packetloss, or pause. */ +#endif +#if defined(CSQC) || defined(SSQC) +float frametime; /* The time since the last physics/render/input frame. */ +#endif +#ifdef CSQC +float player_localentnum; /* This is entity number the player is seeing from/spectating, or the player themself, can change mid-map. */ +float player_localnum; /* The 0-based player index, valid for getplayerkeyvalue calls. */ +float maxclients; /* Maximum number of player slots on the server. */ +float clientcommandframe; /* This is the input-frame sequence. frames < clientcommandframe have been sent to the server. frame==clientcommandframe is still being generated and can still change. */ +float servercommandframe; /* This is the input-frame that was last acknowledged by the server. Input frames greater than this should be applied to the player's entity. */ +#endif +#if defined(QWSSQC) +entity newmis; /* A named entity that should be run soon, to reduce the effects of latency. */ +#endif +#ifdef SSQC +float force_retouch; /* If positive, causes all entities to check for triggers. */ +#endif +#if defined(CSQC) || defined(SSQC) +string mapname; /* The short name of the map. */ +#endif +#if defined(NQSSQC) +float deathmatch; +float coop; +float teamplay; +#endif +#ifdef SSQC +float serverflags; +float total_secrets; +float total_monsters; +float found_secrets; +float killed_monsters; +float parm1; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm2; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm3; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm4; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm5; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm6; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm7; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm8; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm9; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm10; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm11; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm12; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm13; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm14; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm15; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +float parm16; /* Player specific mod-defined values that are transferred from one map to the next. These are set by the qc inside calls to SetNewParms and SetChangeParms, and can then be read on a per-player basis after a call to the setspawnparms builtin. They are not otherwise valid. */ +#endif +#ifdef CSQC +float intermission; +#endif +#if defined(CSQC) || defined(SSQC) +vector v_forward; +vector v_up; +vector v_right; +#endif +#ifdef CSQC +vector view_angles; /* +x=DOWN */ +#endif +#if defined(CSQC) || defined(SSQC) +float trace_allsolid; +float trace_startsolid; +float trace_fraction; +vector trace_endpos; +vector trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; +#endif +#ifdef CSQC +float input_timelength; +vector input_angles; /* +x=DOWN */ +vector input_movevalues; +float input_buttons; +float input_impulse; +#endif +#ifdef SSQC +entity msg_entity; +void() main; /* This function is never called, and is effectively dead code. */ +void() StartFrame; /* Called at the start of each new physics frame. Player entities may think out of sequence so try not to depend upon explicit ordering too much. */ +void() PlayerPreThink; /* With Prediction(QW compat/FTE default): Called before the player's input commands are processed. +No Prediction(NQ compat): Called AFTER the player's movement intents have already been processed (ie: velocity will have already changed according to input_*, but before the actual position change. */ +void() PlayerPostThink; /* Called after the player's input commands are processed. */ +void() ClientKill; /* Called in response to 'cmd kill' (or just 'kill'). */ +void() ClientConnect; /* Called after the connecting client has finished loading and is ready to receive active entities. Note that this is NOT the first place that a client might be referred to. To determine if the client has csqc active (and kick anyone that doesn't), you can use if(infokeyf(self,INFOKEY_P_CSQCACTIVE)) {sprint(self, "CSQC is required for this server\n");dropclient(self);} */ +void() PutClientInServer; /* Enginewise, this is only ever called immediately after ClientConnect and is thus a little redundant. Modwise, this is also called for respawning a player etc. */ +void() ClientDisconnect; /* Called once a client disconnects or times out. Not guarenteed to be called on map changes. */ +void() SetNewParms; /* Called without context when a new client initially connects (before ClientConnect is even called). This function is expected to only set the parm* globals so that they can be decoded properly later. You should not rely on 'self' being set. */ +void() SetChangeParms; /* Called for each client on map changes. Should copy various entity fields to the parm* globals. */ +#endif +void end_sys_globals; +#if defined(CSQC) || defined(SSQC) +.float modelindex; /* This is the model precache index for the model that was set on the entity, instead of having to look up the model according to the .model field. Use setmodel to change it. */ +.vector absmin; /* Set by the engine when the entity is relinked (by setorigin, setsize, or setmodel). This is in world coordinates. */ +.vector absmax; /* Set by the engine when the entity is relinked (by setorigin, setsize, or setmodel). This is in world coordinates. */ +#endif +#ifdef SSQC +.float ltime; /* On MOVETYPE_PUSH entities, this is used as an alternative to the 'time' global, and .nextthink is synced to this instead of time. This allows time to effectively freeze if the entity is blocked, ensuring the think happens when the entity reaches the target point instead of randomly. */ +#endif +#ifdef CSQC +.float entnum; /* The entity number as its known on the server. */ +.float drawmask; /* Acts as a filter in the addentities call. */ +.float() predraw; /* Called by addentities after the filter and before the entity is actually drawn. Do your interpolation and animation in here. Should return one of the PREDRAW_* constants. */ +#endif +#if defined(QWSSQC) +.float lastruntime; /* This field used to be used to avoid running an entity multiple times in a single frame due to quakeworld's out-of-order thinks. It is no longer used by FTE due to precision issues, but may still be updated for compatibility reasons. */ +#endif +#if defined(CSQC) || defined(SSQC) +.float movetype; /* Describes how the entity moves. One of the MOVETYPE_ constants. */ +.float solid; /* Describes whether the entity is solid or not, and any special properties infered by that. Must be one of the SOLID_ constants */ +.vector origin; /* The current location of the entity in world space. Inline bsp entities (ie: ones placed by a mapper) will typically have a value of '0 0 0' in their neutral pose, as the geometry is offset from that. It is the reference point of the entity rather than the center of its geometry, for non-bsp models, this is often not a significant distinction. */ +.vector oldorigin; /* This is often used on players to reset the player back to where they were last frame if they somehow got stuck inside something due to fpu precision. Never change a player's oldorigin field to inside a solid, because that might cause them to become pemanently stuck. */ +.vector velocity; /* The direction and speed that the entity is moving in world space. */ +.vector angles; /* The eular angles the entity is facing in, in pitch, yaw, roll order. Due to a legacy bug, mdl/iqm/etc formats use +x=UP, bsp/spr/etc formats use +x=DOWN. */ +.vector avelocity; /* The amount the entity's angles change by per second. Note that this is direct eular angles, and thus the angular change is non-linear and often just looks buggy if you're changing more than one angle at a time. */ +#endif +#ifdef CSQC +.float pmove_flags; +#endif +#if defined(NQSSQC) +.vector punchangle; +#endif +#if defined(CSQC) || defined(SSQC) +.string classname; /* Identifies the class/type of the entity. Useful for debugging, also used for loading, but its value is not otherwise significant to the engine, this leaves the mod free to set it to whatever it wants and randomly test strings for values in whatever inefficient way it chooses fit. */ +#endif +#ifdef CSQC +.float renderflags; +#endif +#if defined(CSQC) || defined(SSQC) +.string model; /* The model name that was set via setmodel, in theory. Often, this is cleared to null to prevent the engine from being seen by clients while not changing modelindex. This behaviour allows inline models to remain solid yet be invisible. */ +.float frame; /* The current frame the entity is meant to be displayed in. In CSQC, note the lerpfrac and frame2 fields as well. if it specifies a framegroup, the framegroup will autoanimate in ssqc, but not in csqc. */ +#endif +#ifdef CSQC +.float frame1time; /* The absolute time into the animation/framegroup specified by .frame. */ +.float frame2; /* The alternative frame. Visible only when lerpfrac is set to 1. */ +.float frame2time; /* The absolute time into the animation/framegroup specified by .frame2. */ +.float lerpfrac; /* If 0, use frame1 only. If 1, use frame2 only. Mix them together for values between. */ +#endif +#if defined(CSQC) || defined(SSQC) +.float skin; /* The skin index to use. on a bsp entity, setting this to 1 will switch to the 'activated' texture instead. A negative value will be understood as a replacement contents value, so setting it to CONTENTS_WATER will make a movable pool of water. */ +.float effects; /* Lots of random flags that change random effects. See EF_* constants. */ +.vector mins; /* The minimum extent of the model (ie: the bottom-left coordinate relative to the entity's origin). Change via setsize. May also be changed by setmodel. */ +.vector maxs; /* like mins, but in the other direction. */ +.vector size; /* maxs-mins. Updated when the entity is relinked (by setorigin, setsize, setmodel) */ +.void() touch; +#endif +#ifdef SSQC +.void() use; +#endif +#if defined(CSQC) || defined(SSQC) +.void() think; +.void() blocked; +.float nextthink; /* The time at which the entity is next scheduled to fire its think event. For MOVETYPE_PUSH entities, this is relative to that entity's ltime field, for all other entities it is relative to the time gloal. */ +#endif +#ifdef SSQC +.entity groundentity; +.float health; +.float frags; +.float weapon; +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells; +.float ammo_nails; +.float ammo_rockets; +.float ammo_cells; +.float items; +.float takedamage; +#endif +#if defined(CSQC) || defined(SSQC) +.entity chain; +#endif +#ifdef SSQC +.float deadflag; +.vector view_ofs; +.float button0; +.float button1; +.float button2; +.float impulse; +.float fixangle; /* Forces the clientside view angles to change to the value of .angles (has some lag). If set to 1/TRUE, the server will guess whether to send a delta or an explicit angle. If 2, will always send a delta (due to lag between transmission and acknowledgement, this cannot be spammed reliably). If 3, will always send an explicit angle. */ +.vector v_angle; /* The angles a player is viewing. +x is DOWN (pitch, yaw, roll) */ +#endif +#if defined(NQSSQC) +.float idealpitch; +#endif +#ifdef SSQC +.string netname; +#endif +#if defined(CSQC) || defined(SSQC) +.entity enemy; +.float flags; +.float colormap; +#endif +#ifdef SSQC +.float team; +.float max_health; +.float teleport_time; /* While active, prevents the player from using the +back command, also blocks waterjumping. */ +.float armortype; +.float armorvalue; +.float waterlevel; +.float watertype; +.float ideal_yaw; +.float yaw_speed; +.entity aiment; +.entity goalentity; +.float spawnflags; +.string target; +.string targetname; +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; +#endif +#if defined(CSQC) || defined(SSQC) +.entity owner; +#endif +#ifdef SSQC +.vector movedir; +.string message; +.float sounds; +.string noise; +.string noise1; +.string noise2; +.string noise3; +#endif +void end_sys_fields; +#ifdef MENU +float time; /* The current local time. Increases while paused. */ +#endif +#if defined(CSQC) || defined(SSQC) +float input_sequence; /* This is the client-generated input sequence number. 0 for unsequenced movements. */ +float input_servertime; /* Server's timestamp of the client's interpolation state. */ +#endif +#ifdef SSQC +float input_timelength; +vector input_angles; /* +x=DOWN */ +vector input_movevalues; +float input_buttons; +float input_impulse; +#endif +#if defined(CSQC) || defined(SSQC) +int trace_endcontents; +int trace_surfaceflags; +int trace_brush_id; +int trace_brush_faceid; +int trace_surface_id; /* 1-based. 0 if not known. */ +int trace_bone_id; /* 1-based. 0 if not known. typically needs MOVE_HITMODEL. */ +int trace_triangle_id; /* 1-based. 0 if not known. */ +#endif +#ifdef CSQC +float trace_networkentity; /* Repots which ssqc entnum was hit when a csqc traceline impacts an ssqc-based brush entity. */ +vector pmove_org; /* Reports the origin of the engineside player (after prediction). Does not work when the player is a csqc-owned entity. */ +vector pmove_vel; /* Reports the velocity of the engineside player (after prediction). Does not work when the player is a csqc-owned entity. */ +float pmove_onground; /* Reports the onground state of the engineside player (after prediction). Does not work when the player is a csqc-owned entity. */ +#endif +#if defined(CSQC) || defined(SSQC) +vector global_gravitydir = '0 0 -1'; /* The direction gravity should act in if not otherwise specified per entity. */ +int serverid; /* The unique id of this server within the server cluster. */ +#endif +#ifdef CSQC +.string message; /* Allows the csqc to read the map description from the server. */ +#endif +#ifdef SSQC +.float button3; +.float button4; +.float button5; +.float button6; +.float button7; +.float button8; +#endif +#if defined(NQSSQC) +.float buttonuse; /* For DP compat only. */ +.float buttonchat; /* For DP compat only. */ +.float cursor_active; /* For DP compat only. */ +.float button9; /* For DP compat only. */ +.float button10; /* For DP compat only. */ +.float button11; /* For DP compat only. */ +.float button12; /* For DP compat only. */ +.float button13; /* For DP compat only. */ +.float button14; /* For DP compat only. */ +.float button15; /* For DP compat only. */ +.float button16; /* For DP compat only. */ +.vector cursor_screen; /* For DP compat only. */ +.vector cursor_start; /* For DP compat only. */ +.vector cursor_impact; /* For DP compat only. */ +.entity cursor_entitynumber; /* For DP compat only. */ +.float lastruntime; /* This field used to be used to avoid running an entity multiple times in a single frame due to quakeworld's out-of-order thinks. It is no longer used by FTE due to precision issues, but may still be updated for compatibility reasons. */ +#endif +#if defined(CSQC) || defined(QWSSQC) +.vector punchangle; +#endif +#if defined(CSQC) || defined(SSQC) +.float gravity; /* Multiplier applied in addition to sv_gravity (not absolute units), to control the gravity affecting this entity specifically. */ +.float hull; /* Overrides the hull used by the entity for walkmove/movetogoal and not traceline/tracebox. */ +.entity movechain; /* This is a linked list of entities which will be moved whenever this entity moves, logically they are attached to this entity. */ +.void() chainmoved; /* Called when the entity is moved as a result of being part of another entity's .movechain */ +.void(float old, float new) contentstransition; /* This function is called when the entity moves between water and air. If specified, default splash sounds will be disabled allowing you to provide your own. */ +.float dimension_solid; /* This is the bitmask of dimensions which the entity is solid within. This is not networked, instead csqc traces impacting ssqc entities assumes the ssqc entity to have a dimension_solid of 1. */ +.float dimension_hit; /* This is the bitmask of dimensions which the entity will be blocked by. If other.dimension_solid & self.dimension_hit, our traces will impact and not proceed. If its false, the traces will NOT impact, allowing self to pass straight through. */ +.int hitcontentsmaski; /* Traces performed for this entity will impact against surfaces that match this contents mask (CONTENTBITS_* constants). */ +__deprecated("Does not support mod-specific contents.") .float dphitcontentsmask; /* Some crappy field that inefficiently requires translating to the native contents flags. Ditch the 'dp', do it properly. */ +.float scale; /* Multiplier that resizes the entity. 1 is normal sized, 2 is double sized. scale 0 is remapped to 1. In SSQC, this is limited to 1/16th precision, with a maximum just shy of 16. */ +.float fatness; /* How many QuakeUnits to push the entity's verticies along their normals by. */ +.float alpha; /* The transparency of the entity. 1 means opaque, 0.0001 means virtually invisible. 0 is remapped to 1, for compatibility. */ +.float modelflags; /* Used to override the flags set in the entity's model. Should be set according to the MF_ constants. Use effects|=EF_NOMODELFLAGS to ignore the model's flags completely. The traileffectnum field is more versatile. */ +#endif +#ifdef SSQC +.float frame1time; /* This controls the time into the framegroup/animation named by .frame, you should increment this value according to frametime or to distance moved, depending on the sort of animation you're attempting. You may wish to avoid incrementing this while lerpfrac is still changing, to avoid wasting parts of the animation. */ +#endif +#if defined(CSQC) || defined(SSQC) +.float basebone; /* The base* frame animations are equivelent to their non-base versions, except that they only affect bone numbers below the 'basebone' value. This means that the base* animation can affect the legs of a skeletal model independantly of the normal animation fields affecting the torso area. For more complex animation than this, use skeletal objects. */ +.float baseframe; /* See basebone */ +.void() customphysics; /* Called once each physics frame, overriding the entity's .movetype field and associated logic. You'll probably want to use tracebox to move it through the world. Be sure to call .think as appropriate. */ +.entity tag_entity; /* Specifies which entity this entity's origin+angles is 'attached' to. */ +.float tag_index; /* Specifies the tag or bone on the parent entity that we're attached to. If this is -1 then the entity is instead a q3-like camera portal, with the tag_entity saying the entity to display for. If tag_entity is world then this is a q3-like portal surface marker with a separate camera (with a tag_entity referring to the portal surface). */ +.float skeletonindex; /* This object serves as a container for the skeletal bone states used to override the animation data. */ +.vector colormod; /* Provides a colour tint for the entity (does not affect fullbrights). */ +.vector glowmod; /* Scaler for an entity's fullbright textures. */ +.vector gravitydir; /* Specifies the direction in which gravity acts. Must be normalised. '0 0 0' also means down. Use '0 0 1' if you want the player to be able to run on ceilings. */ +.vector(vector org, vector ang) camera_transform; /* A callback that provides portal transform information for portal surfaces attached to this entity. Also used to open up pvs in ssqc. */ +#endif +#ifdef SSQC +.float pmove_flags; +#endif +#if defined(CSQC) || defined(SSQC) +.float geomtype; +.float friction; +.float erp; +.float jointtype; +.float mass; +.float bouncefactor; +.float bouncestop; +#endif +#if defined(CSQC) || defined(QWSSQC) +.float idealpitch; +#endif +#if defined(CSQC) || defined(SSQC) +.float pitch_speed; +.float drawflags; /* Various flags that affect lighting values and scaling. Typically set to 96 in quake for proper compatibility with DP_QC_SCALE. */ +.float abslight; /* Allows overriding light levels. Use drawflags to state that this field should actually be used. */ +.vector color; /* This affects the colour of realtime lights that were enabled via the pflags field. */ +.float light_lev; /* This is the radius of an entity's light. This is not normally used by the engine, but is used for realtime lights (ones that are enabled with the pflags field). */ +.float style; /* Used by the light util to decide how an entity's light should animate. On an entity with pflags set, this also affects realtime lights. */ +.float pflags; /* Realtime lighting flags */ +#endif +#ifdef SSQC +.float maxspeed; +.entity view2; /* defines a second viewpoint, typically displayed in a corner of the screen (also punches open pvs). */ +.vector movement; /* These are the directions that the player is currently trying to move in (ie: which +forward/+moveright/+moveup etc buttons they have held), expressed relative to that player's angles. Order is forward, right, up. */ +.float vw_index; /* This acts as a second modelindex, using the same frames etc. */ +__deprecated("Cannot be recorded in MVDs, nor work properly with splitscreen. Use CSQC instead.") .entity nodrawtoclient; /* This entity will not be sent to the player named by this field. They will be invisible and not emit dlights/particles. Does not work in MVD-recorded game. */ +__deprecated("Cannot be recorded in MVDs, nor work properly with splitscreen. Use CSQC instead.") .entity drawonlytoclient; /* This entity will be sent *only* to the player named by this field. To other players they will be invisible and not emit dlights/particles. Does not work in MVD-recorded game. */ +__deprecated("Redundant. Cannot be recorded in MVDs, nor work properly with splitscreen. Use CSQC instead.") .entity viewmodelforclient; /* This entity will be sent only to the player named by this field, and this entity will be attached to the player's view as an additional weapon model. */ +__deprecated("Cannot be recorded in MVDs, nor work properly with splitscreen. Use CSQC instead.") .entity exteriormodeltoclient; /* This entity will be invisible to the player named by this field, except in mirrors or mirror-like surfaces, where it will be visible as normal. It may still cast shadows as normal, and generate lights+particles, depending on client settings. Does not affect how other players see the entity. */ +.entity clientcamera; /* Controls which entity to use for this client's camera. */ +.float glow_size; /* Some outdated particle trail thing. */ +.float glow_color; /* Some outdated particle trail thing. */ +.float glow_trail; /* Some outdated particle trail thing. */ +.float traileffectnum; /* This should be set to the result of particleeffectnum, in order to attach a custom trail effect to an entity as it moves. */ +.float emiteffectnum; /* This should be set to the result of particleeffectnum, in order to continually spawn particles in the direction that this entity faces. */ + +/* We use these for flash grenades and are OK with MVD/splitscreen issues. */ +/*__deprecated("Does not work with MVDs nor splitscreen.") */ .float dimension_see; /* This is the dimension mask (bitfield) that the client is allowed to see. Entities and events not in this dimension mask will be invisible. */ +/* __deprecated("Does not work with MVDs nor splitscreen.") */ .float dimension_seen; /* This is the dimension mask (bitfield) that the client is visible within. Clients that cannot see this dimension mask will not see this entity. */ +/* __deprecated("Does not work with MVDs nor splitscreen.") */ .float dimension_ghost; /* If this entity is visible only within these dimensions, it will become transparent, as if a ghost. */ +/* __deprecated("Does not work with MVDs nor splitscreen.") */ .float dimension_ghost_alpha; /* If this entity is subject to dimension_ghost, this is the scaler for its alpha value. If 0, 0.5 will be used instead. */ +.float(entity playerent, float changedflags) SendEntity; /* Called by the engine whenever an entity needs to be (re)sent to a client's csprogs, either because SendFlags was set or because data was lost. Must write its data to the MSG_ENTITY buffer. Will be called at the engine's leasure. */ +.float SendFlags; /* Indicates that something in the entity has been changed, and that it needs to be updated to all players that can see it. The engine will clear it at some point, with the cleared bits appearing in the 'changedflags' argument of the SendEntity method. */ +__deprecated("Use SendFlags instead.") .float Version; /* Obsolete */ +__deprecated("Doesn't support RGB player colours.") .float clientcolors; +.float viewzoom; +.float playerclass; +.float hasted; +.float light_level; /* Used by hexen2 to indicate the light level where the player is standing. */ +.float pvsflags; /* Reconfigures when the entity is visible to clients */ +.float uniquespawnid; /* Incremented by 1 whenever the entity is respawned. Persists across remove calls, for when the two-second grace period is insufficient. */ +DEP_CSQC .float() customizeentityforclient; /* Called just before an entity is sent to a client (non-csqc protocol). This gives you a chance to tailor 'self' according to what 'other' should see. */ +#endif +#ifdef CSQC +.float frame3; /* Some people just don't understand how to use framegroups... */ +.float frame3time; /* .frame3 equivelent of frame1time. */ +.float lerpfrac3; /* Weight of .frame3 - .frame's weight is automatically calculated as 1-(lerpfrac+lerpfrac3+lerpfrac4), as a result these fields should NEVER add to above 1. */ +.float frame4; +.float frame4time; /* .frame4 equivelent of frame1time. */ +.float lerpfrac4; +.float forceshader; /* Contains a shader handle used to replace all surfaces upon the entity. */ +.float baseframe2; /* See basebone */ +.float baseframe1time; /* See basebone */ +.float baseframe2time; /* See basebone */ +.float baselerpfrac; /* See basebone */ +.float bonecontrol1; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol2; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol3; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol4; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol5; /* Halflife model format bone controller. This typically affects the mouth. */ +.float subblendfrac; /* Weird animation value specific to halflife models. On player models, this typically affects the spine's pitch, or yaw, or... */ +.float subblend2frac; /* Weird animation value specific to halflife models. I've no idea what this does, probably nothing for most models. */ +.float basesubblendfrac; /* See basebone */ +.float basesubblend2frac; /* See basebone */ +#endif +void(float reqid, float responsecode, string resourcebody, int resourcebytes) URI_Get_Callback; /* Called as an eventual result of the uri_get builtin. */ +#ifdef SSQC +void() SpectatorConnect; /* Called when a spectator joins the game. */ +void() SpectatorDisconnect; /* Called when a spectator disconnects from the game. */ +void() SpectatorThink; /* Called each frame for each spectator. */ +void(string cmd) SV_ParseClientCommand; /* Provides QC with a way to intercept 'cmd foo' commands from the client. Very handy. Self will be set to the sending client, while the 'cmd' argument can be tokenize()d and each element retrieved via argv(argno). Unrecognised cmds MUST be passed on to the clientcommand builtin. */ +void(string dest, string from, string cmd, string info) SV_ParseClusterEvent; /* Part of cluster mode. Handles cross-node events that were sent via clusterevent, on behalf of the named client. */ +float(string sender, string body) SV_ParseConnectionlessPacket; /* Provides QC with a way to communicate between servers, or with client server browsers. Sender is the sender's ip. Body is the body of the message. You'll need to add your own password/etc support as required. Self is not valid. */ +void(float pauseduration) SV_PausedTic; /* For each frame that the server is paused, this function will be called to give the gamecode a chance to unpause the server again. the pauseduration argument says how long the server has been paused for (the time global is frozen and will not increment while paused). Self is not valid. */ +float(float newstatus) SV_ShouldPause; /* Called to give the qc a change to block pause/unpause requests. Return false for the pause request to be ignored. newstatus is 1 if the user is trying to pause the game. For the duration of the call, self will be set to the player who tried to pause, or to world if it was triggered by a server-side event. */ +void() SV_RunClientCommand; /* Called each time a player movement packet was received from a client. Self is set to the player entity which should be updated, while the input_* globals specify the various properties stored within the input packet. The contents of this function should be somewaht identical to the equivelent function in CSQC, or prediction misses will occur. If you're feeling lazy, you can simply call 'runstandardplayerphysics' after modifying the inputs. */ +void() SV_AddDebugPolygons; /* Called each video frame. This is the only place where ssqc is allowed to call the R_BeginPolygon/R_PolygonVertex/R_EndPolygon builtins. This is exclusively for debugging, and will break in anything but single player as it will not be called if the engine is not running both a client and a server. */ +DEP_CSQC void() SV_PlayerPhysics; /* Compatibility method to tweak player input that does not reliably work with prediction (prediction WILL break). Mods that care about prediction should use SV_RunClientCommand instead. If pr_no_playerphysics is set to 1, this function will never be called, which will either fix prediction or completely break player movement depending on whether the feature was even useful. */ +void(float fd, float entcount, float playerslots) SV_PerformSave; /* Called by the engine as part of saved games. The QC is responsible for writing any data that will be required to restore the game. Save files are not limited to just text, you can use fwrite (and later fread) for binary data. Remember to close the passed file. */ +void(float fd, float entcount, float playerslots) SV_PerformLoad; /* Called by the engine to restore a saved game that was saved via SV_PerformSave, the server will have already started the game to its inital state (for precaches+makestatic calls), so entities+globals will have their post-spawn values (you will likely want to remove most of them, you can use the two extra args as helpers for that). You can use respawn_edict to un-remove specific entities, with parseentitydata or putentityfieldstring(findentityfield(NAME), ent, value) to assign to fields by name strings. Don't expect bprints/etc to work - players are likely in a pending state. Remember to close the passed file. */ +void() RestoreGame; /* Called for each reconnecting player as part of loading a saved game. Part of NEH_RESTOREGAME. */ +void() EndFrame; /* Called after non-player entities have been run at the end of the physics frame. Player physics is performed out of order and can/will still occur between EndFrame and BeginFrame. */ +void() SetTransferParms; /* Called as an alternative to SetChangeParms when a player is transferring to another server. Part of FTE_SV_CLUSTER. */ +string(string addr, string uinfo, string features) SV_CheckRejectConnection; /* Called to give the mod a chance to ignore connection requests based upon client protocol support or other properties. Use infoget to read the uinfo and features arguments. */ +#endif +#ifdef CSQC +void(float apilevel, string enginename, float engineversion) CSQC_Init; /* Called at startup. enginename and engineversion are arbitary hints and can take any form. enginename should be consistant between revisions, but this cannot truely be relied upon. */ +void() CSQC_WorldLoaded; /* Called after the server's model+sound precaches have been executed. Gives a chance for the qc to read the entity lump from the bsp (via getentitytoken). */ +void() CSQC_Shutdown; /* Specifies that the csqc is going down. Save your persistant settings here. */ +void(float vwidth, float vheight, float notmenu) CSQC_UpdateView; /* Called every single video frame. The CSQC is responsible for rendering the entire screen. */ +void(float vwidth, float vheight, float notmenu) CSQC_UpdateViewLoading; /* Alternative to CSQC_UpdateView, called when the engine thinks there should be a loading screen. If present, will inhibit the engine's normal loading screen, deferring to qc to draw it. */ +void(vector viewsize, float scoresshown) CSQC_DrawHud; /* Part of simple csqc, called after drawing the 3d view whenever CSQC_UpdateView is not defined. */ +void(vector viewsize, float scoresshown) CSQC_DrawScores; /* Part of simple csqc, called after CSQC_DrawHud whenever CSQC_UpdateView is not defined, and when there are no menus/console active. */ +void(string msg) CSQC_Parse_StuffCmd; /* Gives the CSQC a chance to intercept stuffcmds. Use the tokenize builtin to parse the message. Unrecognised commands would normally be localcmded, but its probably better to drop unrecognised stuffcmds completely. */ +float(string msg) CSQC_Parse_CenterPrint; /* Gives the CSQC a chance to intercept centerprints. Return true if you wish the engine to otherwise ignore the centerprint. */ +float(float save, float take, vector inflictororg) CSQC_Parse_Damage; /* Called as a result of player.dmg_save or player.dmg_take being set on the server. +Return true to completely inhibit the engine's colour shift and damage rolls, allowing you to do your own thing. +You can use punch_roll += (normalize(inflictororg-player.origin)*v_right)*(take+save)*autocvar_v_kickroll; as a modifier for the roll angle should the player be hit from the side, and slowly fade it away over time. */ +void(string printmsg, float printlvl) CSQC_Parse_Print; /* Gives the CSQC a chance to intercept sprint/bprint builtin calls. CSQC should filter by the client's current msg setting and then pass the message on to the print command, or handle them itself. */ +void() CSQC_Parse_Event; /* Called when the client receives an SVC_CGAMEPACKET. The csqc should read the data or call the error builtin if it does not recognise the message. */ +float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent; /* Called whenever a key is pressed, the mouse is moved, etc. evtype will be one of the IE_* constants. The other arguments vary depending on the evtype. Key presses are not guarenteed to have both scan and unichar values set at the same time. */ +__used void() CSQC_Input_Frame; /* Called just before each time clientcommandframe is updated. You can edit the input_* globals in order to apply your own player inputs within csqc, which may allow you a convienient way to pass certain info to ssqc. */ +void(string rendererdescription) CSQC_RendererRestarted; /* Called by the engine after the video was restarted. This serves to notify the CSQC that any render targets that it may have cached were purged, and will need to be regenerated. */ +float(string cmd) CSQC_ConsoleCommand; /* Called if the user uses any console command registed via registercommand. */ +float(string text, string info) CSQC_ConsoleLink; /* Called if the user clicks a ^[text\infokey\infovalue^] link. Use infoget to read/check each supported key. Return true if you wish the engine to not attempt to handle the link itself. +WARNING: link text can potentially come from other players, so be careful about what you allow to be changed. */ +void(float entnum) CSQC_Ent_Spawn; /* Clumsily defined function for compat with DP. Should call spawn, set that ent's entnum field, and return the entity inside the 'self' global which will then be used for fllowing Ent_Updates. MUST NOT PARSE ANY NETWORK DATA (which makes it kinda useless). */ +void(float isnew) CSQC_Ent_Update; /* Parses the data sent by ssqc's various SendEntity functions (must use the exact same reads as the ssqc used writes - to debug this rule more easily, you may wish to use sv_csqcdebug). 'self' provides context between frames, and self.entnum should normally report which ssqc entity . Be aware that interpolation will need to happen separately. */ +void() CSQC_Ent_Remove; +float(float entnum, float channel, string soundname, float vol, float attenuation, vector pos, float pitchmod, float flags) CSQC_Event_Sound; +float() CSQC_Parse_TempEntity; /* Please don't use this. Use CSQC_Parse_Event and multicasts instead. +The use of serverside protocol translation to handle QW vs NQ protocols mean that you're likely to end up reading slightly different data. Which is bad. +Return true to say that you fully handled the tempentity. Return false to have the client attempt to rewind the network stream and parse the message itself. */ +#endif +#if defined(CSQC) || defined(MENU) +void(string cmdtext) GameCommand; +#endif +string(string uri, string method, string postdata, __in string requestheaders, __inout string responseheaders) Cef_GeneratePage; /* Provides an entrypoint to generate pages for the CEF plugin from within QC. Headers are +-separated key/value pairs (use tokenizebyseparator). */ +#ifdef SSQC +string(string uri, string method, string postdata, __in string requestheaders, __inout string responseheaders) HTTP_GeneratePage; /* Provides an entrypoint to generate pages for pages requested over http (sv_port_tcp+net_enable_http). Headers are +-separated key/value pairs (use tokenizebyseparator). Return __NULL__ to let the engine handle it, an empty string for a 404, and any other text for a regular 200 response. */ +#endif +#if defined(CSQC) || defined(SSQC) +void(float prevprogs) init; /* Part of FTE_MULTIPROGS. Called as soon as a progs is loaded, called at a time when entities are not valid. This is the only time when it is safe to call addprogs without field assignment. As it is also called as part of addprogs, this also gives you a chance to hook functions in modules that are already loaded (via externget+externget). */ +void() initents; /* Part of FTE_MULTIPROGS. Called after fields have been finalized. This is the first point at which it is safe to call spawn(), and is called before any entity fields have been parsed. You can use this entrypoint to send notifications to other modules. */ +#endif +#ifdef MENU +void() m_init; +void() m_shutdown; +void(vector screensize) m_draw; /* Provides the menuqc with a chance to draw. Will be called even if the menu does not have focus, so be sure to avoid that. COMPAT: screensize is not provided in DP. */ +void(vector screensize, float opaque) m_drawloading; /* Additional drawing function to draw loading screens. If opaque is set, then this function must ensure that the entire screen is overdrawn (even if just by a black drawfill). */ +void(string rendererdescription) Menu_RendererRestarted; /* Called by the engine after the video was restarted. This serves to notify the MenuQC that any render targets that it may have cached were purged, and will need to be regenerated. */ +float(float evtype, float scanx, float chary, float devid) Menu_InputEvent; /* If present, this is called instead of m_keydown and m_keyup +Called whenever a key is pressed, the mouse is moved, etc. evtype will be one of the IE_* constants. The other arguments vary depending on the evtype. Key presses are not guarenteed to have both scan and unichar values set at the same time. */ +__deprecated("Use Menu_InputEvent") void(float scan, float chr) m_keydown; +__deprecated("Use Menu_InputEvent") void(float scan, float chr) m_keyup; +void(float wantmode) m_toggle; +float(string cmd) m_consolecommand; +float(float idx) m_gethostcachecategory; +#endif +#ifdef SSQC +float parm17, parm18, parm19, parm20, parm21, parm22, parm23, parm24, parm25, parm26, parm27, parm28, parm29, parm30, parm31, parm32; /* Additional spawn parms, following the same parmN theme. */ +float parm33, parm34, parm35, parm36, parm37, parm38, parm39, parm40, parm41, parm42, parm43, parm44, parm45, parm46, parm47, parm48; +float parm49, parm50, parm51, parm52, parm53, parm54, parm55, parm56, parm57, parm58, parm59, parm60, parm61, parm62, parm63, parm64; +string parm_string; /* Like the regular parmN globals, but preserves string contents. */ +string startspot; /* Receives the value of the second argument to changelevel from the previous map. */ +var float dimension_send; /* Used by multicast functionality. Multicasts (and related builtins that multicast internally) will only be sent to players where (player.dimension_see & dimension_send) is non-zero. */ +//var float dimension_default = 255; +/* Default dimension bitmask */ +__unused var string __fullspawndata; /* Set by the engine before calls to spawn functions, and is most easily parsed with the tokenize builtin. This allows you to handle halflife's multiple-fields-with-the-same-name (or target-specific fields). */ +#endif +#if defined(CSQC) || defined(SSQC) +__used var float physics_mode = 2; /* 0: original csqc - physics are not run +1: DP-compat. Thinks occur, but not true movetypes. +2: movetypes occur just as they do in ssqc. */ +#endif +#ifdef CSQC +float gamespeed; /* Set by the engine, this is the value of the sv_gamespeed cvar */ +float numclientseats; /* This is the number of splitscreen clients currently running on this client. */ +#endif +#if defined(CSQC) || defined(MENU) +var vector drawfontscale = '1 1 0'; /* Specifies a scaler for all text rendering. There are other ways to implement this. */ +float drawfont; /* Allows you to choose exactly which font is to be used to draw text. Fonts can be registered/allocated with the loadfont builtin. */ +const float FONT_DEFAULT = 0; +#endif +#if defined(NQSSQC) +entity newmis; /* ssqc global */ +#endif +#if defined(CSQC) || defined(QWSSQC) +float deathmatch; /* ssqc global */ +float coop; /* ssqc global */ +#endif +#if defined(QWSSQC) +float teamplay; /* ssqc global */ +#endif +#if defined(CSQC) || defined(SSQC) +int trace_endcontentsi; /* ssqc global */ +int trace_surfaceflagsi; /* ssqc global */ +string trace_surfacename; /* ssqc global */ +float cycle_wrapped; /* ssqc global */ +#endif +#ifdef CSQC +float dimension_default; /* ssqc global */ +#endif +#if defined(CSQC) || defined(SSQC) +unsigned int input_weapon; /* ssqc global */ +float input_lightlevel; /* ssqc global */ +vector input_cursor_screen; /* ssqc global */ +vector input_cursor_trace_start; /* ssqc global */ +vector input_cursor_trace_endpos; /* ssqc global */ +float input_cursor_entitynumber; /* ssqc global */ +unsigned int input_head_status; /* ssqc global */ +vector input_head_origin; /* ssqc global */ +vector input_head_angles; /* ssqc global */ +vector input_head_velocity; /* ssqc global */ +vector input_head_avelocity; /* ssqc global */ +unsigned int input_head_weapon; /* ssqc global */ +unsigned int input_left_status; /* ssqc global */ +vector input_left_origin; /* ssqc global */ +vector input_left_angles; /* ssqc global */ +vector input_left_velocity; /* ssqc global */ +vector input_left_avelocity; /* ssqc global */ +unsigned int input_left_weapon; /* ssqc global */ +unsigned int input_right_status; /* ssqc global */ +vector input_right_origin; /* ssqc global */ +vector input_right_angles; /* ssqc global */ +vector input_right_velocity; /* ssqc global */ +vector input_right_avelocity; /* ssqc global */ +unsigned int input_right_weapon; /* ssqc global */ +#endif +#ifdef SSQC +float trace_endcontentsf; /* ssqc global */ +float trace_surfaceflagsf; /* ssqc global */ +#endif +#if defined(CSQC) || defined(SSQC) +string trace_dphittexturename; /* ssqc global */ +#endif +#ifdef SSQC +float trace_dpstartcontents; /* ssqc global */ +float trace_dphitcontents; /* ssqc global */ +float trace_dphitq3surfaceflags; /* ssqc global */ +#endif +#ifdef CSQC +float(vector angles, float isdelta) CSQC_Parse_SetAngles; +void(float playernum) CSQC_PlayerInfoChanged; +void() CSQC_ServerInfoChanged; +DEP("use CSQC_Event_Sound") float(float channel, string soundname, vector pos, float vol, float attenuation, float flags) CSQC_ServerSound; +void(int entidx, string newentdata) CSQC_MapEntityEdited; +float clframetime; +float servertime; +float serverprevtime; +float serverdeltatime; +DEP("Does not support mod-specific contents.") float trace_dpstartcontents; +DEP("Does not support mod-specific contents.") float trace_dphitcontents; +DEP("Does not support mod-specific surface flags") float trace_dphitq3surfaceflags; +DEP("Does not support all mod-specific surface flags.") float trace_surfaceflagsf; +DEP("Does not support all mod-specific contents.") float trace_endcontentsf; +float intermission_time; +vector pmove_mins; +vector pmove_maxs; +float pmove_jump_held; +float pmove_waterjumptime; +float input_clienttime; +float autocvar_vid_conwidth; +float autocvar_vid_conheight; +#endif +const float TRUE = 1; +const float FALSE = 0; /* File not found... */ +const float M_PI = 3.14159; /* Mathematica Pi constant. */ +#if defined(CSQC) || defined(SSQC) +const float MOVETYPE_NONE = 0; +const float MOVETYPE_WALK = 3; +const float MOVETYPE_STEP = 4; +const float MOVETYPE_FLY = 5; +const float MOVETYPE_TOSS = 6; +const float MOVETYPE_PUSH = 7; +const float MOVETYPE_NOCLIP = 8; +const float MOVETYPE_FLYMISSILE = 9; +const float MOVETYPE_BOUNCE = 10; +const float MOVETYPE_BOUNCEMISSILE = 11; +const float MOVETYPE_FOLLOW = 12; +const float MOVETYPE_6DOF = 30; /* A glorified MOVETYPE_FLY. Players using this movetype will get some flightsim-like physics, with fully independant rotations (order-dependant transforms). */ +const float MOVETYPE_WALLWALK = 31; /* Players using this movetype will be able to orient themselves to walls, and then run up them. */ +const float MOVETYPE_PHYSICS = 32; /* Enable the use of ODE physics upon this entity. */ +const float SOLID_NOT = 0; +const float SOLID_TRIGGER = 1; +const float SOLID_BBOX = 2; +const float SOLID_SLIDEBOX = 3; +const float SOLID_BSP = 4; /* Does not collide against other SOLID_BSP entities. Normally paired with MOVETYPE_PUSH. */ +const float SOLID_CORPSE = 5; /* Non-solid to SOLID_SLIDEBOX or other SOLID_CORPSE entities. For hitscan weapons to hit corpses, change the player's .hitcontentsmaski value to include CONTENTBIT_CORPSE, perform the traceline, then revert the player's .solid value. */ +__deprecated("Obsoleted by .skin=CONTENTS_LADDER") const float SOLID_LADDER = 20; /* Obsolete and may be removed at some point. Use skin=CONTENT_LADDER and solid_bsp or solid_trigger instead. */ +const float SOLID_PORTAL = 21; /* CSG subtraction volume combined with entity transformations on impact. */ +const float SOLID_BSPTRIGGER = 22; /* For complex-shaped trigger volumes, instead of being a pure aabb. */ +const float SOLID_PHYSICS_BOX = 32; +const float SOLID_PHYSICS_SPHERE = 33; +const float SOLID_PHYSICS_CAPSULE = 34; +const float SOLID_PHYSICS_TRIMESH = 35; +const float SOLID_PHYSICS_CYLINDER = 36; +const float GEOMTYPE_NONE = -1; +const float GEOMTYPE_SOLID = 0; +const float GEOMTYPE_BOX = 1; +const float GEOMTYPE_SPHERE = 2; +const float GEOMTYPE_CAPSULE = 3; +const float GEOMTYPE_TRIMESH = 4; +const float GEOMTYPE_CYLINDER = 5; +const float GEOMTYPE_CAPSULE_X = 6; +const float GEOMTYPE_CAPSULE_Y = 7; +const float GEOMTYPE_CAPSULE_Z = 8; +const float GEOMTYPE_CYLINDER_X = 9; +const float GEOMTYPE_CYLINDER_Y = 10; +const float GEOMTYPE_CYLINDER_Z = 11; +const float JOINTTYPE_FIXED = -1; +const float JOINTTYPE_POINT = 1; +const float JOINTTYPE_HINGE = 2; +const float JOINTTYPE_SLIDER = 3; +const float JOINTTYPE_UNIVERSAL = 4; +const float JOINTTYPE_HINGE2 = 5; +#endif +#ifdef CSQC +const float GE_MAXENTS = -1; /* Valid for getentity, ignores the entity argument. Returns the maximum number of entities which may be valid, to avoid having to poll 65k when only 100 are used. */ +const float GE_ACTIVE = 0; /* Valid for getentity. Returns whether this entity is known to the client or not. */ +const float GE_ORIGIN = 1; /* Valid for getentity. Returns the interpolated .origin. */ +const float GE_FORWARD = 2; /* Valid for getentity. Returns the interpolated forward vector. */ +const float GE_RIGHT = 3; /* Valid for getentity. Returns the entity's right vector. */ +const float GE_UP = 4; /* Valid for getentity. Returns the entity's up vector. */ +const float GE_SCALE = 5; /* Valid for getentity. Returns the entity .scale. */ +const float GE_ORIGINANDVECTORS = 6; /* Valid for getentity. Returns interpolated .origin, but also sets v_forward, v_right, and v_up accordingly. Use vectoangles(v_forward,v_up) to determine the angles. */ +const float GE_ALPHA = 7; /* Valid for getentity. Returns the entity alpha. */ +const float GE_COLORMOD = 8; /* Valid for getentity. Returns the colormod vector. */ +const float GE_PANTSCOLOR = 9; /* Valid for getentity. Returns the entity's lower color (from .colormap), as a palette range value. */ +const float GE_SHIRTCOLOR = 10; /* Valid for getentity. Returns the entity's lower color (from .colormap), as a palette range value. */ +const float GE_SKIN = 11; /* Valid for getentity. Returns the entity's .skin index. */ +const float GE_MINS = 12; /* Valid for getentity. Guesses the entity's .min vector. */ +const float GE_MAXS = 13; /* Valid for getentity. Guesses the entity's .max vector. */ +const float GE_ABSMIN = 14; /* Valid for getentity. Guesses the entity's .absmin vector. */ +const float GE_ABSMAX = 15; /* Valid for getentity. Guesses the entity's .absmax vector. */ +const float GE_MODELINDEX = 200; /* Valid for getentity. Guesses the entity's .modelindex float. */ +const float GE_MODELINDEX2 = 201; /* Valid for getentity. Guesses the entity's .vw_index float. */ +const float GE_EFFECTS = 202; /* Valid for getentity. Guesses the entity's .effects float. */ +const float GE_FRAME = 203; /* Valid for getentity. Guesses the entity's .frame float. */ +const float GE_ANGLES = 204; /* Valid for getentity. Guesses the entity's .angles vector. */ +const float GE_FATNESS = 205; /* Valid for getentity. Guesses the entity's .fatness float. */ +const float GE_DRAWFLAGS = 206; /* Valid for getentity. Guesses the entity's .drawflags float. */ +const float GE_ABSLIGHT = 207; /* Valid for getentity. Guesses the entity's .abslight float. */ +const float GE_GLOWMOD = 208; /* Valid for getentity. Guesses the entity's .glowmod vector. */ +const float GE_GLOWSIZE = 209; /* Valid for getentity. Guesses the entity's .glowsize float. */ +const float GE_GLOWCOLOUR = 210; /* Valid for getentity. Guesses the entity's .glowcolor float. */ +const float GE_RTSTYLE = 211; /* Valid for getentity. Guesses the entity's .style float. */ +const float GE_RTPFLAGS = 212; /* Valid for getentity. Guesses the entity's .pflags float. */ +const float GE_RTCOLOUR = 213; /* Valid for getentity. Guesses the entity's .color vector. */ +const float GE_RTRADIUS = 214; /* Valid for getentity. Guesses the entity's .light_lev float. */ +const float GE_TAGENTITY = 215; /* Valid for getentity. Guesses the entity's .tag_entity float. */ +const float GE_TAGINDEX = 216; /* Valid for getentity. Guesses the entity's .tag_index float. */ +const float GE_GRAVITYDIR = 217; /* Valid for getentity. Guesses the entity's .gravitydir vector. */ +const float GE_TRAILEFFECTNUM = 218; /* Valid for getentity. Guesses the entity's .traileffectnum float. */ +#endif +#ifdef SSQC +const float DAMAGE_NO = 0; +const float DAMAGE_YES = 1; +const float DAMAGE_AIM = 2; +#endif +#if defined(CSQC) || defined(SSQC) +const float CONTENT_EMPTY = -1; +const float CONTENT_SOLID = -2; +const float CONTENT_WATER = -3; +const float CONTENT_SLIME = -4; +const float CONTENT_LAVA = -5; +const float CONTENT_SKY = -6; +const float CONTENT_LADDER = -16; /* If this value is assigned to a solid_bsp's .skin field, the entity will become a ladder volume. */ +const int CONTENTBIT_NONE = 0x00000000i; +const int CONTENTBIT_SOLID = 0x00000001i; +const int CONTENTBIT_LAVA = 0x00000008i; +const int CONTENTBIT_SLIME = 0x00000010i; +const int CONTENTBIT_WATER = 0x00000020i; +const int CONTENTBIT_FTELADDER = 0x00004000i; /* Content bit used for .skin=CONTENT_LADDER entities. */ +const int CONTENTBIT_PLAYERCLIP = 0x00010000i; +const int CONTENTBIT_MONSTERCLIP = 0x00020000i; +const int CONTENTBIT_BODY = 0x02000000i; /* Content bit that indicates collisions against SOLID_BBOX/SOLID_SLIDEBOX entities. */ +const int CONTENTBIT_CORPSE = 0x04000000i; /* Content bit that indicates collisions against SOLID_CORPSE entities. */ +const int CONTENTBIT_Q2LADDER = 0x20000000i; /* Content bit specific to q2bsp (conflicts with q3bsp contents so use with caution). */ +const int CONTENTBIT_SKY = 0x80000000i; /* Content bit somewhat specific to q1bsp (aliases to NODROP in q3bsp), but you should probably check surfaceflags&SURF_SKY as well for q2+q3bsp too. */ +const int CONTENTBITS_POINTSOLID = CONTENTBIT_SOLID|0x00000002i|CONTENTBIT_BODY; /* Bits that traceline would normally consider solid */ +const int CONTENTBITS_BOXSOLID = CONTENTBIT_SOLID|0x00000002i|CONTENTBIT_BODY|CONTENTBIT_PLAYERCLIP; /* Bits that tracebox would normally consider solid */ +const int CONTENTBITS_FLUID = CONTENTBIT_WATER|CONTENTBIT_SLIME|CONTENTBIT_LAVA|CONTENTBIT_SKY; +const float SPA_POSITION = 0; /* These SPA_* constants are to specify which attribute is returned by the getsurfacepointattribute builtin */ +const float SPA_S_AXIS = 1; +const float SPA_T_AXIS = 2; +const float SPA_R_AXIS = 3; /* aka: SPA_NORMAL */ +const float SPA_TEXCOORDS0 = 4; +const float SPA_LIGHTMAP0_TEXCOORDS = 5; +const float SPA_LIGHTMAP0_COLOR = 6; +const float CHAN_AUTO = 0; /* The automatic channel, play as many sounds on this channel as you want, and they'll all play, however the other channels will replace each other. */ +const float CHAN_WEAPON = 1; +const float CHAN_VOICE = 2; +const float CHAN_ITEM = 3; +const float CHAN_BODY = 4; +#endif +#if defined(QWSSQC) +const float CHANF_RELIABLE = 8; /* Only valid if the flags argument is not specified. The sound will be sent reliably, which is important if it is intended to replace looping sounds on doors etc. */ +#endif +#ifdef SSQC +const float SOUNDFLAG_RELIABLE = 1; /* The sound will be sent reliably, and without regard to phs. */ +#endif +#ifdef CSQC +const float SOUNDFLAG_ABSVOLUME = 16; /* The sample's volume is not scaled by the volume cvar. Use with caution */ +#endif +#if defined(CSQC) || defined(SSQC) +const float SOUNDFLAG_FORCELOOP = 2; /* The sound will restart once it reaches the end of the sample. */ +const float SOUNDFLAG_NOSPACIALISE = 4; /* The different audio channels are played at the same volume regardless of which way the player is facing, without needing to use 0 attenuation. */ +const float SOUNDFLAG_NOREVERB = 32; /* Disables the use of underwater/reverb effects on this sound effect. */ +const float SOUNDFLAG_FOLLOW = 64; /* The sound's origin will updated to follow the emitting entity. */ +const float SOUNDFLAG_NOREPLACE = 128; /* Sounds started with this flag will be ignored when there's already a sound playing on that same ent-channel. */ +#endif +#ifdef SSQC +const float SOUNDFLAG_UNICAST = 256; /* The sound will be sent only by the player specified by msg_entity. Spectators and related splitscreen players will also hear the sound. */ +const float SOUNDFLAG_SENDVELOCITY = 512; /* The entity's current velocity will be sent to the client, only useful if doppler is enabled. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float ATTN_NONE = 0; /* Sounds with this attenuation can be heard throughout the map */ +const float ATTN_NORM = 1; /* Standard attenuation */ +const float ATTN_IDLE = 2; /* Extra attenuation so that sounds don't travel too far. */ +const float ATTN_STATIC = 3; /* Even more attenuation to avoid torches drowing out everything else throughout the map. */ +#endif +#ifdef SSQC +const float SVC_CGAMEPACKET = 83; /* Direct ssqc->csqc message. Must only be multicast. The data triggers a CSQC_Parse_Event call in the csqc for the csqc to read the contents. The server *may* insert length information for clients connected via proxies which are not able to cope with custom csqc payloads. This should only ever be used in conjunction with the MSG_MULTICAST destination. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float TE_SPIKE = 0; +const float TE_SUPERSPIKE = 1; +const float TE_GUNSHOT = 2; +const float TE_EXPLOSION = 3; +const float TE_TAREXPLOSION = 4; +const float TE_LIGHTNING1 = 5; +const float TE_LIGHTNING2 = 6; +const float TE_WIZSPIKE = 7; +const float TE_KNIGHTSPIKE = 8; +const float TE_LIGHTNING3 = 9; +const float TE_LAVASPLASH = 10; +const float TE_TELEPORT = 11; +#endif +#if defined(CSQC) || defined(QWSSQC) +const float TE_BLOOD = 12; +#endif +#if defined(NQSSQC) +const float TE_EXPLOSION2 = 12; +#endif +#if defined(CSQC) || defined(QWSSQC) +const float TE_LIGHTNINGBLOOD = 13; +#endif +#if defined(NQSSQC) +const float TE_BEAM = 13; +const float MSG_BROADCAST = 0; /* The byte(s) will be unreliably sent to all players. MSG_ constants are valid arguments to the Write* builtin family. */ +const float MSG_ONE = 1; /* The byte(s) will be reliably sent to the player specified in the msg_entity global. WARNING: in quakeworld servers without network preparsing enabled, this can result in illegible server messages (due to individual reliable messages being split between multiple backbuffers/packets). NQ has larger reliable buffers which avoids this issue, but still kicks the client. */ +const float MSG_ALL = 2; /* The byte(s) will be reliably sent to all players. */ +#endif +#if defined(QWSSQC) +__deprecated("Use MSG_MULTICAST+multicast(MULTICAST_*)") const float MSG_BROADCAST; /* The byte(s) will be unreliably sent to all players. MSG_ constants are valid arguments to the Write* builtin family. */ +__deprecated("Use MSG_MULTICAST+multicast(MULTICAST_ONE_R)") const float MSG_ONE = 1; /* The byte(s) will be reliably sent to the player specified in the msg_entity global. WARNING: in quakeworld servers without network preparsing enabled, this can result in illegible server messages (due to individual reliable messages being split between multiple backbuffers/packets). NQ has larger reliable buffers which avoids this issue, but still kicks the client. */ +__deprecated("Use MSG_MULTICAST+multicast(MULTICAST_ALL)") const float MSG_ALL = 2; /* The byte(s) will be reliably sent to all players. */ +#endif +#ifdef SSQC +const float MSG_INIT = 3; /* The byte(s) will be written into the signon buffer. Clients will see these messages when they connect later. This buffer is only flushed on map changes, so spamming it _WILL_ result in overflows. */ +const float MSG_MULTICAST = 4; /* The byte(s) will be written into the multicast buffer for more selective sending. Messages sent this way will never be split across packets, and using this for csqc-only messages will not break protocol translation. */ +const float MSG_ENTITY = 5; /* The byte(s) will be written into the entity buffer. This is a special value used only inside 'SendEntity' functions. */ +const float MULTICAST_ALL = 0; /* The multicast message is unreliably sent to all players. MULTICAST_ constants are valid arguments for the multicast builtin, which ignores the specified origin when given this constant. */ +const float MULTICAST_PHS = 1; /* The multicast message is unreliably sent to only players that can potentially hear the specified origin. Its quite loose. */ +const float MULTICAST_PVS = 2; /* The multicast message is unreliably sent to only players that can potentially see the specified origin. */ +const float MULTICAST_ONE = 6; /* The multicast message is unreliably sent to the player (AND ALL TRACKING SPECTATORS) specified in the msg_entity global. The specified origin is ignored. */ +const float MULTICAST_ONE_NOSPECS = 9; /* The multicast message is unreliably sent to the player specified in the msg_entity global. The specified origin is ignored. */ +const float MULTICAST_ALL_R = 3; /* The multicast message is reliably sent to all players. The specified origin is ignored. */ +const float MULTICAST_PHS_R = 4; /* The multicast message is reliably sent to only players that can potentially hear the specified origin. Players might still not receive it if they are out of range. */ +const float MULTICAST_PVS_R = 5; /* The multicast message is reliably sent to only players that can potentially see the specified origin. Players might still not receive it if they cannot see the event. */ +const float MULTICAST_ONE_R = 7; /* The multicast message is reliably sent to the player (AND ALL TRACKING SPECTATORS) specified in the msg_entity global. The specified origin is ignored */ +const float MULTICAST_ONE_R_NOSPECS = 10; /* The multicast message is reliably sent to the player specified in the msg_entity global. The specified origin is ignored */ +#endif +#if defined(QWSSQC) +const float PRINT_LOW = 0; +const float PRINT_MEDIUM = 1; +const float PRINT_HIGH = 2; +const float PRINT_CHAT = 3; +#endif +#ifdef SSQC +const float PVSF_NORMALPVS = 0; /* Filter first by PVS, then filter this entity using tracelines if sv_cullentities is enabled. */ +const float PVSF_NOTRACECHECK = 1; /* Filter strictly by PVS. */ +const float PVSF_USEPHS = 2; /* Send if we're close enough to be able to hear this entity. */ +const float PVSF_IGNOREPVS = 3; /* Ignores pvs. This entity is visible whereever you are on the map. Updates will be sent regardless of pvs or phs */ +const float PVSF_NOREMOVE = 128; /* Once visible to a client, this entity will remain visible. This can be useful for csqc and corpses. While this flag is set, no CSQC_Remove events will be sent for the entity, but this does NOT mean that it will still receive further updates while outside of the pvs. */ +const string INFOKEY_P_IP = "ip"; /* The apparent ip address of the client. This may be a proxy's ip address. */ +const string INFOKEY_P_REALIP = "realip"; /* If sv_getrealip is set, this gives the ip as determine using that algorithm. */ +const string INFOKEY_P_CSQCACTIVE = "csqcactive"; /* Client has csqc enabled. CSQC ents etc will be sent to this player. */ +const string INFOKEY_P_SVPING = "svping"; +const string INFOKEY_P_GUID = "guid"; /* Some hash string which should be reasonably unique to this player's quake installation. */ +const string INFOKEY_P_CHALLENGE = "challenge"; +const string INFOKEY_P_USERID = "*userid"; +const string INFOKEY_P_DOWNLOADPCT = "download"; /* The client's download percentage for the current file. Additional files are not known. */ +const string INFOKEY_P_TRUSTLEVEL = "trustlevel"; +const string INFOKEY_P_PROTOCOL = "protocol"; /* The network protocol the client is using to connect to the server. */ +const string INFOKEY_P_VIP = "*VIP"; /* 1 if the player has the VIP 'penalty'. */ +const string INFOKEY_P_ISMUTED = "*ismuted"; /* 1 if the player has the 'mute' penalty and is not allowed to use the say/say_team commands. */ +const string INFOKEY_P_ISDEAF = "*isdeaf"; /* 1 if the player has the 'deaf' penalty and cannot see other people's say/say_team commands. */ +const string INFOKEY_P_ISCRIPPLED = "*ismuted"; /* 1 if the player has the cripple penalty, and their movement values are ignored (.movement is locked to 0). */ +const string INFOKEY_P_ISCUFFED = "*ismuted"; /* 1 if the player has the cuff penalty, and is unable to attack or use impulses(.button0 and .impulse fields are locked to 0). */ +const string INFOKEY_P_ISLAGGED = "*ismuted"; /* 1 if the player has the fakelag penalty and has an extra 200ms of lag. */ +#endif +#if defined(CSQC) || defined(SSQC) +const string INFOKEY_P_PING = "ping"; /* The player's ping time, in milliseconds. */ +const string INFOKEY_P_NAME = "name"; /* The player's name. */ +const string INFOKEY_P_SPECTATOR = "*spectator"; /* Whether the player is a spectator or not. */ +const string INFOKEY_P_TOPCOLOR = "topcolor"; /* The player's upper/shirt colour (palette index). */ +const string INFOKEY_P_BOTTOMCOLOR = "bottomcolor"; /* The player's lower/pants/trouser colour (palette index). */ +#endif +#ifdef CSQC +const string INFOKEY_P_TOPCOLOR_RGB = "topcolor_rgb"; /* The player's upper/shirt colour as an rgb value in a format usable with stov. */ +const string INFOKEY_P_BOTTOMCOLOR_RGB = "bottomcolor_rgb"; /* The player's lower/pants/trouser colour as an rgb value in a format usable with stov. */ +const string INFOKEY_P_MUTED = "ignored"; /* 0: we can see the result of the player's say/say_team commands. 1: we see no say/say_team messages from this player. Use the ignore command to toggle this value. */ +const string INFOKEY_P_VOIP_MUTED = "vignored"; /* 0: we can hear this player when they speak (assuming voip is generally enabled). 1: we ignore everything this player says. Use cl_voip_mute to change the values. */ +const string INFOKEY_P_ENTERTIME = "entertime"; /* Reads the timestamp at which the player entered the game, in terms of csqc's time global. */ +const string INFOKEY_P_FRAGS = "frags"; /* Reads a player's frag count. */ +const string INFOKEY_P_PACKETLOSS = "pl"; /* Reads a player's packetloss, as a percentage. */ +const string INFOKEY_P_VOIPSPEAKING = "voipspeaking"; /* Boolean value that says whether the given player is currently sending voice information. */ +const string INFOKEY_P_VOIPLOUDNESS = "voiploudness"; /* Only valid for the local player. Gives a value between 0 and 1 to indicate to the user how loud their mic is. */ +const string SERVERKEY_IP = "ip"; /* The address of the server we connected to. */ +const string SERVERKEY_SERVERNAME = "servername"; /* The hostname that was last passed to the connect command. */ +const string SERVERKEY_CONSTATE = "constate"; /* The current connection state. Will be set to one of: disconnected (menu-only mode), active (gamestate received and loaded), connecting(connecting, downloading, or precaching content, aka: loading screen). */ +const string SERVERKEY_TRANSFERRING = "transferring"; /* Set to the hostname of the server that we are attempting to connect or transfer to. */ +const string SERVERKEY_LOADSTATE = "loadstate"; /* loadstage, loading image name, current step, max steps +Stages are: 1=connecting, 2=serverside, 3=clientside +Key will be empty if we are not loading. */ +const string SERVERKEY_PAUSESTATE = "pausestate"; /* 1 if the server claimed to be paused. 0 otherwise */ +const string SERVERKEY_DLSTATE = "dlstate"; /* The progress of any current downloads. Empty string if no download is active, otherwise a tokenizable string containing this info: +files-remaining, total-size, unknown-sizes-flag, file-localname, file-remotename, file-percent, file-rate, file-received-bytes, file-total-bytes +If the current file info is omitted, then we are waiting for a download to start. */ +const string SERVERKEY_PROTOCOL = "protocol"; /* The protocol we are connected to the server with. */ +const string SERVERKEY_MAXPLAYERS = "maxplayers"; /* The number of player/spectator slots allocated on the server. */ +#endif +#ifdef SSQC +const float STUFFCMD_IGNOREINDEMO = 1; /* This stuffcmd will NOT be written to mvds/qtv. */ +const float STUFFCMD_DEMOONLY = 2; /* This stuffcmd will ONLY be written into mvds/qtv streams. */ +const float STUFFCMD_BROADCAST = 4; /* The stuffcmd will be broadcast server-wide (according to the mvd filters). */ +const float STUFFCMD_UNRELIABLE = 8; /* The stuffcmd might not arrive. It might also get there faster than ones sent over the reliable channel. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float FL_FLY = 1; +const float FL_SWIM = 2; +const float FL_CLIENT = 8; +const float FL_INWATER = 16; +const float FL_MONSTER = 32; +#endif +#ifdef SSQC +const float FL_GODMODE = 64; +const float FL_NOTARGET = 128; +#endif +#if defined(CSQC) || defined(SSQC) +const float FL_ITEM = 256; +const float FL_ONGROUND = 512; +const float FL_PARTIALGROUND = 1024; +const float FL_WATERJUMP = 2048; +#endif +#if defined(NQSSQC) +const float FL_JUMPRELEASED = 4096; +#endif +#if defined(CSQC) || defined(SSQC) +const float FL_FINDABLE_NONSOLID = 16384; /* Allows this entity to be found with findradius */ +#endif +#ifdef SSQC +const float FL_LAGGEDMOVE = 65536; /* Enables anti-lag on rockets etc. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float MOVE_NORMAL = 0; +const float MOVE_NOMONSTERS = 1; /* The trace will ignore all non-solid_bsp entities. */ +const float MOVE_MISSILE = 2; /* The trace will use a bbox size of +/- 15 against entities with FL_MONSTER set. */ +const float MOVE_WORLDONLY = 3; /* The trace will ignore everything but the worldmodel. This is useful for to prevent the q3bsp pvs+culling issues that come with spectator modes leaving the world . */ +const float MOVE_HITMODEL = 4; /* Traces will impact the actual mesh of the model instead of merely their bounding box. Should generally only be used for tracelines. Note that this flag is unreliable as an object can animate through projectiles. The bounding box MUST be set to completely encompass the entity or those extra areas will be non-solid (leaving a hole for things to go through). */ +const float MOVE_TRIGGERS = 16; /* This trace type will impact only triggers. It will ignore non-solid entities. */ +const float MOVE_EVERYTHING = 32; /* This type of trace will hit solids and triggers alike. Even non-solid entities. */ +#endif +#ifdef SSQC +const float MOVE_LAGGED = 64; /* Will use antilag based upon the player's latency. Traces will be performed against old positions for entities instead of their current origin. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float MOVE_ENTCHAIN = 128; /* Returns a list of entities impacted via the trace_ent.chain field */ +const float MOVE_OTHERONLY = 256; /* Traces that use this trace type will collide against *only* the entity specified via the 'other' global, and will ignore all owner/solid_not/dimension etc rules, they will still adhere to contents and bsp/bbox rules though. */ +#endif +const float CVAR_TYPEFLAG_EXISTS = 1; /* Cvar name actually exists. */ +const float CVAR_TYPEFLAG_SAVED = 2; /* Cvar is flaged for archival (might need cfg_save to actually save). */ +const float CVAR_TYPEFLAG_PRIVATE = 4; /* QC is not allowed to read. */ +const float CVAR_TYPEFLAG_ENGINE = 8; /* Cvar was created by the engine itself (not user/mod created). */ +const float CVAR_TYPEFLAG_HASDESCRIPTION = 16; /* cvar_description will return something (hopefully) useful. */ +const float CVAR_TYPEFLAG_READONLY = 32; /* cvar may not be changed by qc. */ +const float RESTYPE_MODEL = 0; /* RESTYPE_* constants are used as arguments with the resourcestatus builtin. */ +const float RESTYPE_SOUND = 1; /* precache_sound */ +const float RESTYPE_PARTICLE = 2; /* particleeffectnum */ +#if defined(CSQC) || defined(MENU) +const float RESTYPE_PIC = 3; /* precache_pic. Status results are an amalgomation of the textures used by the named shader. */ +const float RESTYPE_SKIN = 4; /* setcustomskin */ +const float RESTYPE_TEXTURE = 5; /* Individual textures within shaders. These are not directly usable, but may be named as part of a skin file, or a shader. */ +#endif +const float RESSTATE_NOTKNOWN = 0; /* RESSTATE_* constants are return values from the resourcestatus builtin. The engine doesn't know about the resource if it is in this state. This means you will need to precache it. Attempting to use it anyway may result in warnings, errors, or silently succeed, depending on engine version and resource type. */ +const float RESSTATE_NOTLOADED = 1; /* The resource was precached, but has been flushed and there has not been an attempt to reload it. If you use the resource normally, chances are it'll be loaded but at the cost of a stall. */ +const float RESSTATE_LOADING = 2; /* Resources in this this state are queued for loading, and will be loaded at the engine's convienience. If you attempt to query the resource now, the engine will stall until the result is available. sounds in this state may be delayed, while models/pics/shaders may be invisible. */ +const float RESSTATE_FAILED = 3; /* Resources in this state are unusable/could not be loaded. You will get placeholders or dummy results. Queries will not stall the engine. The engine may display placeholder content. */ +const float RESSTATE_LOADED = 4; /* Resources in this state are finally usable, everything will work okay. Hurrah. Queries will not stall the engine. */ +#if defined(CSQC) || defined(SSQC) +const float EF_BRIGHTFIELD = 1; +#endif +#if defined(CSQC) || defined(NQSSQC) +const float EF_MUZZLEFLASH = 2; +#endif +#if defined(CSQC) || defined(SSQC) +const float EF_BRIGHTLIGHT = 4; +const float EF_DIMLIGHT = 8; +#endif +#if defined(QWSSQC) +const float EF_FLAG1 = 16; +const float EF_FLAG2 = 32; +#endif +#if defined(CSQC) || defined(NQSSQC) +const float EF_NODRAW = 16; /* Disables drawing of the model. Does NOT work on QW players. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float EF_ADDITIVE = 32; /* The entity will be drawn with an additive blend. This is NOT supported on players in any quakeworld engine. */ +const float EF_BLUE = 64; /* A blue glow */ +const float EF_RED = 128; /* A red glow */ +const float EF_GREEN = 262144; /* A green glow */ +const float EF_FULLBRIGHT = 512; /* This entity will ignore lighting */ +const float EF_NOSHADOW = 4096; /* This entity will not cast shadows */ +const float EF_NODEPTHTEST = 8192; /* This entity will be drawn over the top of other things that are closer. */ +#endif +#ifdef SSQC +const float EF_NOMODELFLAGS = 8388608; /* Surpresses the normal flags specified in the model. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float MF_ROCKET = 1; +const float MF_GRENADE = 2; +const float MF_GIB = 4; /* Regular blood trail */ +const float MF_ROTATE = 8; +const float MF_TRACER = 16; /* AKA: green scrag trail */ +const float MF_ZOMGIB = 32; /* Dark blood trail */ +const float MF_TRACER2 = 64; /* AKA: hellknight projectile trail */ +const float MF_TRACER3 = 128; /* AKA: purple vore trail */ +#endif +#ifdef SSQC +DEP_CSQC const float SL_ORG_TL = 20; /* Used with showpic etc, specifies that the x+y values are relative to the top-left of the screen */ +DEP_CSQC const float SL_ORG_TR = 21; +DEP_CSQC const float SL_ORG_BL = 22; +DEP_CSQC const float SL_ORG_BR = 23; +DEP_CSQC const float SL_ORG_MM = 24; +DEP_CSQC const float SL_ORG_TM = 25; +DEP_CSQC const float SL_ORG_BM = 26; +DEP_CSQC const float SL_ORG_ML = 27; +DEP_CSQC const float SL_ORG_MR = 28; +#endif +#if defined(CSQC) || defined(SSQC) +const float PFLAGS_NOSHADOW = 1; /* Associated RT lights attached will not cast shadows, making them significantly faster to draw. */ +const float PFLAGS_CORONA = 2; /* Enables support of coronas on the associated rtlights. */ +#endif +#ifdef SSQC +const float PFLAGS_FULLDYNAMIC = 128; /* When set in self.pflags, enables fully-customised dynamic lights. Custom rtlight information is not otherwise used. */ +#endif +const float EV_STRING = 1; +const float EV_FLOAT = 2; +const float EV_VECTOR = 3; +const float EV_ENTITY = 4; +const float EV_FIELD = 5; +const float EV_FUNCTION = 6; +const float EV_POINTER = 7; +const float EV_INTEGER = 8; +const float EV_UINT = 9; +const float EV_INT64 = 10; +const float EV_UINT64 = 11; +const float EV_DOUBLE = 12; +hashtable gamestate; /* Special hash table index for hash_add and hash_get. Entries in this table will persist over map changes (and doesn't need to be created/deleted). */ +const float HASH_REPLACE = 256; /* Used with hash_add. Attempts to remove the old value instead of adding two values for a single key. */ +const float HASH_ADD = 512; /* Used with hash_add. The new entry will be inserted in addition to the existing entry. */ +#ifdef CSQC +const float STAT_HEALTH = 0; /* Player's health. */ +const float STAT_WEAPONMODELI = 2; /* This is the modelindex of the current viewmodel (renamed from the original name 'STAT_WEAPON' due to confusions). */ +const float STAT_AMMO = 3; /* player.currentammo */ +const float STAT_ARMOR = 4; +const float STAT_WEAPONFRAME = 5; +const float STAT_SHELLS = 6; +const float STAT_NAILS = 7; +const float STAT_ROCKETS = 8; +const float STAT_CELLS = 9; +const float STAT_ACTIVEWEAPON = 10; /* player.weapon */ +const float STAT_TOTALSECRETS = 11; +const float STAT_TOTALMONSTERS = 12; +const float STAT_FOUNDSECRETS = 13; +const float STAT_KILLEDMONSTERS = 14; +const float STAT_ITEMS = 15; /* self.items | (self.items2<<23). In order to decode this stat properly, you need to use getstatbits(STAT_ITEMS,0,23) to read self.items, and getstatbits(STAT_ITEMS,23,11) to read self.items2 or getstatbits(STAT_ITEMS,28,4) to read the visible part of serverflags, whichever is applicable. */ +const float STAT_VIEWHEIGHT = 16; /* player.view_ofs_z */ +const float STAT_VIEW2 = 20; /* This stat contains the number of the entity in the server's .view2 field. */ +const float STAT_VIEWZOOM = 21; /* Scales fov and sensitiity. Part of DP_VIEWZOOM. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float STAT_USER = 32; /* Custom user stats start here (lower values are reserved for engine use). */ +#endif +#if defined(CSQC) || defined(MENU) +const float PRECACHE_PIC_FROMWAD = 1; /* Attempt to load it from the legacy gfx.wad file (usually its better to just use a gfx/ prefix instead). */ +const float PRECACHE_PIC_NOCLAMP = 4; /* Texture coords for the pic will not be clamped nor padded nor atlased. */ +const float PRECACHE_PIC_DOWNLOAD = 256; /* If no image could be loaded then attempt to download one from the server. This flag can cause the function to block until completion. (Slow!) */ +const float PRECACHE_PIC_TEST = 512; /* The precache will block until the image is fully loaded, returning a null string on failure. (Slow!) */ +const float VF_MIN = 1; /* The top-left of the 3d viewport in screenspace. The VF_ values are used via the setviewprop/getviewprop builtins. */ +const float VF_MIN_X = 2; +const float VF_MIN_Y = 3; +const float VF_SIZE = 4; /* The width+height of the 3d viewport in screenspace. */ +const float VF_SIZE_X = 5; +const float VF_SIZE_Y = 6; +const float VF_VIEWPORT = 7; /* vector+vector. Two argument shortcut for VF_MIN and VF_SIZE */ +const float VF_FOV = 8; /* sets both fovx and fovy. consider using afov instead. */ +const float VF_FOV_X = 9; /* horizontal field of view. does not consider aspect at all. */ +const float VF_FOV_Y = 10; /* vertical field of view. does not consider aspect at all. */ +const float VF_ORIGIN = 11; /* The origin of the view. Not of the player. */ +const float VF_ORIGIN_X = 12; +const float VF_ORIGIN_Y = 13; +const float VF_ORIGIN_Z = 14; +const float VF_ANGLES = 15; /* The angles the view will be drawn at. Not the angle the client reports to the server. */ +const float VF_ANGLES_X = 16; +const float VF_ANGLES_Y = 17; +const float VF_ANGLES_Z = 18; +#endif +#ifdef CSQC +const float VF_DRAWWORLD = 19; /* boolean. If set to 1, the engine will draw the world and static/persistant rtlights. If 0, the world will be skipped and everything will be fullbright. */ +const float VF_DRAWENGINESBAR = 20; /* boolean. If set to 1, the sbar will be drawn, and viewsize will be honoured automatically. */ +const float VF_DRAWCROSSHAIR = 21; /* boolean. If set to 1, the engine will draw its default crosshair. */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_MINDIST = 23; /* The distance of the near clip plane from the view position. Should generally not be <=0, as this would introduce NANs. */ +const float VF_MAXDIST = 24; /* The distance of the far clip plane from the view position. If 0, will be considered infinite. */ +#endif +#ifdef CSQC +const float VF_CL_VIEWANGLES = 33; +const float VF_CL_VIEWANGLES_X = 34; +const float VF_CL_VIEWANGLES_Y = 35; +const float VF_CL_VIEWANGLES_Z = 36; +#endif +#if defined(CSQC) || defined(MENU) +const float VF_PERSPECTIVE = 200; /* 1: regular rendering. Fov specifies the angle. 0: isometric-style. Fov specifies the number of Quake Units each side of the viewport, and mindist restrictions are removed, pvs culling should be disabled. */ +#endif +#ifdef CSQC +#define VF_LPLAYER VF_ACTIVESEAT +const float VF_ACTIVESEAT = 202; /* The 'seat' number, used when running splitscreen. */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_AFOV = 203; /* Aproximate fov. Matches the 'fov' cvar. The engine handles the aspect ratio for you. */ +const float VF_SCREENVSIZE = 204; /* Provides a reliable way to retrieve the current virtual screen size (even if the screen is automatically scaled to retain aspect). */ +const float VF_SCREENPSIZE = 205; /* Provides a reliable way to retrieve the current physical screen size (cvars need vid_restart for them to take effect). */ +#endif +#ifdef CSQC +const float VF_VIEWENTITY = 206; /* Changes the RF_EXTERNALMODEL flag on entities to match the new selection, and removes entities flaged with RF_VIEWENTITY. Requires cunning use of .entnum and typically requires calling addentities(MASK_VIEWMODEL) too. */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_RT_DESTCOLOUR = 212; /* The texture name to write colour info into, this includes both 3d and 2d drawing. +Additional arguments are: format (IMGFMT_*), sizexy. +Written to by both 3d and 2d rendering. +Note that any rendertarget textures may be destroyed on video mode changes or so. Shaders can name render targets by prefixing texture names with '$rt:', or $sourcecolour. */ +const float VF_RT_DESTCOLOUR1 = 213; /* Like VF_RT_DESTCOLOUR, for multiple render targets. */ +const float VF_RT_DESTCOLOUR2 = 214; /* Like VF_RT_DESTCOLOUR, for multiple render targets. */ +const float VF_RT_DESTCOLOUR3 = 215; /* Like VF_RT_DESTCOLOUR, for multiple render targets. */ +const float VF_RT_SOURCECOLOUR = 209; /* The texture name to use with shaders that specify a $sourcecolour map. */ +const float VF_RT_DEPTH = 210; /* The texture name to use as a depth buffer. Also used for shaders that specify $sourcedepth. 1-based. Additional arguments are: format (IMGFMT_D*), sizexy. */ +const float VF_RT_RIPPLE = 211; /* The texture name to use as a ripplemap (target for shaders with 'sort ripple'). Also used for shaders that specify $ripplemap. 1-based. Additional arguments are: format, sizexy. */ +const float VF_ENVMAP = 220; /* The cubemap name to use as a fallback for $reflectcube, if a shader was unable to load one. Note that this doesn't automatically change shader permutations or anything. */ +const float VF_USERDATA = 221; /* Pointer (and byte size) to an array of vec4s. This data is then globally visible to all glsl via the w_user uniform. */ +#endif +#ifdef CSQC +const float VF_SKYROOM_CAMERA = 222; /* Controls the camera position of the skyroom (which will be drawn underneath transparent sky surfaces). This should move slightly with the real camera, but not so much that the skycamera enters walls. Requires a skyshader with a blend mode on the first pass (or no passes). */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_PROJECTIONOFFSET = 224; /* vec2 horizontal+vertical offset for the projection matrix, for weird off-centre rendering. */ +const float DRAWFLAG_NORMAL = 0; /* Args for drawpic/drawfill/beginpolygon. Not to be confused with the hexen2-compatibility feature. */ +const float DRAWFLAG_ADD = 1; /* Forces additive blending, overriding any shader settings. */ +const float DRAWFLAG_MODULATE = 2; /* Forces alpha blending, overriding any shader settings. */ +const float DRAWFLAG_2D = 4; /* For use with beginpolygon. The polygon will be drawn to the 2d screen, instead of being added to the 3d scene. */ +const float DRAWFLAG_TWOSIDED = 1024; /* For use with beginpolygon. The polygon will be two-sided without any backface culling. */ +const float DRAWFLAG_LINES = 2048; /* For use with beginpolygon. The surface verticies should be interpreted as a line loop, instead of a triangle fan. */ +const float IMGFMT_R8G8B8A8 = 1; /* Typical 32bit rgba pixel format. */ +const float IMGFMT_R16G16B16A16F = 2; /* Half-Float pixel format. Requires gl3 support. */ +const float IMGFMT_R32G32B32A32F = 3; /* Regular Float pixel format. Requires gl3 support. */ +const float IMGFMT_D16 = 4; /* 16-bit depth pixel format. Must not be used with VF_RT_DESTCOLOUR*. */ +const float IMGFMT_D24 = 5; /* 24-bit depth pixel format. Must not be used with VF_RT_DESTCOLOUR*. */ +const float IMGFMT_D32 = 6; /* 32-bit depth pixel format. Must not be used with VF_RT_DESTCOLOUR*. */ +const float IMGFMT_R8 = 7; /* Single channel red-only 8bit pixel format. */ +const float IMGFMT_R16F = 8; /* Single channel red-only Half-Float pixel format. Requires gl3 support. */ +const float IMGFMT_R32F = 9; /* Single channel red-only Float pixel format. Requires gl3 support. */ +const float IMGFMT_A2B10G10R10 = 10; /* Packed 32-bit packed 10-bit colour pixel format. Requires gl3 support. */ +const float IMGFMT_R5G6B5 = 11; /* Packed 16-bit colour pixel format. */ +const float IMGFMT_R4G4B4A4 = 12; /* Packed 16-bit colour pixel format, with alpha */ +const float IMGFMT_R8G8 = 13; /* 16-bit two-channel pixel format. */ +const float IMGFMT_R32G32B32F = 14; /* A pixel format that matches QC's vector type. */ +#endif +#ifdef CSQC +const float RF_VIEWMODEL = 1; /* Specifies that the entity is a view model, and that its origin is relative to the current view position. These entities are also subject to viewweapon bob. */ +const float RF_EXTERNALMODEL = 2; /* Specifies that this entity should be displayed in mirrors (and may still cast shadows), but will not otherwise be visible. */ +#endif +#if defined(CSQC) || defined(MENU) +const float RF_DEPTHHACK = 4; /* Hacks the depth values such that the entity uses depth values as if it were closer to the screen. This is useful when combined with viewmodels to avoid weapons poking in to walls. */ +const float RF_ADDITIVE = 8; /* Shaders from this entity will temporarily be hacked to use an additive blend mode instead of their normal blend mode. */ +#endif +#ifdef CSQC +const float RF_USEAXIS = 16; /* The entity will be oriented according to the current v_forward+v_right+v_up vector values instead of the entity's .angles field. */ +const float RF_NOSHADOW = 32; /* This entity will not cast shadows. Often useful on view models. */ +const float RF_FRAMETIMESARESTARTTIMES = 64; /* Specifies that the frame1time, frame2time field are timestamps (denoting the start of the animation) rather than time into the animation. */ +const float RF_FIRSTPERSON = 1024; /* This is basically the opposite of RF_EXTERNALMODEL. Don't draw in third-person or mirrors. */ +#endif +#if defined(CSQC) || defined(MENU) +const float IE_KEYDOWN = 0; /* Specifies that a key was pressed. Second argument is the scan code. Third argument is the unicode (printable) char value. Fourth argument denotes which keyboard(or mouse, if its a mouse 'scan' key) the event came from. Note that some systems may completely separate scan codes and unicode values, with a 0 value for the unspecified argument. */ +const float IE_KEYUP = 1; /* Specifies that a key was released. Arguments are the same as IE_KEYDOWN. On some systems, this may be fired instantly after IE_KEYDOWN was fired. */ +const float IE_MOUSEDELTA = 2; /* Specifies that a mouse was moved (touch screens and tablets typically give IE_MOUSEABS events instead, use in_windowed_mouse 0 to test code to cope with either). Second argument is the X displacement, third argument is the Y displacement. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_MOUSEABS = 3; /* Specifies that a mouse cursor or touch event was moved to a specific location relative to the virtual screen space. Second argument is the new X position, third argument is the new Y position. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_ACCELEROMETER = 4; +const float IE_FOCUS = 5; /* Specifies that input focus was given. parama says mouse focus, paramb says keyboard focus. If either are -1, then it is unchanged. */ +const float IE_JOYAXIS = 6; /* Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller. */ +const float IE_GYROSCOPE = 7; +const float GGDI_GAMEDIR = 0; /* Used with getgamedirinfo to query the mod's public gamedir. There is often other info that cannot be expressed with just a gamedir name, resulting in dupes or other weirdness. */ +const float GGDI_DESCRIPTION = 1; /* The human-readable title of the mod. Empty when no data is known (ie: the gamedir just contains some maps). */ +const float GGDI_OVERRIDES = 2; /* A list of settings overrides. */ +const float GGDI_LOADCOMMAND = 3; /* The console command needed to actually load the mod. */ +const float GGDI_ICON = 4; /* The mod's Icon path, ready for drawpic. */ +const float GGDI_GAMEDIRLIST = 5; /* A semi-colon delimited list of gamedirs that the mod's content can be loaded through. */ +#endif +#ifdef SSQC +const float CLIENTTYPE_DISCONNECTED = 0; /* Return value from clienttype() builtin. This entity is a player slot that is currently empty. */ +const float CLIENTTYPE_REAL = 1; /* This is a real player, and not a bot. */ +const float CLIENTTYPE_BOT = 2; /* This player slot does not correlate to a real player, any messages sent to this client will be ignored. */ +const float CLIENTTYPE_NOTACLIENT = 3; /* This entity is not even a player slot. This is typically an error condition. */ +#endif +const float FILE_READ = 0; /* The file may be read via fgets to read a single line at a time. */ +const float FILE_APPEND = 1; /* Like FILE_WRITE, but writing starts at the end of the file. */ +const float FILE_WRITE = 2; /* fputs will be used to write to the file. */ +const float FILE_READNL = 4; /* Like FILE_READ, except newlines are not special. fgets reads the entire file into a tempstring. */ +const float FILE_MMAP_READ = 5; /* The file will be loaded into memory. fgets returns a pointer to the first byte (and will always return the same value for this file). Cast this to your datatype. */ +const float FILE_MMAP_RW = 6; /* Like FILE_MMAP_READ, except any changes to the data will be written back to disk once the file is closed. */ +#ifdef CSQC +const float MASK_ENGINE = 1; /* Valid as an argument for addentities. If specified, all non-csqc entities will be added to the scene. */ +const float MASK_VIEWMODEL = 2; /* Valid as an argument for addentities. If specified, the regular engine viewmodel will be added to the scene. */ +const float PREDRAW_AUTOADD = 0; /* Valid as a return value from the predraw function. Returning this will cause the engine to automatically invoke addentity(self) for you. */ +const float PREDRAW_NEXT = 1; /* Valid as a return value from the predraw function. Returning this will simply move on to the next entity without the autoadd behaviour, so can be used for particle/invisible/special entites, or entities that were explicitly drawn with addentity. */ +const float LFIELD_ORIGIN = 0; +const float LFIELD_COLOUR = 1; +const float LFIELD_RADIUS = 2; +const float LFIELD_FLAGS = 3; +const float LFIELD_STYLE = 4; +const float LFIELD_ANGLES = 5; +const float LFIELD_FOV = 6; +const float LFIELD_CORONA = 7; +const float LFIELD_CORONASCALE = 8; +const float LFIELD_CUBEMAPNAME = 9; +const float LFIELD_AMBIENTSCALE = 10; +const float LFIELD_DIFFUSESCALE = 11; +const float LFIELD_SPECULARSCALE = 12; +const float LFIELD_ROTATION = 13; +const float LFIELD_DIETIME = 14; +const float LFIELD_RGBDECAY = 15; +const float LFIELD_RADIUSDECAY = 16; +const float LFIELD_STYLESTRING = 17; +const float LFIELD_NEARCLIP = 18; +const float LFIELD_OWNER = 19; +const float LFLAG_NORMALMODE = 1; +const float LFLAG_REALTIMEMODE = 2; +const float LFLAG_LIGHTMAP = 4; +const float LFLAG_FLASHBLEND = 8; +const float LFLAG_NOSHADOWS = 256; +const float LFLAG_SHADOWMAP = 512; +const float LFLAG_CREPUSCULAR = 1024; +const float LFLAG_ORTHOSUN = 2048; +const float TEREDIT_RELOAD = 0; +const float TEREDIT_SAVE = 1; +const float TEREDIT_SETHOLE = 2; +const float TEREDIT_HEIGHT_SET = 3; +const float TEREDIT_HEIGHT_SMOOTH = 4; +const float TEREDIT_HEIGHT_SPREAD = 5; +const float TEREDIT_HEIGHT_RAISE = 6; +const float TEREDIT_HEIGHT_FLATTEN = 18; +const float TEREDIT_HEIGHT_LOWER = 7; +const float TEREDIT_TEX_KILL = 8; +const float TEREDIT_TEX_GET = 9; +const float TEREDIT_TEX_BLEND = 10; +const float TEREDIT_TEX_UNIFY = 11; +const float TEREDIT_TEX_NOISE = 12; +const float TEREDIT_TEX_BLUR = 13; +const float TEREDIT_TEX_REPLACE = 19; +const float TEREDIT_TEX_SETMASK = 25; +const float TEREDIT_WATER_SET = 14; +const float TEREDIT_MESH_ADD = 15; +const float TEREDIT_MESH_KILL = 16; +const float TEREDIT_TINT = 17; +const float TEREDIT_RESET_SECT = 20; +const float TEREDIT_RELOAD_SECT = 21; +const float TEREDIT_ENT_GET = 26; +const float TEREDIT_ENT_SET = 27; +const float TEREDIT_ENT_ADD = 28; +const float TEREDIT_ENT_COUNT = 29; +#endif +#if defined(CSQC) || defined(MENU) +const float SLIST_HOSTCACHEVIEWCOUNT = 0; +const float SLIST_HOSTCACHETOTALCOUNT = 1; +const float SLIST_MASTERQUERYCOUNT = 2; +const float SLIST_MASTERREPLYCOUNT = 3; +const float SLIST_SERVERQUERYCOUNT = 4; +const float SLIST_SERVERREPLYCOUNT = 5; +const float SLIST_SORTFIELD = 6; +const float SLIST_SORTDESCENDING = 7; +const float SLIST_TEST_CONTAINS = 0; +const float SLIST_TEST_NOTCONTAIN = 1; +const float SLIST_TEST_LESSEQUAL = 2; +const float SLIST_TEST_LESS = 3; +const float SLIST_TEST_EQUAL = 4; +const float SLIST_TEST_GREATER = 5; +const float SLIST_TEST_GREATEREQUAL = 6; +const float SLIST_TEST_NOTEQUAL = 7; +const float SLIST_TEST_STARTSWITH = 8; +const float SLIST_TEST_NOTSTARTSWITH = 9; +#endif +#ifdef MENU +float(string ext) checkextension = #1; /* + Checks if the named extension is supported by the running engine. */ + +void(string err,...) error = #2; /* + Fatal error that will trigger a crash-to-console that users will actually notice. */ + +void(string err,...) objerror = #3; /* + For some reason this has been redefined as non-fatal, and as it won't force the user to look at the console it'll generally be ignored completely so really what's the point? Other than as a convoluted way to remove(self) that is. */ + +void(string text,...) print = #4; /* Part of DP_SV_PRINT + Hello, world. Shoves junk on the console. Hopefully people will bother to read it, maybe. */ + +DEP void(string text,...) bprint = #5; +DEP void(float clientnum, string text,...) msprint = #6; +void(string text,...) cprint = #7; /* + Tries to show the given message in the centre of the screen, assuming that its not obscured by menus. Oh hey look, you're calling it in menuqc! */ + +vector(vector) normalize = #8; +float(vector) vlen = #9; +float(vector) vectoyaw = #10; +vector(vector) vectoangles = #11; +float() random = #12; +void(string,...) localcmd = #13; +float(string name) cvar = #14; +void(string name, string value) cvar_set = #15; +void(string text, ...) dprint = #16; +string(float) ftos = #17; +float(float) fabs = #18; +string(vector) vtos = #19; +string(entity) etos = #20; /* Part of DP_QC_ETOS*/ +float(string) stof = #21; /* Part of FRIK_FILE, FTE_QC_INFOKEY, FTE_STRINGS, QW_ENGINE, ZQ_QC_STRINGS*/ +entity() spawn = #22; +void(entity) remove = #23; +entity(entity start, .string field, string match) find = #24; +entity(entity start, .__variant field, __variant match) findfloat = #25; /* Part of DP_QC_FINDFLOAT*/ +entity(.string field, string match) findchain = #26; /* Part of DP_QC_FINDCHAIN*/ +entity(.__variant field, __variant match) findchainfloat = #27; /* Part of DP_QC_FINDCHAINFLOAT*/ +string(string file) precache_file = #28; /* + Attempts to download the named file from the current server, if it isn't found locally. Not very useful as menuqc is normally meant to work before joining servers too. */ + +string(string sample) precache_sound = #29; +void() coredump = #30; /* + Takes a dump, writing the qcvm's state to disk. There are normally easier ways to debug, but I suppose this one still beats print spam. */ + +void() traceon = #31; /* + Enables single-stepping. Its generally easier to just set a breakpoint. */ + +void() traceoff = #32; /* + Disables single-stepping. Which sucks if you started said singlestepping outside of qc. */ + +void(entity) eprint = #33; +float(float) rint = #34; +float(float) floor = #35; +float(float) ceil = #36; +entity(entity) nextent = #37; +float(float) sin = #38; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float) cos = #39; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float) sqrt = #40; /* Part of DP_QC_SINCOSSQRTPOW*/ +vector() randomvector = #41; +float(string name, string value, float flags) registercvar = #42; /* Part of DP_REGISTERCVAR + Creates the cvar if it didn't already exist. This presents issues for setting those cvars via startup configs of course, and autocvars are easier but I suppose they don't get any flags (which are ignored anyway, of course). */ + +float(float,...) min = #43; /* Part of DP_QC_MINMAXBOUND*/ +float(float,...) max = #44; /* Part of DP_QC_MINMAXBOUND*/ +float(float min,float value,float max) bound = #45; /* Part of DP_QC_MINMAXBOUND*/ +float(float,float) pow = #46; /* Part of DP_QC_SINCOSSQRTPOW*/ +void(entity src, entity dst) copyentity = #47; /* Part of DP_QC_COPYENTITY + Copies all entity fields from one entity into another (forgetting any that were previously set on the destination). */ + +filestream(string filename, float mode) fopen = #48; /* Part of FRIK_FILE*/ +void(filestream fhandle) fclose = #49; /* Part of FRIK_FILE*/ +string(filestream fhandle) fgets = #50; /* Part of FRIK_FILE*/ +void(filestream fhandle, string s) fputs = #51; /* Part of FRIK_FILE*/ +float(string) strlen = #52; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string, optional string, optional string, optional string, optional string, optional string, optional string, optional string) strcat = #53; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, float start, float length) substring = #54; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +vector(string) stov = #55; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +FTEDEP("Redundant") string(string) strzone = #56; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS + Exists in FTE for compat only, no different from strcat. */ + +FTEDEP("Redundant") void(string) strunzone = #57; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS + Exists in FTE for compat only, does nothing. */ + +float(string) tokenize = #58; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND + Splits up the given string into its different components (what constitutes a token separator is not well defined and has been hacked about with over the years so have fun with that), returning the number of tokens that were found. Call argv(0 through ret-1) to retrieve each individual token. Take care to not use this recursively. */ + +string(float) argv = #59; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND + Returns one of the tokens found via tokenize (and equivelent builtins). */ + +float() isserver = #60; /* + Returns true if the local engine is running a server, and thus cvars and localcmds are shared with said server. */ + +float() clientcount = #61; /* + Returns the maximum number of players on the server. Useless if its a remote server, so its a kinda useless builtin really. */ + +float() clientstate = #62; /* + Tells you whether the client is actually connected to anything. 0 for a dedicated server (but dedicated servers don't normally run menuqc anyway), 2 if connecting or connected to a server (but not necessarily spawned+active), 1 for sitting around idle without trying to connect to anything yet. */ + +void(string map) changelevel = #64; /* + Not really any different from a localcmd, but with proper string escapes. */ + +void(string sample, optional float channel, optional float volume) localsound = #65; /* + Plays a sound, locally. precaching is optional, but recommended. */ + +vector() getmousepos = #66; /* + Obsolete. Return values depend upon the current cursor mode. Implement Menu_InputEvent instead, so you can handle deltas as-is or absolutes if that's all the OS can provide. */ + +float(optional float timetype) gettime = #67; +void(string s) loadfromdata = #68; /* + Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +void(string s) loadfromfile = #69; /* + Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +float(float val, float m) mod = #70; +string(string name) cvar_string = #71; /* Part of DP_QC_CVAR_STRING + Returns the value of a cvar, as a string. */ + +FTEDEP("Call 'error' instead") void() crash = #72; /* + Demonstrates that no program is bug free. */ + +void() stackdump = #73; /* + Prints out the QC's stack, for console-based error reports. */ + +searchhandle(string pattern, enumflags:float{SB_CASEINSENSITIVE=1<<0,SB_FULLPACKAGEPATH=1<<1,SB_ALLOWDUPES=1<<2,SB_FORCESEARCH=1<<3,SB_MULTISEARCH=1<<4,SB_NAMESORT=1<<5} flags, float quiet, optional string package) search_begin = #74; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE*/ +void(searchhandle handle) search_end = #75; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE*/ +float(searchhandle handle) search_getsize = #76; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE*/ +string(searchhandle handle, float num) search_getfilename = #77; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE*/ +float(entity) etof = #79; +entity(float) ftoe = #80; +float(string) validstring = #81; /* + Returns true if str isn't null. In case 'if [not](str)' was configured to test for empty instead of null. */ + +DEP float(string str) altstr_count = #82; /* + Reports how many single-quotes there were in the string, divided by 2. */ + +DEP string(string str) altstr_prepare = #83; /* + Adds markup to escape only single-quotes. Does not add any. */ + +DEP string(string str, float num) altstr_get = #84; /* + Gets the Nth single-quoted token in the input. */ + +DEP string(string str, float num, string setval) altstr_set = #85; /* + Changes the Nth single-quoted token. The setval argument must not contain any single-quotes (use altstr_prepare to ensure this). */ + +entity(entity start, .float field, float match) findflags = #87; /* Part of DP_QC_FINDFLAGS*/ +entity(.float field, float match) findchainflags = #88; /* Part of DP_QC_FINDCHAINFLAGS*/ +string(string name) cvar_defstring = #89; /* Part of DP_QC_CVAR_DEFSTRING*/ +void(entity ent, string mname) setmodel = #90; /* + Menuqc-specific version. */ + +void(string mname) precache_model = #91; /* + Menuqc-specific version. */ + +void(entity ent, vector neworg) setorigin = #92; /* + Menuqc-specific version. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector vang) makevectors = #1; /* + Takes an angle vector (pitch,yaw,roll) (+x=DOWN). Writes its results into v_forward, v_right, v_up vectors. */ + +void(entity e, vector o) setorigin = #2; /* + Changes e's origin to be equal to o. Also relinks collision state (as well as setting absmin+absmax), which is required after changing .solid */ + +void(entity e, string m) setmodel = #3; /* + Looks up m in the model precache list, and sets both e.model and e.modelindex to match. BSP models will set e.mins and e.maxs accordingly, other models depend upon the value of sv_gameplayfix_setmodelrealbox - for compatibility you should always call setsize after all pickups or non-bsp models. Also relinks collision state. */ + +void(entity e, vector min, vector max) setsize = #4; /* + Sets the e's mins and maxs fields. Also relinks collision state, which sets absmin and absmax too. */ + +void() breakpoint = #6; /* + Trigger a debugging event. FTE will break into the qc debugger. Other engines may crash with a debug execption. */ + +float() random = #7; /* + Returns a random value between 0 and 1. Be warned, this builtin can return 1 in most engines, which can break arrays. */ + +void(entity e, float chan, string samp, float vol, float atten, optional float speedpct, optional float flags, optional float timeofs) sound = #8; /* + Starts a sound centered upon the given entity. + chan is the entity sound channel to use, channel 0 will allow you to mix many samples at once, others will replace the old sample + 'samp' must have been precached first + if specified, 'speedpct' should normally be around 100 (or =0), 200 for double speed or 50 for half speed. + If flags is specified, the reliable flag in the channels argument is used for additional channels. Flags should be made from SOUNDFLAG_* constants + timeofs should be negative in order to provide a delay before the sound actually starts. */ + +vector(vector v) normalize = #9; /* + Shorten or lengthen a direction vector such that it is only one quake unit long. */ + +void(string e) error = #10; /* + Ends the game with an easily readable error message. */ + +void(string e) objerror = #11; /* + Displays a non-fatal easily readable error message concerning the self entity, including a field dump. self will be removed! */ + +float(vector v) vlen = #12; /* + Returns the square root of the dotproduct of a vector with itself. Or in other words the length of a distance vector, in quake units. */ + +float(vector v, optional entity reference) vectoyaw = #13; /* + Given a direction vector, returns the yaw angle in which that direction vector points. If an entity is passed, the yaw angle will be relative to that entity's gravity direction. */ + +entity() spawn = #14; /* + Adds a brand new entity into the world! Hurrah, you're now a parent! */ + +void(entity e) remove = #15; /* + Destroys the given entity and clears some limited fields (including model, modelindex, solid, classname). Any references to the entity following the call are an error. After half a second the entity will be reused, in the interim you can unfortunatly still read its fields to see if the reference is no longer valid. */ + +#endif +void(entity e) removeinstant = #0:removeinstant; /* + Same thing as the regular remove builtin, but bypasses the half-second rule. The entity slot may be reused instantly. Be CERTAIN that you have no lingering references, because if they're followed they will end up poking an entirely different type of entity! So only use this where you're sure its safe. */ + +#if defined(CSQC) || defined(SSQC) +void(vector v1, vector v2, float flags, entity ent) traceline = #16; /* + Traces a thin line through the world from v1 towards v2. + Will not collide with ent, ent.owner, or any entity who's owner field refers to ent. + The passed entity will also be used to determine whether to use a capsule trace, the contents that the trace should impact, and a couple of other extra fields that define the trace. + There are no side effects beyond the trace_* globals being written. + flags&MOVE_NOMONSTERS will not impact on non-bsp entities. + flags&MOVE_MISSILE will impact with increased size. + flags&MOVE_HITMODEL will impact upon model meshes, instead of their bounding boxes. + flags&MOVE_TRIGGERS will also stop on triggers + flags&MOVE_EVERYTHING will stop if it hits anything, even non-solid entities. + flags&MOVE_LAGGED will backdate entity positions for the purposes of this builtin according to the indicated player ent's latency, to provide lag compensation. */ + +#endif +#ifdef SSQC +entity() checkclient = #17; /* + Returns one of the player entities. The returned player will change periodically. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(entity start, .string fld, string match) find = #18; /* + Scan for the next entity with a given field set to the given 'match' value. start should be either world, or the previous entity that was found. Returns world on failure/if there are no more. + If you have many many entities then you may find that hashtables will give more performance (but requires extra upkeep). */ + +#endif +entity*(.__variant fld, __variant match, int type=EV_STRING, __out int count) find_list = #0:find_list; /* + Scan for the next entity with a given field set to the given 'match' value. start should be either world, or the previous entity that was found. Returns world on failure/if there are no more. + If you have many many entities then you may find that hashtables will give more performance (but requires extra upkeep). */ + +#if defined(CSQC) || defined(SSQC) +string(string s) precache_sound = #19; /* + Precaches a sound, making it known to clients and loading it from disk. This builtin (strongly) should be called during spawn functions. This builtin must be called for the sound before the sound builtin is called, or it might not even be heard. */ + +string(string s) precache_model = #20; /* + Precaches a model, making it known to clients and loading it from disk if it has a .bsp extension. This builtin (strongly) should be called during spawn functions. This must be called for each model name before setmodel may use that model name. + Modelindicies precached in SSQC will always be positive. CSQC precaches will be negative if they are not also on the server. */ + +#endif +#ifdef SSQC +void(entity client, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) stuffcmd = #21; /* + Sends a console command (or cvar) to the client, where it will be executed. Different clients support different commands. Do NOT forget the final \n. + This builtin is generally considered evil. */ + +void(entity client, float flags, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6) stuffcmdflags = #0:stuffcmdflags; /* Part of FTE_QC_STUFFCMDFLAGS + Sends a console command (or cvar) to the client, where it will be executed. Different clients support different commands. Do NOT forget the final \n. + This (just as evil) variant allows specifying some flags too. See the STUFFCMD_* constants. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(vector org, float rad, optional .entity chainfield) findradius = #22; /* + Finds all entities within a distance of the 'org' specified. One entity is returned directly, while other entities are returned via that entity's .chain field. Use findradius_list for an updated alternative without reenterancy issues. */ + +entity*(vector org, float rad, __out int foundcount, int sort=0) findradius_list = #0:findradius_list; /* + Finds all entities linked with a bbox within a distance of the 'org' specified, returning the list as a temp-array (world signifies the end). Unlike findradius, sv_gameplayfix_blowupfallenzombies is ignored (use FL_FINDABLE_NONSOLID instead), while sv_gameplayfix_findradiusdistancetobox and dpcompat_findradiusarealinks are force-enabled. The resulting buffer will automatically be cleaned up by the engine and does not need to be freed. */ + +#endif +#if defined(NQSSQC) +void(string s, optional string/__ s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8) bprint = #23; /* + NQ: Concatenates all arguments, and prints the messsage on the console of all connected clients. */ + +#endif +#if defined(QWSSQC) +void(float msglvl, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) bprint = #23; /* + QW: Concatenates all string arguments, and prints the messsage on the console of only all clients who's 'msg' infokey is set lower or equal to the supplied 'msglvl' argument. */ + +#endif +#if defined(NQSSQC) +void(entity client, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) sprint = #24; /* + NQ: Concatenates all string arguments, and prints the messsage on the named client's console */ + +#endif +#if defined(QWSSQC) +void(entity client, float msglvl, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6) sprint = #24; /* + QW: Concatenates all string arguments, and prints the messsage on the named client's console, but only if that client's 'msg' infokey is set lower or equal to the supplied 'msglvl' argument. */ + +#endif +#if defined(CSQC) || defined(NQSSQC) +void(string s, ...) dprint = #25; /* + NQ: Prints the given message on the server's console, but only if the developer cvar is set. Arguments will be concatenated into a single message. */ + +#endif +#if defined(CSQC) || defined(QWSSQC) +void(string s, ...) dprint = #25; /* + QW: Unconditionally prints the given message on the server's console. Arguments will be concatenated into a single message. */ + +#endif +#if defined(CSQC) || defined(SSQC) +string(float val) ftos = #26; /* + Returns a tempstring containing a representation of the given float. Precision depends upon engine. */ + +string(vector val) vtos = #27; /* + Returns a tempstring containing a representation of the given vector. Precision depends upon engine. */ + +void() coredump = #28; /* + Writes out a coredump. This contains stack, globals, and field info for all ents. This can be handy for debugging. */ + +void() traceon = #29; /* + Enables tracing. This may be spammy, slow, and stuff. Set debugger 1 in order to use fte's qc debugger. */ + +void() traceoff = #30; /* + Disables tracing again. */ + +void(entity e) eprint = #31; /* + Debugging builtin that prints all fields of the given entity to the console. */ + +float(float yaw, float dist, optional float settraceglobals) walkmove = #32; /* + Attempt to walk the entity at a given angle for a given distance. + if settraceglobals is set, the trace_* globals will be set, showing the results of the movement. + This function will trigger touch events. */ + +float() droptofloor = #34; /* + Instantly moves the entity downwards until it hits the ground. If the entity is in solid or would need to drop more than 'pr_droptofloorunits' quake units, its position will be considered invalid and the builtin will abort, returning FALSE, otherwise TRUE. */ + +void(float lightstyle, string stylestring, optional vector rgb) lightstyle = #35; /* + Specifies an auto-animating string that specifies the light intensity for entities using that lightstyle. + a is off, z is fully lit. Should be lower case only. + rgb will recolour all lights using that lightstyle. */ + +float(float) rint = #36; /* + Rounds the given float up or down to the closest integeral value. X.5 rounds away from 0 */ + +float(float) floor = #37; /* + Rounds the given float downwards, even when negative. */ + +float(float) ceil = #38; /* + Rounds the given float upwards, even when negative. */ + +float(entity ent) checkbottom = #40; /* + Expensive checks to ensure that the entity is actually sitting on something solid, returns true if it is. */ + +float(vector pos) pointcontents = #41; /* + Checks the given point to see what is there. Returns one of the CONTENTS_* constants. Just because a point is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests. */ + +__uint(vector pos, optional float worldonly=1) pointcontentsmask = #0:pointcontentsmask; /* + Checks the given point to see what is there. Returns a mask of the CONTENTBIT_* constants. Just because a point is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests. */ + +float(float) fabs = #43; /* + Removes the sign of the float, making it positive if it is negative. */ + +#endif +#ifdef SSQC +vector(entity player, float missilespeed) aim = #44; /* + Returns a tweaked copy of the v_forward vector (must be set! ie: makevectors(player.v_angle) ). This is important for keyboard users (that don't want to have to look up/down the whole time), as well as joystick users (who's aim is otherwise annoyingly imprecise). Only the upwards component of the result will differ from the value of v_forward. The builtin will select the enemy closest to the crosshair within the angle of acos(sv_aim). */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(string) cvar = #45; /* + Returns the numeric value of the named cvar */ + +void(string, ...) localcmd = #46; /* + Adds the string to the console command queue. Commands will not be executed immediately, but rather at the start of the following frame. */ + +entity(entity) nextent = #47; /* + Returns the following entity. Skips over removed entities. Returns world when passed the last valid entity. */ + +void(vector pos, vector dir, float colour, float count) particle = #48; /* + Spawn 'count' particles around 'pos' moving in the direction 'dir', with a palette colour index between 'colour' and 'colour+8'. */ + +#define ChangeYaw changeyaw +void() changeyaw = #49; /* + Changes the self.angles_y field towards self.ideal_yaw by up to self.yaw_speed. */ + +vector(vector fwd, optional vector up) vectoangles = #51; /* + Returns the angles (+x=UP) required to orient an entity to look in the given direction. The 'up' argument is required if you wish to set a roll angle, otherwise it will be limited to just monster-style turning. */ + +#endif +#ifdef SSQC +void(float to, float val) WriteByte = #52; /* + Writes a single byte into a network message buffer. Typically you will find a more correct alternative to writing arbitary data. 'to' should be one of the MSG_* constants. MSG_ONE must have msg_entity set first. */ + +void(float to, float val) WriteChar = #53; /* + Writes a signed value between -128 and 127. */ + +void(float to, float val) WriteShort = #54; /* + Writes a signed value between -32768 and 32767. As an exception, values up to 65535 will not trigger warnings (but readshort will read the result as negative!) */ + +void(float to, float val) WriteLong = #55; /* + Writes a signed 32bit integer. Note that the input argument being of float type limits the resulting integer to a mere 24 consecutive bits of validity. Use WriteInt if you want to write an entire 32bit int without data loss. */ + +void(float to, float val) WriteCoord = #56; /* + Writes a single value suitable for a map coordinate axis. The precision is not strictly specified but is assumed to be of at least 13.3 fixed-point precision (ie: +/-4k with 1/8th precision). */ + +void(float to, float val) WriteAngle = #57; /* + Writes a single value suitable for an angle axis. The precision is not strictly specified but is assumed to be 8bit, giving 256 notches instead of the assumed 360 range passed in. */ + +void(float to, string val) WriteString = #58; /* + Writes a variable-length null terminated string. There are length limits. The codepage is not translated, so be sure that client+server agree on whether utf-8 is being used or not (or just stick to ascii+markup). */ + +void(float to, entity val) WriteEntity = #59; /* + Writes the index of the specified entity (the network data size is not specified). This can be read clientside using the readentitynum builtin, with caveats. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(float angle) sin = #60; /* Part of DP_QC_SINCOSSQRTPOW + Forgive me father, for I have trigonometry homework. */ + +float(float angle) cos = #61; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float value) sqrt = #62; /* Part of DP_QC_SINCOSSQRTPOW*/ +#endif +#ifdef SSQC +float(float a, float n) modulo = #0:modulo; +#endif +#if defined(CSQC) || defined(SSQC) +void(entity ent) changepitch = #63; /* Part of DP_QC_CHANGEPITCH*/ +void(entity ent, entity ignore) tracetoss = #64; +string(entity ent) etos = #65; /* Part of DP_QC_ETOS*/ +void(float step) movetogoal = #67; /* + Runs lots and lots of fancy logic in order to try to step the entity the specified distance towards its goalentity. */ + +string(string s) precache_file = #68; /* + This builtin does nothing. It was used only as a hint for pak generation. */ + +void(entity e) makestatic = #69; /* + Sends a copy of the entity's renderable fields to all clients, and REMOVES the entity, preventing further changes. This means it will be unmutable and non-solid. */ + +#endif +#ifdef SSQC +void(string mapname, optional string newmapstartspot) changelevel = #70; /* + Attempts to change the map to the named map. If 'newmapstartspot' is specified, the state of the current map will be preserved, and the argument will be passed to the next map in the 'startspot' global, and the next map will be loaded from archived state if it was previously visited. If not specified, all archived map states will be purged. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(string cvarname, string valuetoset) cvar_set = #72; /* + Instantly sets a cvar to the given string value. Warning: the resulting string includes apostrophies surrounding the result. You may wish to use sprintf instead. */ + +#endif +#ifdef SSQC +void(entity ent, string text, optional string text2, optional string text3, optional string text4, optional string text5, optional string text6, optional string text7) centerprint = #73; +#endif +#if defined(CSQC) || defined(SSQC) +void (vector pos, string samp, float vol, float atten) ambientsound = #74; +string(string str) precache_model2 = #75; +string(string str) precache_sound2 = #76; +string(string str) precache_file2 = #77; +#endif +#ifdef SSQC +void(entity player) setspawnparms = #78; +float() ex_finaleFinished = #0:ex_finaleFinished; /* + Behaviour is undocumented. */ + +void(entity client, string sample) ex_localsound = #0:ex_localsound; /* + Behaviour is undocumented. */ + +void(entity ent, string text, optional string s0, optional string s1, optional string s2, optional string s3, optional string s4, optional string s5) ex_centerprint = #0:ex_centerprint; /* + Remaster: Sends the strings to the client, which will order according to {#}. Also substitutes localised strings for $NAME strings. */ + +void(string s, optional string s0, optional string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6) ex_bprint = #0:ex_bprint; /* + Remaster: Sends the strings to all clients, which will order them according to {#}. Also substitutes localised strings for $NAME strings. */ + +void(entity client, string s, optional string s0, optional string s1, optional string s2, optional string s3, optional string s4, optional string s5) ex_sprint = #0:ex_sprint; /* + Remaster: Sends the strings to the client, which will order according to {#}. Also substitutes localised strings for $NAME strings. */ + +float(entity playerEnt) ex_CheckPlayerEXFlags = #0:ex_CheckPlayerEXFlags; /* + Behaviour is undocumented. */ + +float(float movedist, vector goal) ex_walkpathtogoal = #0:ex_walkpathtogoal; /* + Behaviour is undocumented. */ + +void(entity killer, entity killee) logfrag = #79; /* Part of QW_ENGINE*/ +#endif +#if defined(CSQC) || defined(SSQC) +string(entity e, string key) infokey = #80; /* Part of FTE_QC_INFOKEY, QW_ENGINE + If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo. */ + +#endif +#ifdef SSQC +float(entity e, string key) infokeyf = #0:infokeyf; /* + Identical to regular infokey, except returns a float. */ + +int(entity e, string key, optional void *outbuf, int outbufsize) infokey_blob = #0:infokey_blob; /* + Retrieves a user's blob size, and optionally writes it to the specified buffer. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(string) stof = #81; /* Part of FRIK_FILE, FTE_QC_INFOKEY, FTE_STRINGS, QW_ENGINE, ZQ_QC_STRINGS*/ +#endif +#ifdef SSQC +#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0) +void(vector where, float set) multicast = #82; /* Part of EXT_CSQC, FTE_QC_MULTICAST + Once the MSG_MULTICAST network message buffer has been filled with data, this builtin is used to dispatch it to the given target, filtering by pvs for reduced network bandwidth. */ + +DEP void(entity to, string str) redirectcmd = #101; /* Part of ??MVDSV_BUILTINS + Executes a single console command, and sends the text generated by it to the specified player. The command will be executed at the end of the frame once QC is no longer running - you may wish to pre/postfix it with 'echo'. */ + +#endif +#if defined(CSQC) || defined(SSQC) +string(float style, optional __out vector rgb) getlightstyle = #0:getlightstyle; /* + Obtains the light style string for the given style. */ + +vector(float style) getlightstylergb = #0:getlightstylergb; /* + Obtains the current rgb value of the specified light style. In csqc, this is correct with regard to the current frame, while ssqc gives no guarentees about time and ignores client cvars. Note: use getlight if you want the actual light value at a point. */ + +#endif +#ifdef SSQC +void(float style, float val, optional vector rgb) lightstylestatic = #5; /* + Sets the lightstyle to an explicit numerical level. From Hexen2. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent) tracebox = #90; /* Part of DP_QC_TRACEBOX + Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values. */ + +vector() randomvec = #91; /* Part of DP_QC_RANDOMVEC + Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive. */ + +vector(vector org) getlight = #92; /* Part of DP_QC_GETLIGHT*/ +float(string cvarname, string defaultvalue) registercvar = #93; /* Part of DP_REGISTERCVAR + Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op. + This builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop. + In engines that support it, you will generally find the autocvar feature easier and more efficient to use. */ + +float(float a, float b, ...) min = #94; /* Part of DP_QC_MINMAXBOUND + Returns the lowest value of its arguments. */ + +float(float a, float b, ...) max = #95; /* Part of DP_QC_MINMAXBOUND + Returns the highest value of its arguments. */ + +float(float minimum, float val, float maximum) bound = #96; /* Part of DP_QC_MINMAXBOUND + Returns val, unless minimum is higher, or maximum is less. */ + +float(float value, float exp) pow = #97; /* Part of DP_QC_SINCOSSQRTPOW*/ +#endif +float(float v, optional float base) logarithm = #0:logarithm; /* + Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by. */ + +#if defined(CSQC) || defined(SSQC) +#define findentity findfloat +entity(entity start, .__variant fld, __variant match) findfloat = #98; /* Part of DP_QC_FINDFLOAT + Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value. + world is returned when there are no more entities. */ + +float(string extname) checkextension = #99; /* + Checks for an extension by its name (eg: checkextension("FRIK_FILE") says that its okay to go ahead and use strcat). + Use cvar("pr_checkextension") to see if this builtin exists. */ + +#endif +float(__variant funcref) checkbuiltin = #0:checkbuiltin; /* + Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions. Warning, if two different engines map different builtins to the same number, then this function will not tell you which will be called, only that it won't crash (the exception being #0, which are remapped as available). */ + +#ifdef SSQC +float(string builtinname) builtin_find = #100; /* + Looks to see if the named builtin is valid, and returns the builtin number it exists at. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(float value) anglemod = #102; +float(float newangle, float oldangle) anglesub = #0:anglesub; /* + Returns newangle-oldangle, except returning the shortest route around a circle so yields a result between -180 and +180. */ + +#endif +#ifdef SSQC +DEP_CSQC void(string slot, string picname, float x, float y, float zone, optional entity player) showpic = #104; /* Part of TEI_SHOWLMP2*/ +DEP_CSQC void(string slot, optional entity player) hidepic = #105; /* Part of TEI_SHOWLMP2*/ +DEP_CSQC void(string slot, float x, float y, float zone, optional entity player) movepic = #106; /* Part of TEI_SHOWLMP2*/ +DEP_CSQC void(string slot, string picname, optional entity player) changepic = #107; /* Part of TEI_SHOWLMP2*/ +#endif +#if defined(CSQC) || defined(SSQC) +filestream(string filename, float mode, optional float mmapminsize) fopen = #110; /* Part of FRIK_FILE + Opens a file, typically prefixed with "data/", for either read or write access. */ + +void(filestream fhandle) fclose = #111; /* Part of FRIK_FILE*/ +string(filestream fhandle) fgets = #112; /* Part of FRIK_FILE + Reads a single line out of the file. The new line character is not returned as part of the string. Returns the null string on EOF (use if not(string) to easily test for this, which distinguishes it from the empty string which is returned if the line being read is blank */ + +void(filestream fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) fputs = #113; /* Part of FRIK_FILE + Writes the given string(s) into the file. For compatibility with fgets, you should ensure that the string is terminated with a \n - this will not otherwise be done for you. It is up to the engine whether dos or unix line endings are actually written. */ + +#endif +int(filestream fhandle, void *ptr, int size) fread = #0:fread; /* Part of FTE_QC_FILE_BINARY + Reads binary data out of the file. Returns truncated lengths if the read exceeds the length of the file. */ + +int(filestream fhandle, void *ptr, int size) fwrite = #0:fwrite; /* Part of FTE_QC_FILE_BINARY + Writes binary data out of the file. */ + +#define ftell fseek //c compat +int(filestream fhandle, optional int newoffset) fseek = #0:fseek; /* Part of FTE_QC_FILE_BINARY + Changes the current position of the file, if specified. Returns prior position, in bytes. */ + +int(filestream fhandle, optional int newsize) fsize = #0:fsize; /* Part of FTE_QC_FILE_BINARY + Reports the total size of the file, in bytes. Can also be used to truncate/extend the file */ + +#if defined(CSQC) || defined(SSQC) +float(string s) strlen = #114; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8) strcat = #115; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, float start, float length) substring = #116; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +vector(string s) stov = #117; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +FTEDEP("Redundant") string(string s, ...) strzone = #118; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS + Create a semi-permanent copy of a string that only becomes invalid once strunzone is called on the string (instead of when the engine assumes your string has left scope). This builtin has become redundant in FTEQW due to the FTE_QC_PERSISTENTTEMPSTRINGS extension and is now functionally identical to strcat for compatibility with old engines+mods. */ + +FTEDEP("Redundant") void(string s) strunzone = #119; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS + Destroys a string that was allocated by strunzone. Further references to the string MAY crash the game. In FTE, this function became redundant and now does nothing. */ + +#endif +void*(int bytes) createbuffer = #0:createbuffer; /* + Returns a temporary buffer that can be written to / read from. The buffer will be garbage collected and thus cannot be explicitly freed. Tempstrings and buffer references must not be stored into the buffer as the garbage collector will not scan these. */ + +#ifdef SSQC +void(string cvar, float val) cvar_setf = #176; +#endif +#if defined(CSQC) || defined(SSQC) +void(string soundname, optional float channel, optional float volume) localsound = #177; /* + Plays a sound... locally... probably best not to call this from ssqc. Also disables reverb. */ + +float(string modelname, optional float queryonly) getmodelindex = #200; /* + Acts as an alternative to precache_model(foo);setmodel(bar, foo); return bar.modelindex; + If queryonly is set and the model was not previously precached, the builtin will return 0 without needlessly precaching the model. */ + +float(string soundname, optional float queryonly) getsoundindex = #0:getsoundindex; /* + Provides a way to query if a sound is already precached or not. The return value can also be checked for <=255 to see if it'll work over any network protocol. The sound index can also be used for writebyte hacks, but this is discouraged - use SOUNDFLAG_UNICAST instead. */ + +__variant(float prnum, string funcname, ...) externcall = #201; /* Part of FTE_MULTIPROGS + Directly call a function in a different/same progs by its name. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +float(string progsname) addprogs = #202; /* Part of FTE_MULTIPROGS + Loads an additional .dat file into the current qcvm. The returned handle can be used with any of the externcall/externset/externvalue builtins. + There are cvars that allow progs to be loaded automatically. */ + +__variant(float prnum, string varname) externvalue = #203; /* Part of FTE_MULTIPROGS + Reads a global in the named progs by the name of that global. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +void(float prnum, __variant newval, string varname) externset = #204; /* Part of FTE_MULTIPROGS + Sets a global in the named progs by name. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +void(entity portal, float state) openportal = #207; /* + Opens or closes the portals associated with a door or some such on q2 or q3 maps. On Q2BSPs, the entity should be the 'func_areaportal' entity - its style field will say which portal to open. On Q3BSPs, the entity is the door itself, the portal will be determined by the two areas found from a preceding setorigin call. */ + +#endif +#ifdef SSQC +float(float attributes, string effectname, ...) RegisterTempEnt = #208; /* Part of FTE_PEXT_CUSTOMTENTS*/ +void(float type, vector pos, ...) CustomTempEnt = #209; /* Part of FTE_PEXT_CUSTOMTENTS*/ +float(optional float sleeptime) fork = #210; /* Part of FTE_MULTITHREADED + When called, this builtin simply returns. Twice. + The current 'thread' will return instantly with a return value of 0. The new 'thread' will return after sleeptime seconds with a return value of 1. See documentation for the 'sleep' builtin for limitations/requirements concerning the new thread. Note that QC should probably call abort in the new thread, as otherwise the function will return to the calling qc function twice also. */ + +#endif +void(optional __variant ret) abort = #211; /* Part of FTE_MULTITHREADED + QC execution is aborted. Parent QC functions on the stack will be skipped, effectively this forces all QC functions to 'return ret' until execution returns to the engine. If ret is ommited, it is assumed to be 0. */ + +#ifdef SSQC +void(float sleeptime) sleep = #212; /* Part of FTE_MULTITHREADED + Suspends the current QC execution thread for 'sleeptime' seconds. + Other QC functions can and will be executed in the interim, including changing globals and field state (but not simultaneously). + The self and other globals will be restored when the thread wakes up (or set to world if they were removed since the thread started sleeping). Locals will be preserved, but will not be protected from remove calls. + If the engine is expecting the QC to return a value (even in the parent/root function), the value 0 shall be used instead of waiting for the qc to resume. */ + +void(entity player, string key, string value) forceinfokey = #213; /* Part of FTE_FORCEINFOKEY + Directly changes a user's info without pinging off the client. Also allows explicitly setting * keys, including *spectator. Does not affect the user's config or other servers. */ + +void(entity player, string key, void *data, int size) forceinfokeyblob = #0:forceinfokeyblob; /* Part of FTE_INFOBLOBS + Directly changes a user's info without pinging off the client. Also allows explicitly setting * keys, including *spectator. Does not affect the user's config or other servers. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector org, vector dmin, vector dmax, float colour, float effect, float count) particle2 = #215; /* Part of FTE_HEXEN2*/ +void(vector org, vector box, float colour, float effect, float count) particle3 = #216; /* Part of FTE_HEXEN2*/ +void(vector org, float radius, float colour, float effect, float count) particle4 = #217; /* Part of FTE_HEXEN2*/ +float(float number, float quantity) bitshift = #218; /* Part of EXT_BITSHIFT*/ +void(vector pos) te_lightningblood = #219; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ +#endif +float(string s1, string sub, optional float startidx) strstrofs = #221; /* Part of FTE_STRINGS + Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1. + If startidx is set, this builtin will ignore matches before that 0-based offset. */ + +float(string str, float index) str2chr = #222; /* Part of FTE_STRINGS + Retrieves the character value at offset 'index'. */ + +string(float chr, ...) chr2str = #223; /* Part of FTE_STRINGS + The input floats are considered character values, and are concatenated. */ + +string(float ccase, float redalpha, float redchars, string str, ...) strconv = #224; /* Part of FTE_STRINGS + Converts quake chars in the input string amongst different representations. + ccase specifies the new case for letters. + 0: not changed. + 1: forced to lower case. + 2: forced to upper case. + redalpha and redchars switch between colour ranges. + 0: no change. + 1: Forced white. + 2: Forced red. + 3: Forced gold(low) (numbers only). + 4: Forced gold (high) (numbers only). + 5+6: Forced to white and red alternately. + You should not use this builtin in combination with UTF-8. */ + +string(float pad, string str1, ...) strpad = #225; /* Part of FTE_STRINGS + Pads the string with spaces, to ensure its a specific length (so long as a fixed-width font is used, anyway). If pad is negative, the spaces are added on the left. If positive the padding is on the right. */ + +infostring(infostring old, string key, string value) infoadd = #226; /* Part of FTE_STRINGS + Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \ character. */ + +string(infostring info, string key) infoget = #227; /* Part of FTE_STRINGS + Reads a named value from an infostring. The returned value is a tempstring */ + +#define strcmp strncmp +float(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs) strncmp = #228; /* Part of FTE_STRINGS + Compares up to 'len' chars in the two strings. s1ofs allows you to treat s2 as a substring to compare against, or should be 0. + Returns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher. */ + +float(string s1, string s2) strcasecmp = #229; /* Part of FTE_STRINGS + Compares the two strings without case sensitivity. + Returns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon. */ + +float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs) strncasecmp = #230; /* Part of FTE_STRINGS + Compares up to 'len' chars in the two strings without case sensitivity. s1ofs allows you to treat s2 as a substring to compare against, or should be 0. + Returns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon. */ + +string(string s) strtrim = #0:strtrim; /* + Trims the whitespace from the start+end of the string. */ + +#if defined(CSQC) || defined(SSQC) +__deprecated("Use strftime.") void() calltimeofday = #231; /* Part of FTE_CALLTIMEOFDAY + Asks the engine to instantly call the qc's 'timeofday' function, before returning. For compatibility with mvdsv. + timeofday should have the prototype: void(float secs, float mins, float hour, float day, float mon, float year, string strvalue) + The strftime builtin is more versatile and less weird. */ + +#endif +#ifdef SSQC +void(float num, float type, .__variant fld) clientstat = #232; /* Part of EXT_CSQC + Specifies what data to use in order to send various stats, in a client-specific way. + 'num' should be a value between 32 and 127, other values are reserved. + 'type' must be set to one of the EV_* constants, one of EV_FLOAT, EV_STRING, EV_INTEGER, EV_ENTITY. + fld must be a reference to the field used, each player will be sent only their own copy of these fields. */ + +void(float num, float type, string name) globalstat = #233; /* + Specifies what data to use in order to send various stats, in a non-client-specific way. num and type are as in clientstat, name however, is the name of the global to read in the form of a string (pass "foo"). */ + +void(float num, float type, __variant *address) pointerstat = #0:pointerstat; /* + Specifies what data to use in order to send various stats, in a non-client-specific way. num and type are as in clientstat, address however, is the address of the variable you would like to use (pass &foo). */ + +void(entity ent, vector sendflags, entity unicastplayer) setsendneeded = #0:setsendneeded; /* + Flags the entity as needing to be resent. This builtin allows for more bits than supported by the SendEntity field, as well as allows flagging sends to specific players. */ + +float(entity player) isbackbuffered = #234; /* Part of FTE_ISBACKBUFFERED + Returns if the given player's network buffer will take multiple network frames in order to clear. If this builtin returns non-zero, you should delay or reduce the amount of reliable (and also unreliable) data that you are sending to that client. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector angle) rotatevectorsbyangle = #235; /* + rotates the v_forward,v_right,v_up matrix by the specified angles. */ + +void(vector fwd, vector right, vector up) rotatevectorsbyvectors = #236; +float(float mdlindex, string skinname) skinforname = #237; +#endif +#if defined(CSQC) || defined(MENU) +float(string shadername, optional string defaultshader, ...) shaderforname = #238; /* Part of FTE_FORCESHADER + Caches the named shader and returns a handle to it. + If the shader could not be loaded from disk (missing file or ruleset_allow_shaders 0), it will be created from the 'defaultshader' string if specified, or a 'skin shader' default will be used. + defaultshader if not empty should include the outer {} that you would ordinarily find in a shader. */ + +#endif +#ifdef CSQC +void(string shadername, string replacement, float timeoffset) remapshader = #0:remapshader; /* + All surfaces drawn with the specified shader will instead be drawn using the specified replacement shader. Shaders can be remapped to something else later by using the same source shadername. This is mostly useful for worldmodel surfaces (eg showing which team is currently winning). Entities should generally use setcustomskin or forceshader instead. Remaps will be forgotten on vid_reload, but can be reapplied via CSQC_RendererRestarted. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector org, optional float count) te_bloodqw = #239; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ +#endif +#ifdef SSQC +void(entity ent) te_muzzleflash = #0:te_muzzleflash; +#endif +#if defined(CSQC) || defined(SSQC) +float(vector viewpos, entity entity) checkpvs = #240; /* Part of FTE_QC_CHECKPVS*/ +#endif +#ifdef SSQC +entity(string match, optional float matchnum) matchclientname = #241; /* Part of FTE_QC_MATCHCLIENTNAME*/ +#endif +float(string destaddress, string content) sendpacket = #242; /* Part of FTE_QC_SENDPACKET + Sends a UDP packet to the specified destination. Note that the payload will be prefixed with four 255 bytes as a sort of security feature. */ + +#ifdef CSQC +vector(entity ent, float tagnum) rotatevectorsbytag = #244; +#endif +#if defined(CSQC) || defined(SSQC) +float(float dividend, float divisor) mod = #245; +#endif +#ifdef SSQC +float(optional string host, optional string user, optional string pass, optional string defaultdb, optional string driver) sqlconnect = #250; /* Part of FTE_SQL*/ +void(float serveridx) sqldisconnect = #251; /* Part of FTE_SQL*/ +float(float serveridx, void(float serveridx, float queryidx, float rows, float columns, float eof, float firstrow) callback, float querytype, string query) sqlopenquery = #252; /* Part of FTE_SQL*/ +void(float serveridx, float queryidx) sqlclosequery = #253; /* Part of FTE_SQL*/ +string(float serveridx, float queryidx, float row, float column) sqlreadfield = #254; /* Part of FTE_SQL*/ +string(float serveridx, optional float queryidx) sqlerror = #255; /* Part of FTE_SQL*/ +string(float serveridx, string data) sqlescape = #256; /* Part of FTE_SQL*/ +string(float serveridx) sqlversion = #257; /* Part of FTE_SQL*/ +float(float serveridx, float queryidx, float row, float column) sqlreadfloat = #258; /* Part of FTE_SQL*/ +int(float serveridx, float queryidx, float row, float column, __variant *ptr, int maxsize) sqlreadblob = #0:sqlreadblob; +string(float serveridx, __variant *ptr, int maxsize) sqlescapeblob = #0:sqlescapeblob; +#endif +typedef struct json_s *json_t; +accessor jsonnode : json_t; +jsonnode(string) json_parse = #0:json_parse; /* + Parses the given JSON string. */ + +void(jsonnode) json_free = #0:json_free; /* + Frees a json tree and all of its children. Must only be called on the root node. */ + +enum json_type_e : int +{ + JSON_TYPE_STRING, + JSON_TYPE_NUMBER, + JSON_TYPE_OBJECT, + JSON_TYPE_ARRAY, + JSON_TYPE_TRUE, + JSON_TYPE_FALSE, + JSON_TYPE_NULL +}; +json_type_e(jsonnode node) json_get_value_type = #0:json_get_value_type; /* + Get type of a JSON value. */ + +int(jsonnode node) json_get_integer = #0:json_get_integer; /* + Get an integer from a json node. */ + +float(jsonnode node) json_get_float = #0:json_get_float; /* + Get a float from a json node. */ + +string(jsonnode node) json_get_string = #0:json_get_string; /* + Get a string from a value. Returns a null string if its not a string type. */ + +jsonnode(jsonnode node, string) json_find_object_child = #0:json_find_object_child; /* + Find a child of a json object by name. Returns NULL if the handle couldn't be found. */ + +int(jsonnode node) json_get_length = #0:json_get_length; /* + Get the length of a json array or object. Returns 0 if its not an array. */ + +jsonnode(jsonnode node, int childindex) json_get_child_at_index = #0:json_get_child_at_index; /* + Get the nth child of a json array or object. Returns NULL if the index is out of range. */ + +string(jsonnode node) json_get_name = #0:json_get_name; /* + Gets the object's name (useful if you're using json_get_child_at_index to walk an object's children for whatever reason). */ + +string(string javascript) js_run_script = #0:js_run_script; /* + Runs the provided javascript snippet. This builtin functions only in emscripten builds, returning a null string on other systems (or if the script evaluates to null). */ + +#if defined(CSQC) || defined(SSQC) +int(string) stoi = #259; /* Part of FTE_QC_INTCONV + Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string. */ + +string(int) itos = #260; /* Part of FTE_QC_INTCONV + Converts the passed true integer into a base10 string. */ + +int(string) stoh = #261; /* Part of FTE_QC_INTCONV + Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P */ + +string(int) htos = #262; /* Part of FTE_QC_INTCONV + Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters. */ + +#endif +int(float) ftoi = #0:ftoi; /* Part of FTE_QC_INTCONV + Converts the given float into a true integer without depending on extended qcvm instructions. */ + +float(int, optional float shift, float mask=24) itof = #0:itof; /* Part of FTE_QC_INTCONV + Converts the given true integer into a float without depending on extended qcvm instructions. If shift and mask are specified then only specific parts of the integer will be cast to float. */ + +#if defined(CSQC) || defined(SSQC) +float(float modlindex, optional float useabstransforms) skel_create = #263; /* Part of FTE_CSQC_SKELETONOBJECTS + Allocates a new uninitiaised skeletal object, with enough bone info to animate the given model. + eg: self.skeletonobject = skel_create(self.modelindex); */ + +float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addfrac) skel_build = #264; /* Part of FTE_CSQC_SKELETONOBJECTS + Animation data (according to the entity's frame info) is pulled from the specified model and blended into the specified skeletal object. + If retainfrac is set to 0 on the first call and 1 on the others, you can blend multiple animations together according to the addfrac value. The final weight should be 1. Other values will result in scaling and/or other weirdness. You can use firstbone and lastbone to update only part of the skeletal object, to allow legs to animate separately from torso, use 0 for both arguments to specify all, as bones are 1-based. */ + +typedef struct +{ + int sourcemodelindex; /*frame data will be imported from this model, bones must be compatible*/ + int reserved; + int firstbone; + int lastbone; + float prescale; /*0 destroys existing data, 1 retains it*/ + float scale[4]; /*you'll need to do lerpfrac manually*/ + int animation[4]; + float animationtime[4]; + /*halflife models*/ + float subblend[2]; + float controllers[5]; +} skelblend_t; +float(float skel, int numblends, skelblend_t *weights, int structsize) skel_build_ptr = #0:skel_build_ptr; /* + Like skel_build, but slightly simpler. */ + +float(float skel) skel_get_numbones = #265; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrives the number of bones in the model. The valid range is 1<=bone<=numbones. */ + +string(float skel, float bonenum) skel_get_bonename = #266; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves the name of the specified bone. Mostly only for debugging. */ + +float(float skel, float bonenum) skel_get_boneparent = #267; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves which bone this bone's position is relative to. Bone 0 refers to the entity's position rather than an actual bone */ + +float(float skel, string tagname) skel_find_bone = #268; /* Part of FTE_CSQC_SKELETONOBJECTS + Finds a bone by its name, from the model that was used to create the skeletal object. */ + +vector(float skel, float bonenum) skel_get_bonerel = #269; /* Part of FTE_CSQC_SKELETONOBJECTS + Gets the bone position and orientation relative to the bone's parent. Return value is the offset, and v_forward, v_right, v_up contain the orientation. */ + +vector(float skel, float bonenum) skel_get_boneabs = #270; /* Part of FTE_CSQC_SKELETONOBJECTS + Gets the bone position and orientation relative to the entity. Return value is the offset, and v_forward, v_right, v_up contain the orientation. + Use gettaginfo for world coord+orientation. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_set_bone = #271; /* Part of FTE_CSQC_SKELETONOBJECTS + Sets a bone position relative to its parent. If the orientation arguments are not specified, v_forward+v_right+v_up are used instead. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_premul_bone = #272; /* Part of FTE_CSQC_SKELETONOBJECTS + Transforms a single bone by a matrix. You can use makevectors to generate a rotation matrix from an angle. */ + +void(float skel, float startbone, float endbone, vector org, optional vector fwd, optional vector right, optional vector up) skel_premul_bones = #273; /* Part of FTE_CSQC_SKELETONOBJECTS + Transforms an entire consecutive range of bones by a matrix. You can use makevectors to generate a rotation matrix from an angle, but you'll probably want to divide the angle by the number of bones. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_postmul_bone = #0:skel_postmul_bone; /* + Transforms a single bone by a matrix. You can use makevectors to generate a rotation matrix from an angle. */ + +void(float skeldst, float skelsrc, float startbone, float entbone) skel_copybones = #274; /* Part of FTE_CSQC_SKELETONOBJECTS + Copy bone data from one skeleton directly into another. */ + +void(float skel) skel_delete = #275; /* Part of FTE_CSQC_SKELETONOBJECTS + Deletes a skeletal object. The actual delete is delayed, allowing the skeletal object to be deleted in an entity's predraw function yet still be valid by the time the addentity+renderscene builtins need it. Also uninstanciates any ragdoll currently in effect on the skeletal object. */ + +float(float modidx, string framename) frameforname = #276; /* Part of FTE_CSQC_SKELETONOBJECTS + Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error. */ + +float(float modidx, float framenum) frameduration = #277; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves the duration (in seconds) of the specified framegroup. */ + +float(float modidx, int actionid) frameforaction = #0:frameforaction; /* + Returns a random frame/animation for the specified mod-defined action, or -1 if no animations have the specified action. */ + +void(float modidx, float framenum, __inout float basetime, float targettime, void(float timestamp, int code, string data) callback) processmodelevents = #0:processmodelevents; /* Part of FTE_GFX_MODELEVENTS + Calls a callback for each event that has been reached. Basetime is set to targettime. */ + +float(float modidx, float framenum, __inout float basetime, float targettime, __out int code, __out string data) getnextmodelevent = #0:getnextmodelevent; /* Part of FTE_GFX_MODELEVENTS + Reports the next event within a model's animation. Returns a boolean if an event was found between basetime and targettime. Writes to basetime,code,data arguments (if an event was found, basetime is set to the event's time, otherwise to targettime). + WARNING: this builtin cannot deal with multiple events with the same timestamp (only the first will be reported). */ + +float(float modidx, float framenum, int eventidx, __out float timestamp, __out int code, __out string data) getmodeleventidx = #0:getmodeleventidx; /* Part of FTE_GFX_MODELEVENTS + Reports an indexed event within a model's animation. Writes to timestamp,code,data arguments on success. Returns false if the animation/event/model was out of range/invalid. Does not consider looping animations (retry from index 0 if it fails and you know that its a looping animation). This builtin is more annoying to use than getnextmodelevent, but can be made to deal with multiple events with the exact same timestamp. */ + +#endif +#define dotproduct(v1,v2) ((vector)(v1)*(vector)(v2)) +vector(vector v1, vector v2) crossproduct = #0:crossproduct; /* Part of FTE_QC_CROSSPRODUCT + Small helper function to calculate the crossproduct of two vectors. */ + +#if defined(CSQC) || defined(SSQC) +float(entity pusher, vector move, vector amove) pushmove = #0:pushmove; +__variant(float action, optional vector pos, optional float radius, optional float quant, ...) terrain_edit = #278; /* Part of FTE_TERRAIN_MAP + Realtime terrain editing. Actions are the TEREDIT_ constants. */ + +typedef struct +{ + string shadername; + vector planenormal; + float planedist; + vector sdir; + float sbias; + vector tdir; + float tbias; +} brushface_t; +int(float modelidx, int brushid, brushface_t *out_faces, int maxfaces, int *out_contents) brush_get = #0:brush_get; /* Part of FTE_RAW_MAP + Queries a brush's information. You must pre-allocate the face array for the builtin to write to. Return value is the number of faces retrieved, 0 on error. */ + +int(float modelidx, brushface_t *in_faces, int numfaces, int contents, optional int brushid) brush_create = #0:brush_create; /* Part of FTE_RAW_MAP + Inserts a new brush into the model. Return value is the new brush's id. */ + +void(float modelidx, int brushid) brush_delete = #0:brush_delete; /* Part of FTE_RAW_MAP + Destroys the specified brush. */ + +float(float modelid, int brushid, int faceid, float selectedstate) brush_selected = #0:brush_selected; /* Part of FTE_RAW_MAP + Allows you to easily set transient visual properties of a brush. returns old value. selectedstate=-1 changes nothing (called for its return value). */ + +int(float modelid, int brushid, int faceid, vector *points, int maxpoints) brush_getfacepoints = #0:brush_getfacepoints; /* Part of FTE_RAW_MAP + Returns the list of verticies surrounding the given face. If face is 0, returns the center of the brush (if space for 1 point) or the mins+maxs (if space for 2 points). */ + +int(int faceid, brushface_t *in_faces, int numfaces, vector *points, int maxpoints) brush_calcfacepoints = #0:brush_calcfacepoints; /* Part of FTE_RAW_MAP + Determines the points of the specified face, if the specified brush were to actually be created. */ + +int(float modelid, vector *planes, float *dists, int numplanes, int *out_brushes, int *out_faces, int maxresults) brush_findinvolume = #0:brush_findinvolume; /* Part of FTE_RAW_MAP + Allows you to easily obtain a list of brushes+faces within the given bounding region. If out_faces is not null, the same brush might be listed twice. */ + +typedef struct +{ + string shadername; + int contents; + int cpwidth; + int cpheight; + int tesswidth; + int tessheight; + vector texinfo;/*scalex,y,rot*/ +} patchinfo_t; +typedef struct +{ + vector xyz; + vector rgb; float a; + float s, t; +} patchvert_t; +#define patch_delete(modelidx,patchidx) brush_delete(modelidx,patchidx) +int(float modelidx, int patchid, patchvert_t *out_controlverts, int maxcp, patchinfo_t *out_info) patch_getcp = #0:patch_getcp; /* + Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error. */ + +int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, __out patchinfo_t out_info) patch_getmesh = #0:patch_getmesh; /* + Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error. */ + +int(float modelidx, int oldpatchid, patchvert_t *in_controlverts, patchinfo_t in_info) patch_create = #0:patch_create; /* + Inserts a new patch into the model. Return value is the new patch's id. */ + +typedef struct +{ + vector dest; + int linkflags; + float radius; +} nodeslist_t; +void(entity ent, vector dest, int denylinkflags, void(entity ent, vector dest, int numnodes, nodeslist_t *nodelist) callback) route_calculate = #0:route_calculate; /* + Begin calculating a route. The callback function will be called once the route has finished being calculated. The route must be memfreed once it is no longer needed. The route must be followed in reverse order (ie: the first node that must be reached is at index numnodes-1). If no route is available then the callback will be called with no nodes. */ + +void(optional entity ent, optional vector neworigin) touchtriggers = #279; /* + Triggers a touch events between self and every SOLID_TRIGGER entity that it is in contact with. This should typically just be the triggers touch functions. Also optionally updates the origin of the moved entity. */ + +#endif +#ifdef SSQC +void(float buf, float fl) WriteFloat = #280; /* + Writes a full 32bit float without any data conversions at all, for full precision. */ + +void(float buf, __double dbl) WriteDouble = #0:WriteDouble; /* + Writes a full 64bit double-precision float without any data conversions at all, for excessive precision. */ + +void(float buf, int fl) WriteInt = #0:WriteInt; /* + Writes all 4 bytes of a 32bit integer without truncating to a float first before converting back to an int (unlike WriteLong does, but otherwise equivelent). */ + +void(float buf, __int64 fl) WriteInt64 = #0:WriteInt64; /* + Writes all 8 bytes of a 64bit integer. This uses variable-length coding and will send only a single byte for any value between -64 and 63. */ + +void(float buf, __uint64 fl) WriteUInt64 = #0:WriteUInt64; /* + Writes all 8 bytes of a 64bit unsigned integer. Values between 0-127 will be sent in a single byte. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(entity skelent, string dollcmd, float animskel) skel_ragupdate = #281; /* Part of FTE_QC_RAGDOLL_WIP + Updates the skeletal object attached to the entity according to its origin and other properties. + if animskel is non-zero, the ragdoll will animate towards the bone state in the animskel skeletal object, otherwise they will pick up the model's base pose which may not give nice results. + If dollcmd is not set, the ragdoll will update (this should be done each frame). + If the doll is updated without having a valid doll, the model's default .doll will be instanciated. + commands: + doll foo.doll : sets up the entity to use the named doll file + dollstring TEXT : uses the doll file directly embedded within qc, with that extra prefix. + cleardoll : uninstanciates the doll without destroying the skeletal object. + animate 0.5 : specifies the strength of the ragdoll as a whole + animatebody somebody 0.5 : specifies the strength of the ragdoll on a specific body (0 will disable ragdoll animations on that body). + enablejoint somejoint 1 : enables (or disables) a joint. Disabling joints will allow the doll to shatter. */ + +float*(float skel) skel_mmap = #282; /* Part of FTE_QC_RAGDOLL_WIP + Map the bones in VM memory. They can then be accessed via pointers. Each bone is 12 floats, the four vectors interleaved (sadly). */ + +void(entity ent, float bonenum, vector org, optional vector angorfwd, optional vector right, optional vector up) skel_set_bone_world = #283; /* Part of FTE_QC_RAGDOLL_WIP + Sets the world position of a bone within the given entity's attached skeletal object. The world position is dependant upon the owning entity's position. If no orientation argument is specified, v_forward+v_right+v_up are used for the orientation instead. If 1 is specified, it is understood as angles. If 3 are specified, they are the forawrd/right/up vectors to use. */ + +string(float modidx, float framenum) frametoname = #284; +string(float modidx, float skin) skintoname = #285; +float(float resourcetype, float tryload, string resourcename) resourcestatus = #286; /* + resourcetype must be one of the RESTYPE_ constants. Returns one of the RESSTATE_ constants. Tryload 0 is a query only. Tryload 1 will attempt to reload the content if it was flushed. */ + +#endif +hashtable(float tabsize, optional float defaulttype) hash_createtab = #287; /* Part of FTE_QC_HASHTABLES + Creates a hash table object. + The tabsize argument is a performance hint and should generally be set to something similar to the number of entries expected, typically a power of two assumption. Too high simply wastes memory, too low results in extra string compares but no actual bugs. + defaulttype must be one of the EV_* values, if specified. + The hash table with index 0 is a game-persistant table and will NEVER be returned by this builtin (except as an error return). */ + +void(hashtable table) hash_destroytab = #288; /* Part of FTE_QC_HASHTABLES + Destroys a hash table object. */ + +void(hashtable table, string name, __variant value, optional float typeandflags) hash_add = #289; /* Part of FTE_QC_HASHTABLES + Adds the given key with the given value to the table. + If flags&HASH_REPLACE, the old value will be removed, otherwise if flags&HASH_ADD then a duplicate entry will be added with a second value (can be obtained via hash_get's index argument). + The type argument describes how the value should be stored in saved games, as well as providing constraints with the hash_get function. While you can claim that all variables are just vectors, being more precise can result in less issues with tempstrings or saved games - be sure to be explicit with EV_STRING where appropriate because tempstrings may be reclaimed before the get (especially with saved games or table 0). */ + +__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index) hash_get = #290; /* Part of FTE_QC_HASHTABLES + Looks up the specified key name in the hash table. Returns deflt if the key was not found. + If requiretype is specified then the function will only consider entries of the matching type (allowing you to store both flags+strings under a single name without getting confused). + If index is specified then the function will ignore the first N entries with the same key (applicable only with entries added using HASH_ADD, not HASH_REPLACE), allowing you to store multiple entries. Keep querying higher indexes starting from 0 until it returns the deflt value. + You will usually need to cast the result of this function to a real datatype. */ + +__variant(hashtable table, string name) hash_delete = #291; /* Part of FTE_QC_HASHTABLES + removes the named key. returns the value of the object that was destroyed, or 0 on error. */ + +string(hashtable table, float idx) hash_getkey = #292; /* Part of FTE_QC_HASHTABLES + gets some random key name. add+delete can change return values of this, so don't blindly increment the key index if you're removing all. */ + +float(string name) checkcommand = #294; /* Part of FTE_QC_CHECKCOMMAND + Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist. */ + +string(string s) argescape = #295; /* + Marks up a string so that it can be reliably tokenized as a single argument later. */ + +#ifdef SSQC +void(string dest, string from, string cmd, string info) clusterevent = #0:clusterevent; /* + Only functions in mapcluster mode. Sends an event to whichever server the named player is on. The destination server can then dispatch the event to the client or handle it itself via the SV_ParseClusterEvent entrypoint. If dest is empty, the event is broadcast to ALL servers. If the named player can't be found, the event will be returned to this server with the cmd prefixed with 'error:'. */ + +string(entity player, optional string newnode) clustertransfer = #0:clustertransfer; /* + Only functions in mapcluster mode. Initiate transfer of the player to a different node. Can take some time. If dest is specified, returns null on error. Otherwise returns the current/new target node (or null if not transferring). */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(float mdlidx) modelframecount = #0:modelframecount; /* + Retrieves the number of frames in the specified model. */ + +#endif +#if defined(CSQC) || defined(MENU) +void() clearscene = #300; /* + Forgets all rentities, polygons, and temporary dlights. Resets all view properties to their default values. */ + +#endif +#ifdef CSQC +void(float mask) addentities = #301; /* + Walks through all entities effectively doing this: + if (ent.drawmask&mask){ if (!ent.predaw()) addentity(ent); } + If mask&MASK_DELTA, non-csqc entities, particles, and related effects will also be added to the rentity list. + If mask&MASK_STDVIEWMODEL then the default view model will also be added. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(entity ent) addentity = #302; /* + Copies the entity fields into a new rentity for later rendering via addscene. */ + +void(entity ent, vector lightdir, vector lightavg, vector lightrange, int reserved1=0,void*reserved2=0) addentity_lighting = #0:addentity_lighting; /* + Copies the entity fields into a new rentity for later rendering via addscene, but with explicit lighting info. */ + +#endif +#ifdef CSQC +void(entity ent) removeentity = #0:removeentity; /* + Undoes all addentities added to the scene from the given entity, without removing ALL entities (useful for splitscreen/etc, readd modified versions as desired). */ + +typedef float vec2[2]; +typedef float vec3[3]; +typedef float vec4[4]; +typedef struct trisoup_simple_vert_s {vec3 xyz;vec2 st;vec4 rgba;} trisoup_simple_vert_t; +void(string texturename, int flags, struct trisoup_simple_vert_s *verts, int *indexes, int numindexes) addtrisoup_simple = #0:addtrisoup_simple; /* + Adds the specified trisoup into the scene as additional geometry. This permits caching geometry to reduce builtin spam. Indexes are a triangle list (so eg quads will need 6 indicies to form two triangles). NOTE: this is not going to be a speedup over polygons if you're still generating lots of new data every frame. */ + +#endif +#if defined(CSQC) || defined(MENU) +#define setviewprop setproperty +float(float property, ...) setproperty = #303; /* + Allows you to override default view properties like viewport, fov, and whether the engine hud will be drawn. Different VF_ values have slightly different arguments, some are vectors, some floats. */ + +void() renderscene = #304; /* + Draws all entities, polygons, and particles on the rentity list (which were added via addentities or addentity), using the various view properties set via setproperty. There is no ordering dependancy. + The scene must generally be cleared again before more entities are added, as entities will persist even over to the next frame. + You may call this builtin multiple times per frame, but should only be called from CSQC_UpdateView. */ + +#endif +#ifdef CSQC +float(vector org, float radius, vector lightcolours, optional float style, optional string cubemapname, optional float pflags) dynamiclight_add = #305; /* + Adds a temporary dlight, ready to be drawn via addscene. Cubemap orientation will be read from v_forward/v_right/v_up. */ + +#endif +void(string texturename, optional float flags, optional float is2d) R_BeginPolygon = #306; /* + Specifies the shader to use for the following polygons, along with optional flags. + If is2d, the polygon will be drawn as soon as the EndPolygon call is made, rather than waiting for renderscene. This allows complex 2d effects. */ + +void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex = #307; /* + Specifies a polygon vertex with its various properties. */ + +void() R_EndPolygon = #308; /* + Ends the current polygon. At least 3 verticies must have been specified. You do not need to call beginpolygon again if you wish to draw another polygon with the same shader. */ + +#ifdef CSQC +void(float radius, vector texcoordbias) R_EndPolygonRibbon = #0:R_EndPolygonRibbon; /* + Ends the current primitive and duplicates each vertex sideways into a ribbon. The texcoordbias will be added to each duplicated vertex allowing for regular 2d textures. At least 2 verticies must have been specified. You do not need to call beginpolygon again if you wish to draw another polygon with the same shader. */ + +#endif +#if defined(CSQC) || defined(MENU) +#define getviewprop getproperty +__variant(float property) getproperty = #309; /* + Retrieve a currently-set (typically view) property, allowing you to read the current viewport or other things. Due to cheat protection, certain values may be unretrievable. */ + +#endif +#ifdef CSQC +vector (vector v) unproject = #310; /* + Transform a 2d screen-space point (with depth) into a 3d world-space point, according the various origin+angle+fov etc settings set via setproperty. */ + +vector (vector v) project = #311; /* + Transform a 3d world-space point into a 2d screen-space point, according the various origin+angle+fov etc settings set via setproperty. */ + +#endif +#if defined(CSQC) || defined(MENU) +float(vector pos, vector size, float alignflags, string text) drawtextfield = #0:drawtextfield; /* + Draws a multi-line block of text, including word wrapping and alignment. alignflags bits are RTLB, typically 3. Returns the total number of lines. */ + +#endif +#ifdef CSQC +void(float width, vector pos1, vector pos2, vector rgb, float alpha, optional float drawflag) drawline = #315; /* + Draws a 2d line between the two 2d points. */ + +float(string name) iscachedpic = #316; /* + Checks to see if the image is currently loaded. Engines might lie, or cache between maps. */ + +string(string name, optional float flags) precache_pic = #317; /* + Forces the engine to load the named image. Flags are a bitmask of the PRECACHE_PIC_* flags. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(string imagename, int width, int height, void *pixeldata, optional int datasize, optional int format) r_uploadimage = #0:r_uploadimage; /* Part of FTE_CSQC_RAWIMAGES + Updates a texture with the specified rgba data (uploading it to the gpu). Will be created if needed. If datasize is specified then the image is decoded (eg .ktx or .dds data) instead of being raw R8G8B8A data. You'll typically want shaderforname to also generate a shader to use the texture. */ + +int*(string filename, __out int width, __out int height, __out int format) r_readimage = #0:r_readimage; /* Part of FTE_CSQC_RAWIMAGES + Reads and decodes an image from disk, providing raw R8G8B8A8 pixel data. Should not be used for dds or ktx etc formats. Returns __NULL__ if the image could not be read for any reason. Use memfree to free the data once you're done with it. */ + +#endif +#ifdef CSQC +#define draw_getimagesize drawgetimagesize +vector(string picname) drawgetimagesize = #318; /* + Returns the dimensions of the named image. Images specified with .lmp should give the original .lmp's dimensions even if texture replacements use a different resolution. WARNING: this function may be slow if used without or directly after its initial precache_pic. */ + +void(string name) freepic = #319; /* + Tells the engine that the image is no longer needed. The image will appear to be new the next time its needed. */ + +string(string modelname, int frame, float frametime) spriteframe = #0:spriteframe; /* + Obtains a suitable shader name to draw a sprite's shader via drawpic/R_BeginPolygon/etc, instead of needing to create a scene. */ + +float(vector position, float character, vector size='8 8', vector rgb='1 1 1', float alpha=1, optional float drawflag=0) drawcharacter = #320; /* + Draw the given quake character at the given position. + If flag&4, the function will consider the char to be a unicode char instead (or display as a ? if outside the 32-127 range). + size should normally be something like '8 8 0'. + rgb should normally be '1 1 1' + alpha normally 1. + Software engines may assume the named defaults. + Note that ALL text may be rescaled on the X axis due to variable width fonts. The X axis may even be ignored completely. */ + +float(vector position, string text, vector size, vector rgb, float alpha, optional float drawflag) drawrawstring = #321; /* + Draws the specified string without using any markup at all, even in engines that support it. + If UTF-8 is globally enabled in the engine, then that encoding is used (without additional markup), otherwise it is raw quake chars. + Software engines may assume a size of '8 8 0', rgb='1 1 1', alpha=1, flag&3=0, but it is not an error to draw out of the screen. */ + +float(vector position, string pic, vector size, vector rgb='1 1 1', float alpha=1, optional float drawflag=0) drawpic = #322; /* + Draws an shader within the given 2d screen box. Software engines may omit support for rgb+alpha, but must support rescaling, and must clip to the screen without crashing. */ + +float(vector position, vector size, vector rgb, float alpha, optional float drawflag) drawfill = #323; /* + Draws a solid block over the given 2d box, with given colour, alpha, and blend mode (specified via flags). + flags&3=0 simple blend. + flags&3=1 additive blend */ + +void(float x, float y, float width, float height) drawsetcliparea = #324; /* + Specifies a 2d clipping region (aka: scissor test). 2d draw calls will all be clipped to this 2d box, the area outside will not be modified by any 2d draw call (even 2d polygons). */ + +void(void) drawresetcliparea = #325; /* + Reverts the scissor/clip area to the whole screen. */ + +float(vector position, string text, vector size='8 8', vector rgb='1 1 1', float alpha=1, float drawflag=0) drawstring = #326; /* + Draws a string, interpreting markup and recolouring as appropriate. */ + +float(string text, float usecolours, vector fontsize='8 8') stringwidth = #327; /* + Calculates the width of the screen in virtual pixels. If usecolours is 1, markup that does not affect the string width will be ignored. Will always be decoded as UTF-8 if UTF-8 is globally enabled. + If the char size is not specified, '8 8 0' will be assumed. */ + +void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, optional float drawflag) drawsubpic = #328; /* + Draws a rescaled subsection of an image to the screen. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(vector pivot, vector mins, vector maxs, string pic, vector rgb, float alpha, float angle) drawrotpic = #0:drawrotpic; /* + Draws an image rotating at the pivot. To rotate in the center, use mins+maxs of half the size with mins negated. Angle is in degrees. */ + +void(vector pivot, vector mins, vector maxs, string pic, vector txmin, vector txsize, vector rgb, vector alphaandangles) drawrotsubpic = #0:drawrotsubpic; /* + Overcomplicated draw function for over complicated people. Positions follow drawrotpic, while texture coords follow drawsubpic. Due to argument count limitations in builtins, the alpha value and angles are combined into separate fields of a vector (tip: use fteqcc's [alpha, angle] feature. */ + +#endif +#ifdef CSQC +#define getstati_punf(stnum) (float)(__variant)getstati(stnum) +int(float stnum) getstati = #330; /* + Retrieves the full precision of a stat registered as EV_INTEGER. */ + +#define getstatbits getstatf +float(float stnum, optional float firstbit, optional float bitcount) getstatf = #331; /* + Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, then this builtin acts as getstati combined with itof, and which should be used for STAT_ITEMS (but not other stats). */ + +string(float stnum) getstats = #332; /* + Retrieves the value of the given EV_STRING stat, as a tempstring. + Older engines may use 4 consecutive integer stats, with a limit of 15 chars (yes, really. 15.), but FTE Quake uses a separate namespace for string stats and has a much higher length limit. */ + +__variant(float playernum, float statnum, float stattype) getplayerstat = #0:getplayerstat; /* + Retrieves a specific player's stat, matching the type specified on the server. This builtin is primarily intended for mvd playback where ALL players are known. Return value matches the specified EV_ stattype. For EV_ENTITY, world will be returned if the entity is not in the pvs, use type-punning with EV_INTEGER to get the entity number if you just want to see if its set. STAT_ITEMS should be queried as an EV_INTEGER on account of runes and items2 being packed into the upper bits. */ + +void(entity e, float mdlindex) setmodelindex = #333; /* + Sets a model by precache index instead of by name. Otherwise identical to setmodel. */ + +#endif +#if defined(CSQC) || defined(SSQC) +string(float mdlindex) modelnameforindex = #334; /* + Retrieves the name of the model based upon a precache index. This can be used to reduce csqc network traffic by enabling model matching (with getmodelindex). */ + +string(float sndindex) soundnameforindex = #0:soundnameforindex; /* + Retrieves the name of the sound based upon a precache index. This can be used to reduce csqc network traffic by enabling sound matching (with getsoundindex). */ + +float(string effectname) particleeffectnum = #335; /* Part of DP_ENT_TRAILEFFECTNUM, FTE_SV_POINTPARTICLES + Precaches the named particle effect. If your effect name is of the form 'foo.bar' then particles/foo.cfg will be loaded by the client if foo.bar was not already defined. + Different engines will have different particle systems, this specifies the QC API only. */ + +void(float effectnum, entity ent, vector start, vector end) trailparticles = #336; /* Part of FTE_SV_POINTPARTICLES + Draws the given effect between the two named points. If ent is not world, distances will be cached in the entity in order to avoid framerate dependancies. The entity is not otherwise used. */ + +void(float effectnum, vector origin, optional vector dir, optional float count) pointparticles = #337; /* Part of FTE_SV_POINTPARTICLES + Spawn a load of particles from the given effect at the given point traveling or aiming along the direction specified. The number of particles are scaled by the count argument. + For regular particles, the dir vector is multiplied by the 'veladd' property (while orgadd will push the particles along it). Decals will use it as a hint to align to the correct surface. In both cases, it should normally be a unit vector, but other lengths will still work. If it has length 0 then FTE will assume downwards. */ + +#endif +#ifdef CSQC +void(string s, ...) cprint = #338; /* + Print into the center of the screen just as ssqc's centerprint would appear. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(string s, ...) print = #339; /* Part of DP_SV_PRINT + Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar). */ + +#endif +#ifdef CSQC +string(float keynum) keynumtostring = #340; /* + Returns a hunam-readable name for the given keycode, as a tempstring. */ + +#endif +#ifdef MENU +DEP string(float keynum) keynumtostring_csqc = #340; /* + Returns a hunam-readable name for the given keycode, as a tempstring. */ + +#endif +#ifdef CSQC +float(string keyname) stringtokeynum = #341; /* + Looks up the key name in the same way that the bind command would, returning the keycode for that key. */ + +#endif +#ifdef MENU +DEP float(string keyname) stringtokeynum_csqc = #341; /* + Looks up the key name in the same way that the bind command would, returning the keycode for that key. */ + +#endif +#if defined(CSQC) || defined(MENU) +string(float keynum) getkeybind = #342; /* + Returns the current binding for the given key (returning only the command executed when no modifiers are pressed). */ + +void(float usecursor, optional string cursorimage, optional vector hotspot, optional float scale) setcursormode = #343; /* + Pass TRUE if you want the engine to release the mouse cursor (absolute input events + touchscreen mode). Pass FALSE if you want the engine to grab the cursor (relative input events + standard looking). If the image name is specified, the engine will use that image for a cursor (use an empty string to clear it again), in a way that will not conflict with the console. Images specified this way will be hardware accelerated, if supported by the platform/port. */ + +float(float effective) getcursormode = #0:getcursormode; /* + Reports the cursor mode this module previously attempted to use. If 'effective' is true, reports the cursor mode currently active (if was overriden by a different module which has precidence, for instance, or if there is only a touchscreen and no mouse). */ + +#endif +#ifdef CSQC +vector() getmousepos = #344; /* + Nasty convoluted DP extension. Typically returns deltas instead of positions. Use CSQC_InputEvent instead for such things in csqc mods. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(vector newpos) setmousepos = #0:setmousepos; /* + Warps the mouse cursor to the given location. Should normally only be done following setcursormode(TRUE,...). The warp MAY be visible through *_InputEvent, but normally be seen as an IE_ABSMOUSE event anyway. Not all systems support cursor warping (or even cursors), so this is a hint only and you should not depend upon it. */ + +#endif +#ifdef CSQC +float(float inputsequencenum) getinputstate = #345; /* + Looks up an input frame from the log, setting the input_* globals accordingly. + The sequence number range used for prediction should normally be servercommandframe < sequence <= clientcommandframe. + The sequence equal to clientcommandframe will change between input frames. */ + +void(float sens) setsensitivityscaler = #346; /* + Temporarily scales the player's mouse sensitivity based upon something like zoom, avoiding potential cvar saving and thus corruption. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(entity ent) runstandardplayerphysics = #347; /* + Perform the engine's standard player movement prediction upon the given entity using the input_* globals to describe movement. */ + +#endif +#ifdef CSQC +string(float playernum, string keyname) getplayerkeyvalue = #348; /* + Look up a player's userinfo, to discover things like their name, topcolor, bottomcolor, skin, team, *ver. + Also includes scoreboard info like frags, ping, pl, userid, entertime, as well as voipspeaking and voiploudness. */ + +float(float playernum, string keyname, optional float assumevalue) getplayerkeyfloat = #0:getplayerkeyfloat; /* + Cheaper version of getplayerkeyvalue that avoids the need for so many tempstrings. */ + +int(float playernum, string keyname, optional void *outptr, int size) getplayerkeyblob = #0:getplayerkeyblob; /* Part of FTE_INFOBLOBS + Obtains a copy of the full data blob. Will write up to size bytes but return the full size. Does not null terminate (but memalloc(ret+1) will, if you want to cast the buffer to a string), and the blob may contain embedded nulls. Ignores all special keys, returning only what is actually there. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(float seat, string keyname, string newvalue) setlocaluserinfo = #0:setlocaluserinfo; /* + Change a userinfo key for the specified local player seat, equivelent to the setinfo console command. The server will normally forward the setting to other clients. */ + +string(float seat, string keyname) getlocaluserinfo = #0:getlocaluserinfo; /* + Reads a local userinfo key for the specified local player seat. This is not quite the same as getplayerkeyvalue, due to latency and possible serverside filtering. */ + +void(float seat, string keyname, void *outptr, int size) setlocaluserinfoblob = #0:setlocaluserinfoblob; /* Part of FTE_INFOBLOBS + Sets the userinfo key to a blob that may contain nulls etc. Keys with a leading underscore will be visible to only the server (for user-specific binary settings). */ + +int(float seat, string keyname, void *outptr, int maxsize) getlocaluserinfoblob = #0:getlocaluserinfoblob; /* Part of FTE_INFOBLOBS + Obtains a copy of the full data blob. Will write up to size bytes but return the full size. Does not null terminate (but memalloc(ret+1) will, if you want to cast the buffer to a string), and the blob may contain embedded nulls. Ignores all special keys, returning only what is actually there. */ + +#endif +#ifdef SSQC +int(string keyname, optional void *outptr, int size) getlocalinfo = #0:getlocalinfo; /* + Obtains a copy of a data blob (with spaces) from the server's private localinfo. Will write up to size bytes and return the actual size. Does not null terminate (but memalloc(ret+1) will, if you want to cast the buffer to a string), and the blob may contain embedded nulls. Ignores all special keys, returning only what is actually there. */ + +void(string keyname, optional void *outptr, int size) setlocalinfo = #0:setlocalinfo; /* + Changes the server's private localinfo. This data will be available for the following map, and will *usually* reload with saved games. */ + +#endif +#if defined(CSQC) || defined(MENU) +float() isdemo = #349; /* + Returns if the client is currently playing a demo or not. Returns 2 when playing an mvd (where other player's stats can be queried, or the pov can be changed freely). */ + +#endif +#ifdef CSQC +float() isserver = #350; /* + Returns non-zero whenever the local console can directly affect the server (ie: listen servers or single-player). Compat note: DP returns 0 for single-player. */ + +void(vector origin, vector forward, vector right, vector up, optional float reverbtype) SetListener = #351; /* + Sets the position of the view, as far as the audio subsystem is concerned. This should be called once per CSQC_UpdateView as it will otherwise revert to default. For reverbtype, see setup_reverb or treat as 'underwater'. */ + +typedef struct { + float flDensity; + float flDiffusion; + float flGain; + float flGainHF; + float flGainLF; + float flDecayTime; + float flDecayHFRatio; + float flDecayLFRatio; + float flReflectionsGain; + float flReflectionsDelay; + vector flReflectionsPan; + float flLateReverbGain; + float flLateReverbDelay; + vector flLateReverbPan; + float flEchoTime; + float flEchoDepth; + float flModulationTime; + float flModulationDepth; + float flAirAbsorptionGainHF; + float flHFReference; + float flLFReference; + float flRoomRolloffFactor; + int iDecayHFLimit; +} reverbinfo_t; +void(float reverbslot, reverbinfo_t *reverbinfo, int sizeofreverinfo_t) setup_reverb = #0:setup_reverb; /* Part of FTE_CSQC_REVERB + Reconfigures a reverb slot for weird effects. Slot 0 is reserved for no effects. Slot 1 is reserved for underwater effects. Reserved slots will be reinitialised on snd_restart, but can otherwise be changed. These reverb slots can be activated with SetListener. Note that reverb will currently only work when using OpenAL. */ + +#endif +void(string cmdname) registercommand = #352; /* + Register the given console command, for easy console use. + Console commands that are later used will invoke CSQC_ConsoleCommand/m_consolecommand/ConsoleCmd according to module. */ + +float(entity ent) wasfreed = #353; /* + Quickly check to see if the entity is currently free. This function is only valid during the half-second non-reuse window, after that it may give bad results. Try one second to make it more robust. */ + +#if defined(CSQC) || defined(SSQC) +string(string key) serverkey = #354; /* + Look up a key in the server's public serverinfo string. If the key contains binary data then it will be truncated at the first null. */ + +float(string key, optional float assumevalue) serverkeyfloat = #0:serverkeyfloat; /* + Version of serverkey that returns the value as a float (which avoids tempstrings). */ + +int(string key, optional void *ptr, int maxsize) serverkeyblob = #0:serverkeyblob; /* Part of FTE_INFOBLOBS + Version of serverkey that returns data as a blob (ie: binary data that may contain nulls). Returns the full blob size, even if truncated (pass maxsize=0 to query required storage). */ + +#endif +#ifdef SSQC +void(string key, void *ptr, optional int size) setserverkey = #0:setserverkey; /* + Changes the server's serverinfo. */ + +#endif +#ifdef CSQC +string(optional string resetstring) getentitytoken = #355; /* + Grab the next token in the map's entity lump. + If resetstring is not specified, the next token will be returned with no other sideeffects. + If empty, will reset from the map before returning the first token, probably {. + If not empty, will tokenize from that string instead. + Always returns tempstrings. */ + +#endif +#if defined(CSQC) || defined(MENU) +float(string s) findfont = #356; /* Part of DP_GFX_FONTS + Looks up a named font slot. Matches the actual font name as a last resort. */ + +float(string fontname, string fontmaps, string sizes, float slot, optional float fix_scale, optional float fix_voffset) loadfont = #357; /* Part of DP_GFX_FONTS + too convoluted for me to even try to explain correct usage. Try drawfont = loadfont("", "cour", "16", -1, 0, 0); to switch to the courier font (optimised for 16 virtual pixels high) ('cour' requires mscorefonts installed in linux). Additionally you can add "outline=1" as an extra token in the sizes string, to have more readable outlined fonts. */ + +#endif +#ifdef CSQC +void(string evname, string evargs, ...) sendevent = #359; /* + Invoke CSEv_evname_evargs in ssqc. evargs must be a string of initials refering to the types of the arguments to pass. v=vector, e=entity(.entnum field is sent), f=float, i=int. 6 arguments max - you can get more if you pack your floats into vectors. */ + +float() readbyte = #360; /* + Reads an unsigned 8-bit value, pair with WriteByte. */ + +float() readchar = #361; /* + Reads a signed 8-bit value. Paired with WriteChar. */ + +float() readshort = #362; /* + Reads a signed 16-bit value. Paired with WriteShort. */ + +float() readlong = #363; /* + Reads a signed 32-bit value. Paired with WriteLong or WriteInt. */ + +float() readcoord = #364; /* + Reads a value matching the unspecified precision written ONLY by WriteCoord. */ + +float() readangle = #365; /* + Reads a value matching the unspecified precision written ONLY by WriteAngle. */ + +string() readstring = #366; /* + Reads a null-terminated string. */ + +float() readfloat = #367; /* + Reads a float without any truncation nor conversions. Data MUST have originally been written with WriteFloat. */ + +__double() readdouble = #0:readdouble; /* + Reads a double-precision float without any truncation nor conversions. Data MUST have originally been written with WriteDouble. */ + +int() readint = #0:readint; /* + Reads a 32bit int without any conversions to float, otherwise interchangable with readlong. */ + +__int64() readint64 = #0:readint64; /* + Reads a 64bit signed int. Paired with WriteInt64. */ + +__uint64() readuint64 = #0:readuint64; /* + Reads a 64bit unsigned int. Paired with WriteUInt64. */ + +float() readentitynum = #368; /* + Reads the serverside index of an entity, paired with WriteEntity. There may be nothing else known about the entity yet, so the result typically needs to be saved as-is and re-looked up each frame. This can be done via getentity(NUM, GE_*) for non-csqc ents, or findentity(world,entnum,NUM) - both of which can fail due to latency. */ + +float(string modelname, float(float isnew) updatecallback, float flags) deltalisten = #371; /* + Specifies a per-modelindex callback to listen for engine-networking entity updates. Such entities are automatically interpolated by the engine (unless flags specifies not to). + The various standard entity fields will be overwritten each frame before the updatecallback function is called. */ + +float(vector org, float radius, vector rgb) dynamiclight_spawnstatic = #0:dynamiclight_spawnstatic; /* + Creates a static persistent light at the given position with the specified colour. Additional properties must be set via dynamiclight_set. */ + +__variant(float lno, float fld) dynamiclight_get = #372; /* + Retrieves a property from the given dynamic/rt light. Return type depends upon the light field requested. */ + +void(float lno, float fld, __variant value) dynamiclight_set = #373; /* + Changes a property on the given dynamic/rt light. Value type depends upon the light field to be changed. */ + +string(float efnum, float body) particleeffectquery = #374; /* + Retrieves either the name or the body of the effect with the given number. The effect body is regenerated from internal state, and can be changed before being reapplied via the localcmd builtin. */ + +void(string shadername, vector origin, vector up, vector side, vector rgb, float alpha) adddecal = #375; /* + Adds a temporary clipped decal shader to the scene, centered at the given point with given orientation. Will be drawn by the next renderscene call, and freed by the next clearscene call. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(entity e, string skinfilename, optional string skindata) setcustomskin = #376; /* Part of FTE_QC_CUSTOMSKINS + Sets an entity's skin overrides to a new skin object. Releases the entities old skin (refcounted). */ + +#endif +#ifdef CSQC +float(string skinfilename, optional string skindata) loadcustomskin = #377; /* Part of FTE_QC_CUSTOMSKINS + Creates a new skin object and returns it. These are custom per-entity surface->shader lookups. The skinfilename/data should be in .skin format: + surfacename,shadername - makes the named surface use the named shader (legacy format for compat with q3) + replace "surfacename" "shadername" - non-legacy equivalent. + qwskin "foo" - use an unmodified quakeworld player skin (including crop+repalette rules) + q1lower 0xff0000 - specify an override for the entity's lower colour, in this case to red + q1upper 0x0000ff - specify an override for the entity's lower colour, in this case to blue + compose "surfacename" "shader" "imagename@x,y:w,h$s,t,s2,t2?r,g,b,a" - compose a skin texture from multiple images. + The texture is determined to be sufficient to hold the first named image, additional images can be named as extra tokens on the same line. + Use a + at the end of the line to continue reading image tokens from the next line also, the named shader must use 'map $diffuse' to read the composed texture (compatible with the defaultskin shader). Must be matched with a releasecustomskin call later, and is pointless without applycustomskin. */ + +void(entity e, float skinobj) applycustomskin = #378; /* Part of FTE_QC_CUSTOMSKINS + Updates the entity's custom skin (refcounted). */ + +void(float skinobj) releasecustomskin = #379; /* Part of FTE_QC_CUSTOMSKINS + Lets the engine know that the skin will no longer be needed. Thanks to refcounting any ents with the skin already applied will retain their skin until later changed. It is valid to destroy a skin just after applying it to an ent in the same function that it was created in, as the skin will only be destroyed once its refcount rops to 0. */ + +void(float devid, float amp_low, float amp_high, float duration) gp_rumble = #0:gp_rumble; /* + Sends a single rumble event to the game-pad specified in devid. Every time you call this, the previous effect is cancelled out. */ + +void(float devid, float left, float right, float duration) gp_rumbletriggers = #0:gp_rumbletriggers; /* + Makes the analog triggers rumble of the specified game-pad, like gp_rumble() one call cancels out the previous one on the device. */ + +void(float devid, vector color) gp_setledcolor = #0:gp_setledcolor; /* + Updates the game-pad LED color. */ + +void(float devid, /*const*/ void *data, int size) gp_settriggerfx = #0:gp_settriggerfx; /* + Sends a specific effect packet to the controller. On the PlayStation 5's DualSense that can adjust the tension on the analog triggers. */ + +#endif +__variant*(int size) memalloc = #384; /* Part of FTE_MEMALLOC + Allocate an arbitary block of memory */ + +void(__variant *ptr) memfree = #385; /* Part of FTE_MEMALLOC + Frees a block of memory that was allocated with memfree */ + +void(__variant *dst, __variant *src, int size) memcpy = #386; /* Part of FTE_MEMALLOC + Copys memory from one location to another */ + +void(__variant *dst, int val, int size) memfill8 = #387; /* Part of FTE_MEMALLOC + Sets an entire block of memory to a specified value. Pretty much always 0. */ + +__variant(__variant *dst, float ofs) memgetval = #388; /* + Looks up the 32bit value stored at a pointer-with-offset. */ + +void(__variant *dst, float ofs, __variant val) memsetval = #389; /* + Changes the 32bit value stored at the specified pointer-with-offset. */ + +__variant*(__variant *base, float ofs) memptradd = #390; /* + Perform some pointer maths. Woo. */ + +float(string s) memstrsize = #0:memstrsize; /* + strlen, except ignores utf-8 */ + +#if defined(CSQC) || defined(MENU) +string(string conname, string field, optional string newvalue) con_getset = #391; /* Part of FTE_CSQC_ALTCONSOLES + Reads or sets a property from a console object. The old value is returned. Iterrate through consoles with the 'next' field. Valid properties: title, name, next, unseen, markup, forceutf8, close, clear, hidden, linecount */ + +void(string conname, string messagefmt, ...) con_printf = #392; /* Part of FTE_CSQC_ALTCONSOLES + Prints onto a named console. */ + +void(string conname, vector pos, vector size, float fontsize) con_draw = #393; /* Part of FTE_CSQC_ALTCONSOLES + Draws the named console. */ + +float(string conname, float inevtype, float parama, float paramb, float paramc) con_input = #394; /* Part of FTE_CSQC_ALTCONSOLES + Forwards input events to the named console. Mouse updates should be absolute only. */ + +void(string newcaption) setwindowcaption = #0:setwindowcaption; /* Part of FTE_CSQC_WINDOWCAPTION + Replaces the title of the game window, as seen when task switching or just running in windowed mode. */ + +float() cvars_haveunsaved = #0:cvars_haveunsaved; /* + Returns true if any archived cvar has an unsaved value. */ + +#endif +float(entity e, float nowreadonly) entityprotection = #0:entityprotection; /* + Changes the protection on the specified entity to protect it from further edits from QC. The return value is the previous setting. Note that this can be used to unprotect the world, but doing so long term is not advised as you will no longer be able to detect invalid entity references. Also, world is not networked, so results might not be seen by clients (or in other words, world.avelocity_y=64 is a bad idea). */ + +#ifdef CSQC +string(vector pos) getlocationname = #0:getlocationname; /* + Looks up the specified position in the current map's .loc file and reports the nearest marked name. */ + +#endif +#ifdef MENU +void(int cliptype) clipboard_get = #0:clipboard_get; /* + Attempts to query the system clipboard. Any pasted text will be returned via Menu_InputEvent */ + +#endif +#if defined(CSQC) || defined(MENU) +void(int cliptype, string text) clipboard_set = #0:clipboard_set; /* + Changes the system clipboard to the specified text. */ + +#endif +#ifdef SSQC +entity(float entnum, optional __out float wasspawned) respawnedict = #0:respawnedict; /* + Acts like edict_num returning a specific entity number, but also marks it as spawned. If it was previously spawned then all of its prior field data will be LOST (you may wish to use wasfreed(edict_num(idx)) to check. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(entity from, optional entity to) copyentity = #400; /* Part of DP_QC_COPYENTITY + Copies all fields from one entity to another. */ + +#endif +#ifdef SSQC +__deprecated("No RGB support.") void(entity ent, float colours) setcolor = #401; /* Part of DP_SV_SETCOLOR + Changes a player's colours. The bits 0-3 are the lower/trouser colour, bits 4-7 are the upper/shirt colours. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(.string field, string match, optional .entity chainfield) findchain = #402; /* Part of DP_QC_FINDCHAIN*/ +entity(.float fld, float match, optional .entity chainfield) findchainfloat = #403; /* Part of DP_QC_FINDCHAINFLOAT*/ +void(vector org, string modelname, float startframe, float endframe, float framerate) effect = #404; /* Part of DP_SV_EFFECT + Spawns a self-animating sprite */ + +void(vector org, vector dir, float count) te_blood = #405; /* Part of DP_TE_BLOOD*/ +void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower = #406; /* Part of _DP_TE_BLOODSHOWER*/ +void(vector org, vector color) te_explosionrgb = #407; /* Part of DP_TE_EXPLOSIONRGB*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube = #408; /* Part of DP_TE_PARTICLECUBE*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain = #409; /* Part of DP_TE_PARTICLERAIN*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow = #410; /* Part of DP_TE_PARTICLESNOW*/ +void(vector org, vector vel, float howmany) te_spark = #411; /* Part of DP_TE_SPARK*/ +void(vector org) te_gunshotquad = #412; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_spikequad = #413; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_superspikequad = #414; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_explosionquad = #415; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_smallflash = #416; /* Part of DP_TE_SMALLFLASH*/ +void(vector org, float radius, float lifetime, vector color) te_customflash = #417; /* Part of DP_TE_CUSTOMFLASH*/ +void(vector org, optional float count) te_gunshot = #418; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_spike = #419; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_superspike = #420; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_explosion = #421; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_tarexplosion = #422; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_wizspike = #423; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_knightspike = #424; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_lavasplash = #425; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_teleport = #426; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org, float color, float colorlength) te_explosion2 = #427; /* Part of DP_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning1 = #428; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning2 = #429; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning3 = #430; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_beam = #431; /* Part of DP_TE_STANDARDEFFECTBUILTINS*/ +void(vector dir) vectorvectors = #432; /* Part of DP_QC_VECTORVECTORS*/ +void(vector org) te_plasmaburn = #433; /* Part of _DP_TE_PLASMABURN*/ +float(entity e, float s) getsurfacenumpoints = #434; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s, float n) getsurfacepoint = #435; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s) getsurfacenormal = #436; /* Part of DP_QC_GETSURFACE*/ +string(entity e, float s) getsurfacetexture = #437; /* Part of DP_QC_GETSURFACE*/ +float(entity e, vector p) getsurfacenearpoint = #438; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; /* Part of DP_QC_GETSURFACE*/ +#endif +#ifdef MENU +strbuf() buf_create = #440; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle) buf_del = #441; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle) buf_getsize = #442; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle_from, float bufhandle_to) buf_copy = #443; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float sortprefixlen, float backward) buf_sort = #444; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, string glue) buf_implode = #445; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, float string_index) bufstr_get = #446; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index, string str) bufstr_set = #447; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle, string str, float ordered) bufstr_add = #448; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index) bufstr_free = #449; /* Part of DP_QC_STRINGBUFFERS*/ +float(string name) iscachedpic = #451; +string(string name, optional float flags) precache_pic = #452; +float(vector position, float character, vector scale, vector rgb, float alpha, optional float flag) drawcharacter = #454; +float(vector position, string text, vector scale, vector rgb, float alpha, optional float flag) drawrawstring = #455; +float(vector position, string pic, vector size, vector rgb, float alpha, optional float flag) drawpic = #456; +float(vector position, vector size, vector rgb, float alpha, optional float flag) drawfill = #457; +void(float x, float y, float width, float height) drawsetcliparea = #458; +void(void) drawresetcliparea = #459; +vector(string picname) drawgetimagesize = #460; +void(float width, vector pos1, vector pos2) drawline = #466; +float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring = #467; +float(string text, float usecolours, vector fontsize='8 8') stringwidth = #468; +void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, float flag) drawsubpic = #469; +#endif +#ifdef SSQC +void(entity e, string s) clientcommand = #440; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +#endif +#if defined(CSQC) || defined(SSQC) +float(string s) tokenize = #441; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +string(float n) argv = #442; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +void(entity e, entity tagentity, string tagname) setattachment = #443; /* Part of DP_GFX_QUAKE3MODELTAGS*/ +searchhandle(string pattern, enumflags:float{SB_CASEINSENSITIVE=1<<0,SB_FULLPACKAGEPATH=1<<1,SB_ALLOWDUPES=1<<2,SB_FORCESEARCH=1<<3} flags, float quiet, optional string filterpackage) search_begin = #444; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE + initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle. SB_FULLPACKAGEPATH interprets the filterpackage arg as a full package path to avoid gamedir ambiguity, equivelent to whichpack's WP_FULLPACKAGEPATH flag. SB_ALLOWDUPES allows returning multiple entries with the same name (but different package, useful with search_fopen). SB_FORCESEARCH requires use of the filterpackage and SB_FULLPACKAGEPATH flag, initiating searches from gamedirs/packages which are not currently active. */ + +void(searchhandle handle) search_end = #445; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE*/ +float(searchhandle handle) search_getsize = #446; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE + Retrieves the number of files that were found. */ + +string(searchhandle handle, float num) search_getfilename = #447; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE + Retrieves name of one of the files that was found by the initial search. */ + +#endif +float(searchhandle handle, float num) search_getfilesize = #0:search_getfilesize; /* Part of FTE_QC_FS_SEARCH_SIZEMTIME + Retrieves the size of one of the files that was found by the initial search. */ + +string(searchhandle handle, float num) search_getfilemtime = #0:search_getfilemtime; /* Part of FTE_QC_FS_SEARCH_SIZEMTIME + Retrieves modification time of one of the files. */ + +string(searchhandle handle, float num) search_getpackagename = #0:search_getpackagename; /* + Retrieves the name of the package containing the file. Search with SB_FULLPACKAGEPATH to see gamedir/package info */ + +filestream(searchhandle handle, float num) search_fopen = #0:search_fopen; /* + Opens the file directly, without getting confused about entries from other packages. Read access only. */ + +#if defined(CSQC) || defined(SSQC) +string(string cvarname) cvar_string = #448; /* Part of DP_QC_CVAR_STRING*/ +entity(entity start, .float fld, float match) findflags = #449; /* Part of DP_QC_FINDFLAGS*/ +entity(.float fld, float match, optional .entity chainfield) findchainflags = #450; /* Part of DP_QC_FINDCHAINFLAGS*/ +float(entity ent, string tagname) gettagindex = #451; /* Part of DP_QC_GETTAGINFO*/ +vector(entity ent, float tagindex) gettaginfo = #452; /* Part of DP_QC_GETTAGINFO + Obtains the current worldspace position+orientation of the bone or tag from the given entity. The return value is the world coord, v_forward, v_right, v_up are also set according to the bone/tag's orientation. */ + +#endif +#ifdef SSQC +void(entity player) dropclient = #453; /* Part of DP_SV_DROPCLIENT*/ +entity() spawnclient = #454; /* Part of DP_SV_BOTCLIENT*/ +float(entity client) clienttype = #455; /* Part of DP_SV_BOTCLIENT*/ +void(float target, string str) WriteUnterminatedString = #456; /* Part of DP_SV_WRITEUNTERMINATEDSTRING*/ +#endif +#if defined(CSQC) || defined(SSQC) +void(vector org, vector vel, float howmany) te_flamejet = #457; /* Part of _DP_TE_FLAMEJET*/ +entity(float entnum) edict_num = #459; /* Part of DP_QC_EDICT_NUM*/ +strbuf() buf_create = #460; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle) buf_del = #461; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle) buf_getsize = #462; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle_from, strbuf bufhandle_to) buf_copy = #463; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float sortprefixlen, float backward) buf_sort = #464; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, string glue) buf_implode = #465; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, float string_index) bufstr_get = #466; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index, string str) bufstr_set = #467; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle, string str, float ordered) bufstr_add = #468; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index) bufstr_free = #469; /* Part of DP_QC_STRINGBUFFERS*/ +#endif +float(float s) asin = #471; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float c) acos = #472; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float t) atan = #473; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float c, float s) atan2 = #474; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float a) tan = #475; /* Part of DP_QC_ASINACOSATANATAN2TAN + Forgive me father, for I have a sunbed and I'm not afraid to use it. */ + +float(string s) strlennocol = #476; /* Part of DP_QC_STRINGCOLORFUNCTIONS + Returns the number of characters in the string after any colour codes or other markup has been parsed. */ + +string(string s) strdecolorize = #477; /* Part of DP_QC_STRINGCOLORFUNCTIONS + Flattens any markup/colours, removing them from the string. */ + +string(float uselocaltime, string format, ...) strftime = #478; /* Part of DP_QC_STRFTIME*/ +float(string s, string separator1, ...) tokenizebyseparator = #479; /* Part of DP_QC_TOKENIZEBYSEPARATOR + Splits up the string using only the specified delimiters/separators. Multiple delimiters can be given, they are each considered equivelent (though should start with the longest if you want to do weird subseparator stuff). + The resulting tokens can be queried via argv (and argv_start|end_index builtins, if you want to determine which of the separators was present between two tokens). + Note that while an input string containing JUST a separator will return 2, a string with no delimiter will return 1, while (in FTE) an empty string will ALWAYS return 0. */ + +string(string s) strtolower = #480; /* Part of DP_QC_STRING_CASE_FUNCTIONS*/ +string(string s) strtoupper = #481; /* Part of DP_QC_STRING_CASE_FUNCTIONS*/ +#if defined(CSQC) || defined(SSQC) +string(string s) cvar_defstring = #482; /* Part of DP_QC_CVAR_DEFSTRING*/ +void(vector origin, string sample, float volume, float attenuation) pointsound = #483; /* Part of DP_SV_POINTSOUND*/ +#endif +string(string search, string replace, string subject) strreplace = #484; /* Part of DP_QC_STRREPLACE*/ +string(string search, string replace, string subject) strireplace = #485; /* Part of DP_QC_STRREPLACE*/ +#if defined(CSQC) || defined(SSQC) +vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; /* Part of DP_QC_GETSURFACEPOINTATTRIBUTE*/ +#endif +#if defined(CSQC) || defined(MENU) +float(string name, optional string initialURI) gecko_create = #487; /* Part of DP_GECKO_SUPPORT + Create a new 'browser tab' shader with the specified name that can then be drawn via drawpic (shader should not already exist - including from map/model textures or disk). In order to function correctly, this builtin depends upon external plugins being available. Use gecko_navigate to navigate it to a page of your choosing. */ + +void(string name) gecko_destroy = #488; /* Part of DP_GECKO_SUPPORT + Destroy a shader. */ + +void(string name, string URI) gecko_navigate = #489; /* Part of DP_GECKO_SUPPORT + Sends a command to the media decoder attached to the specified shader. In the case of a browser decoder, this changes the url that the browser displays. 'cmd:[un]focus' will tell the decoder that it has focus. */ + +float(string name, float key, float eventtype, optional float charcode) gecko_keyevent = #490; /* Part of DP_GECKO_SUPPORT + Send a key event to a media decoder. This applies only to interactive decoders like browsers. */ + +void(string name, float x, float y) gecko_mousemove = #491; /* Part of DP_GECKO_SUPPORT + Sets a media decoder shader's mouse position. Values should be 0-1. */ + +void(string name, float w, float h) gecko_resize = #492; /* Part of DP_GECKO_SUPPORT + Request to resize a media decoder. */ + +vector(string name) gecko_get_texture_extent = #493; /* Part of DP_GECKO_SUPPORT + Retrieves a media decoder current image pixel sizes. */ + +string(string shadname, string propname) gecko_getproperty = #0:gecko_getproperty; /* + Queries the media decoder (especially browser ones) for decoder-specific properties. The cef plugin recognises url, title, status. */ + +#endif +#ifdef CSQC +float(string file, string id) cin_open = #0:cin_open; +void(string id) cin_close = #0:cin_close; +void(string id, float newstate) cin_setstate = #0:cin_setstate; +float(string id) cin_getstate = #0:cin_getstate; +void(string file) cin_restart = #0:cin_restart; +#endif +__deprecated("Use digest_hex") float(float caseinsensitive, string s, ...) crc16 = #494; /* Part of DP_QC_CRC16*/ +float(string name) cvar_type = #495; /* Part of DP_QC_CVAR_TYPE*/ +float() numentityfields = #496; /* Part of DP_QC_ENTITYDATA + Gives the number of named entity fields. Note that this is not the size of an entity, but rather just the number of unique names (ie: vectors use 4 names rather than 3). */ + +float(string fieldname) findentityfield = #0:findentityfield; /* + Find a field index by name. */ + +typedef .__variant field_t; +field_t(float fieldnum) entityfieldref = #0:entityfieldref; /* + Returns a field value that can be directly used to read entity fields. Be sure to validate the type with entityfieldtype before using. */ + +string(float fieldnum) entityfieldname = #497; /* Part of DP_QC_ENTITYDATA + Retrieves the name of the given entity field. */ + +float(float fieldnum) entityfieldtype = #498; /* Part of DP_QC_ENTITYDATA + Provides information about the type of the field specified by the field num. Returns one of the EV_ values. */ + +string(float fieldnum, entity ent) getentityfieldstring = #499; /* Part of DP_QC_ENTITYDATA*/ +float(float fieldnum, entity ent, string s) putentityfieldstring = #500; /* Part of DP_QC_ENTITYDATA*/ +#ifdef SSQC +void(float to, string s, float sz) WritePicture = #501; /* Part of DP_SV_WRITEPICTURE + Encodes the named image across the network as-is adhering to some size limit. In FTE, this simply writes the string and is equivelent to writestring and sz is ignored. WritePicture should be paired with ReadPicture in csqc. */ + +#endif +#ifdef CSQC +string() ReadPicture = #501; /* + Reads a picture that was written by ReadPicture, and returns a name that can be used in drawpic and other 2d drawing functions. In FTE, this acts as a readstring-with-downloadcheck - the image will appear normally once it has been downloaded, but its size may be incorrect until then. */ + +void(float effectindex, entity own, vector org_from, vector org_to, vector dir_from, vector dir_to, float countmultiplier, optional float flags) boxparticles = #502; +#endif +string(string filename, optional enumflags:float{WP_REFERENCEPACKAGE,WP_FULLPACKAGEPATH} flags) whichpack = #503; /* Part of DP_QC_WHICHPACK + Returns the pak file name that contains the file specified. progs/player.mdl will generally return something like 'pak0.pak'. If WP_REFERENCE, clients will automatically be told that the returned package should be pre-downloaded and used, even if allow_download_refpackages is not set. */ + +#ifdef CSQC +__variant(float entnum, float fieldnum) getentity = #504; /* + Looks up fields from non-csqc-visible entities. The entity will need to be within the player's pvs. fieldnum should be one of the GE_ constants. */ + +#endif +string(string in) uri_escape = #510; /* Part of DP_QC_URI_ESCAPE + Uses percent-encoding to encode any bytes in the input string which are not ascii alphanumeric, period, hyphen, or underscore. All other bytes will expand to eg '%20' for a single space char. This encoding scheme is compatible with http and other uris. */ + +string(string in) uri_unescape = #511; /* Part of DP_QC_URI_ESCAPE + Undo any percent-encoding in the input string, hopefully resulting in the same original sequence of bytes (and thus chars too). */ + +float(entity ent) num_for_edict = #512; +#define uri_post uri_get +float(string uril, float id, optional string postmimetype, optional string postdata) uri_get = #513; /* Part of DP_QC_URI_GET, DP_QC_URI_POST + uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned + returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string + For a POST request, you will typically want the postmimetype set to application/x-www-form-urlencoded. + For a GET request, omit the mime+data entirely. + Consult your webserver/php/etc documentation for best-practise. */ + +float(string str) tokenize_console = #514; /* + Tokenize a string exactly as the console's tokenizer would do so. The regular tokenize builtin became bastardized for convienient string parsing, which resulted in a large disparity that can be exploited to bypass checks implemented in a naive SV_ParseClientCommand function, therefore you can use this builtin to make sure it exactly matches. */ + +float(float idx) argv_start_index = #515; /* + Returns the character index that the tokenized arg started at. */ + +float(float idx) argv_end_index = #516; /* + Returns the character index that the tokenized arg stopped at. */ + +void(strbuf strbuf, string pattern, string antipattern) buf_cvarlist = #517; +string(string cvarname) cvar_description = #518; /* + Retrieves the description of a cvar, which might be useful for tooltips or help files. This may still not be useful. */ + +#if defined(CSQC) || defined(SSQC) +float(optional float timetype) gettime = #519; +#endif +#ifdef CSQC +DEP string(float keynum) keynumtostring_omgwtf = #520; +__deprecated("Does not support modifiers") string(string command, optional float bindmap) findkeysforcommand = #521; /* + Returns a list of keycodes that perform the given console command in a format that can only be parsed via tokenize (NOT tokenize_console). This only and always returns two values - if only one key is actually bound, -1 will be returned. The bindmap argument is listed for compatibility with dp-specific defs, but is ignored in FTE. */ + +#endif +#if defined(CSQC) || defined(MENU) +string(string command, optional float bindmap) findkeysforcommandex = #0:findkeysforcommandex; /* + Returns a list of key bindings in keyname format instead of keynums. Use tokenize to parse. This list may contain modifiers. May return large numbers of keys. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(string s) loadfromdata = #529; /* + Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +void(string s) loadfromfile = #530; /* + Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +void(float pause) setpause = #531; /* + Sets whether the server should or should not be paused. This does not affect auto-paused things like when the console is down. */ + +#endif +#ifdef SSQC +float(string mname) precache_vwep_model = #532; /* Part of ZQ_VWEP*/ +#endif +float(float v, optional float base) log = #532; /* + Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by. */ + +#ifdef CSQC +float(entity e, float channel, string newsample, float volume, float attenuation, float pitchpct, float flags, float timeoffset) soundupdate = #0:soundupdate; /* + Changes the properties of the current sound being played on the given entity channel. newsample may be empty, and will be ignored in this case. timeoffset is relative to the current position (subtract the result of getsoundtime for absolute positions). Negative volume can be used to stop the sound. Return value is a fractional value based upon the number of audio devices that could be updated - test against TRUE rather than non-zero. */ + +float(entity e, float channel) getsoundtime = #533; /* + Returns the current playback time of the sample on the given entity's channel. Beware CHAN_AUTO (in csqc, channels are not limited by network protocol). */ + +float(entity e, float channel) getchannellevel = #0:getchannellevel; /* + Reports how load the sound's sample is at its current offset. */ + +#endif +#if defined(CSQC) || defined(MENU) +float(string sample) soundlength = #534; /* + Provides a way to query the duration of a sound sample, allowing you to set up a timer to chain samples. */ + +#endif +float(string filename, strbuf bufhandle) buf_loadfile = #535; /* + Appends the named file into a string buffer (which must have been created in advance). The return value merely says whether the file was readable. */ + +float(filestream filehandle, strbuf bufhandle, optional float startpos, optional float numstrings) buf_writefile = #536; /* + Writes the contents of a string buffer onto the end of the supplied filehandle (you must have already used fopen). Additional optional arguments permit you to constrain the writes to a subsection of the stringbuffer. */ + +float(float bufhandle, string match, float matchrule, float startpos, float step) bufstr_find = #537; /* + Looks for the first occurence of the specified string in the buffer, returning its index or -1 on failure. */ + +#ifdef SSQC +float(optional float forcestate) physics_supported = #0:physics_supported; /* + Queries whether rigid body physics is enabled or not. CSQC and SSQC may report different values. If the force argument is specified then the engine will try to activate or release physics (returning the new state, which may fail if plugins or dlls are missing). Note that restarting the physics engine is likely to result in hitches when collision trees get generated. The state may change if a plugin is disabled mid-map. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(entity e, float physics_enabled) physics_enable = #540; /* + Enable or disable the physics attached to a MOVETYPE_PHYSICS entity. Entities which have been disabled in this way will stop taking so much cpu time. */ + +void(entity e, vector force, vector relative_ofs) physics_addforce = #541; /* + Apply some impulse directional force upon a MOVETYPE_PHYSICS entity. */ + +void(entity e, vector torque) physics_addtorque = #542; /* + Apply some impulse rotational force upon a MOVETYPE_PHYSICS entity. */ + +#endif +#ifdef MENU +void(float dest) setkeydest = #601; +float() getkeydest = #602; +#endif +#if defined(CSQC) || defined(MENU) +void(float trg) setmousetarget = #603; +float() getmousetarget = #604; +#endif +void(.../*, string funcname*/) callfunction = #605; /* + Invokes the named function. The function name is always passed as the last parameter and must always be present. The others are passed to the named function as-is */ + +void(filestream fh, entity e) writetofile = #606; /* + Writes an entity's fields to the named frik_file file handle. */ + +float(string s) isfunction = #607; /* + Returns true if the named function exists and can be called with the callfunction builtin. */ + +#if defined(CSQC) || defined(MENU) +vector(float vidmode, optional float forfullscreen) getresolution = #608; /* + Supposed to query the driver for supported video modes. FTE does not query drivers in this way, nor would it trust drivers anyway. */ + +#endif +#ifdef CSQC +DEP string(float keynum) keynumtostring_menu = #609; +#endif +#ifdef MENU +string(float keynum) keynumtostring = #609; /* + Converts a qscancode key number into a mostly-human-readable name, matching the bind command. */ + +string(string command, optional float bindmap) findkeysforcommand = #610; +#endif +#if defined(CSQC) || defined(MENU) +float(float type) gethostcachevalue = #611; /* Part of FTE_CSQC_SERVERBROWSER*/ +string(float type, float hostnr) gethostcachestring = #612; /* Part of FTE_CSQC_SERVERBROWSER*/ +#endif +float(entity e, string s, optional float offset) parseentitydata = #613; /* + Reads a single entity's fields into an already-spawned entity. s should contain field pairs like in a saved game: {"foo1" "bar" "foo2" "5"}. Returns <=0 on failure, otherwise returns the offset in the string that was read to. */ + +string(entity e) generateentitydata = #0:generateentitydata; /* + Dumps the entities fields into a string which can later be parsed with parseentitydata. */ + +#ifdef MENU +float(string key) stringtokeynum = #614; /* + Returns the qscancode of a key from its name. Names are identical to the bind command. ctrl/shift/alt modifiers are ignored. */ + +#endif +#ifdef CSQC +float(string key) stringtokeynum_menu = #614; +#endif +#if defined(CSQC) || defined(MENU) +void() resethostcachemasks = #615; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float mask, float fld, string str, float op) sethostcachemaskstring = #616; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float mask, float fld, float num, float op) sethostcachemasknumber = #617; /* Part of FTE_CSQC_SERVERBROWSER*/ +void() resorthostcache = #618; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float fld, float descending) sethostcachesort = #619; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(optional float dopurge) refreshhostcache = #620; /* Part of FTE_CSQC_SERVERBROWSER*/ +float(float fld, float hostnr) gethostcachenumber = #621; /* Part of FTE_CSQC_SERVERBROWSER*/ +float(string key) gethostcacheindexforkey = #622; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(string key) addwantedhostcachekey = #623; /* Part of FTE_CSQC_SERVERBROWSER*/ +string() getextresponse = #624; /* Part of FTE_CSQC_SERVERBROWSER*/ +#endif +string(string dnsname, optional float defport) netaddress_resolve = #625; +#if defined(CSQC) || defined(MENU) +string(float n, float prop) getgamedirinfo = #626; /* + Queries properties about an indexed gamedir (or -1 for the current gamedir). Returns null strings when out of bounds. Use the GDDI_* constants for the prop arg. */ + +string(int n, int prop) getpackagemanagerinfo = #0:getpackagemanagerinfo; /* + Queries information about a package from the engine's package manager subsystem. Actions can be taken via the pkg console command. */ + +#endif +string(string fmt, ...) sprintf = #627; /* Part of DP_QC_SPRINTF + 'prints' to a formatted temp-string. Mostly acts as in C, however %d assumes floats (fteqcc has arg checking. Use it.). + type conversions: l=arg is an int, h=arg is a float, and will work as a prefix for any float or int representation. + float representations: d=decimal, e,E=exponent-notation, f,F=floating-point notation, g,G=terse float, c=char code, x,X=hex + other representations: i=int, s=string, S=quoted and marked-up string, v=vector, p=pointer + so %ld will accept an int arg, while %hi will expect a float arg. + entities, fields, and functions will generally need to be printed as ints with %i. */ + +#if defined(CSQC) || defined(SSQC) +float(entity e, float s) getsurfacenumtriangles = #628; +vector(entity e, float s, float n) getsurfacetriangle = #629; +#endif +#if defined(CSQC) || defined(MENU) +float(float key, string bind, optional float bindmap, optional float modifier) setkeybind = #630; +vector() getbindmaps = #631; +float(vector bm) setbindmaps = #632; +#endif +string(string digest, string data, ...) digest_hex = #639; +string(string digest, void *data, int length) digest_ptr = #0:digest_ptr; /* + Calculates the digest of a single contiguous block of memory (including nulls) using the specified hash function. */ + +float(string src, string dst) fcopy = #650; /* + Equivelent to fopen+fread+fwrite+fclose from QC (ie: reads from $gamedir/data/ or $gamedir, but always writes to $gamedir/data/ ) */ + +float(string src, string dst) frename = #651; /* + Renames the file, returning 0 on success. Both paths are relative to the data/ subdir. */ + +float(string fname) fremove = #652; /* + Deletes the named file - path is relative to data/ subdir, like fopen's FILE_WRITE. Returns 0 on success. */ + +float(string fname) fexists = #653; /* + Returns true if it exists inside the default writable path. Use whichpack for greater portability. */ + +float(string path) rmtree = #654; /* + Dangerous, but sandboxed to data/ */ + +#if defined(CSQC) || defined(MENU) +const float K_TAB = 9; +const float K_ENTER = 13; +const float K_ESCAPE = 27; +const float K_SPACE = 32; +const float K_BACKSPACE = 127; +const float K_UPARROW = 128; +const float K_DOWNARROW = 129; +const float K_LEFTARROW = 130; +const float K_RIGHTARROW = 131; +const float K_LALT = 132; +const float K_RALT = -280; +const float K_LCTRL = 133; +const float K_RCTRL = -281; +const float K_LSHIFT = 134; +const float K_RSHIFT = -282; +const float K_F1 = 135; +const float K_F2 = 136; +const float K_F3 = 137; +const float K_F4 = 138; +const float K_F5 = 139; +const float K_F6 = 140; +const float K_F7 = 141; +const float K_F8 = 142; +const float K_F9 = 143; +const float K_F10 = 144; +const float K_F11 = 145; +const float K_F12 = 146; +const float K_INS = 147; +const float K_DEL = 148; +const float K_PGDN = 149; +const float K_PGUP = 150; +const float K_HOME = 151; +const float K_END = 152; +const float K_KP_HOME = 164; +const float K_KP_UPARROW = 165; +const float K_KP_PGUP = 166; +const float K_KP_LEFTARROW = 161; +const float K_KP_5 = 162; +const float K_KP_RIGHTARROW = 163; +const float K_KP_END = 158; +const float K_KP_DOWNARROW = 159; +const float K_KP_PGDN = 160; +const float K_KP_ENTER = 172; +const float K_KP_INS = 157; +const float K_KP_DEL = 167; +const float K_KP_SLASH = 168; +const float K_KP_MINUS = 170; +const float K_KP_PLUS = 171; +const float K_KP_NUMLOCK = 154; +const float K_KP_STAR = 169; +const float K_KP_EQUALS = 173; +const float K_MOUSE1 = 512; +const float K_MOUSE2 = 513; +const float K_MOUSE3 = 514; +const float K_MOUSE4 = 517; +const float K_MOUSE5 = 518; +const float K_MOUSE6 = 519; +const float K_MOUSE7 = 520; +const float K_MOUSE8 = 521; +const float K_MOUSE9 = 522; +const float K_MOUSE10 = 523; +const float K_MWHEELUP = 515; +const float K_MWHEELDOWN = 516; +const float K_LWIN = -274; +const float K_RWIN = -275; +const float K_APP = -276; +const float K_SEARCH = -277; +const float K_POWER = -130; +const float K_VOLUP = -278; +const float K_VOLDOWN = -279; +const float K_JOY1 = 768; +const float K_JOY2 = 769; +const float K_JOY3 = 770; +const float K_JOY4 = 771; +const float K_JOY5 = 772; +const float K_JOY6 = 773; +const float K_JOY7 = 774; +const float K_JOY8 = 775; +const float K_JOY9 = 776; +const float K_JOY10 = 777; +const float K_JOY11 = 778; +const float K_JOY12 = 779; +const float K_JOY13 = 780; +const float K_JOY14 = 781; +const float K_JOY15 = 782; +const float K_JOY16 = 783; +const float K_JOY17 = 784; +const float K_JOY18 = 785; +const float K_JOY19 = 786; +const float K_JOY20 = 787; +const float K_JOY21 = 788; +const float K_JOY22 = 789; +const float K_JOY23 = 790; +const float K_JOY24 = 791; +const float K_JOY25 = 792; +const float K_JOY26 = 793; +const float K_JOY27 = 794; +const float K_JOY28 = 795; +const float K_JOY29 = 796; +const float K_JOY30 = 797; +const float K_JOY31 = 798; +const float K_JOY32 = 799; +const float K_AUX1 = 800; +const float K_AUX2 = 801; +const float K_AUX3 = 802; +const float K_AUX4 = 803; +const float K_AUX5 = 804; +const float K_AUX6 = 805; +const float K_AUX7 = 806; +const float K_AUX8 = 807; +const float K_AUX9 = 808; +const float K_AUX10 = 809; +const float K_AUX11 = 810; +const float K_AUX12 = 811; +const float K_AUX13 = 812; +const float K_AUX14 = 813; +const float K_AUX15 = 814; +const float K_AUX16 = 815; +const float K_PAUSE = 153; +const float K_PRINTSCREEN = 174; +const float K_CAPSLOCK = 155; +const float K_SCROLLLOCK = 156; +const float K_SEMICOLON = 59; +const float K_PLUS = 43; +const float K_MINUS = 45; +const float K_APOSTROPHE = 39; +const float K_QUOTES = 34; +const float K_TILDE = 126; +const float K_BACKQUOTE = 96; +const float K_BACKSLASH = 92; +const float K_GP_A = 826; +const float K_GP_B = 827; +const float K_GP_X = 828; +const float K_GP_Y = 829; +const float K_GP_LSHOULDER = 824; +const float K_GP_RSHOULDER = 825; +const float K_GP_LTRIGGER = 830; +const float K_GP_RTRIGGER = 831; +const float K_GP_BACK = 821; +const float K_GP_START = 820; +const float K_GP_LTHUMB = 822; +const float K_GP_RTHUMB = 823; +const float K_GP_DPAD_UP = 816; +const float K_GP_DPAD_DOWN = 817; +const float K_GP_DPAD_LEFT = 818; +const float K_GP_DPAD_RIGHT = 819; +const float K_GP_GUIDE = -238; +const float K_GP_SHARE = -248; +const float K_GP_PADDLE1 = -249; +const float K_GP_PADDLE2 = -250; +const float K_GP_PADDLE3 = -251; +const float K_GP_PADDLE4 = -252; +const float K_GP_TOUCHPAD = -253; +const float K_GP_UNKNOWN = -264; +const float K_GP_LTHUMB_UP = 832; +const float K_GP_LTHUMB_DOWN = 833; +const float K_GP_LTHUMB_LEFT = 834; +const float K_GP_LTHUMB_RIGHT = 835; +const float K_GP_RTHUMB_UP = 836; +const float K_GP_RTHUMB_DOWN = 837; +const float K_GP_RTHUMB_LEFT = 838; +const float K_GP_RTHUMB_RIGHT = 839; +#endif +#ifdef _ACCESSORS +accessor strbuf : float +{ + inline get float asfloat[float idx] = {return stof(bufstr_get(this, idx));}; + inline set float asfloat[float idx] = {bufstr_set(this, idx, ftos(value));}; + get string[float] = bufstr_get; + set string[float] = bufstr_set; + get float length = buf_getsize; +}; +accessor searchhandle : float +{ + get string[float] = search_getfilename; + get float length = search_getsize; +}; +accessor hashtable : float +{ + inline get vector v[string key] = {return hash_get(this, key, '0 0 0', EV_VECTOR);}; + inline set vector v[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_VECTOR);}; + inline get string s[string key] = {return hash_get(this, key, "", EV_STRING);}; + inline set string s[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_STRING);}; + inline get float f[string key] = {return hash_get(this, key, 0.0, EV_FLOAT);}; + inline set float f[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_FLOAT);}; + inline get __variant[string key] = {return hash_get(this, key, __NULL__);}; + inline set __variant[string key] = {hash_add(this, key, value, HASH_REPLACE);}; +}; +accessor infostring : string +{ + get string[string] = infoget; + inline set& string[string fld] = {this = infoadd(this, fld, value);}; +}; +accessor filestream : float +{ + get string = fgets; + inline set string = {fputs(this,value);}; +}; +#endif +accessor jsonnode : json_t +{ + inline get json_type_e type = json_get_value_type; + inline get string s = json_get_string; + inline get float f = json_get_float; + inline get __int i = json_get_integer; + inline get __int length = json_get_length; + inline get jsonnode a[__int key] = json_get_child_at_index; + inline get jsonnode[string key] = json_find_object_child; + inline get string name = json_get_name; +}; +#undef DEP_CSQC +#undef FTEDEP +#undef DEP +#pragma noref 0 diff --git a/share/mcp_precache.qc b/share/mcp_precache.qc new file mode 100644 index 000000000..aced07227 --- /dev/null +++ b/share/mcp_precache.qc @@ -0,0 +1,27 @@ +void mcp_Precache() = +{ +#define CN_HITTEXT "hittext" + +#define SND_HURTTEAM "hitaudio/hurtteam.wav" +#define SND_HURTSELF "hitaudio/hurtself.wav" +#define SND_HURTENEMY "hitaudio/hurtenemy.wav" +#define SND_HURTENEMY_MEATSHOT "hitaudio/hurtenemy_meatshot.wav" +precache_sound(SND_HURTTEAM); +precache_sound(SND_HURTSELF); +precache_sound(SND_HURTENEMY); +precache_sound(SND_HURTENEMY_MEATSHOT); + +#define SND_NOARMOUR "hitaudio/noarmour.wav" +precache_sound(SND_NOARMOUR); + +#define SND_KILLTEAM "hitaudio/killteam.wav" +#define SND_KILLSELF "hitaudio/killself.wav" +#define SND_KILLENEMY "hitaudio/killenemy.wav" +precache_sound(SND_KILLTEAM); +precache_sound(SND_KILLSELF); +precache_sound(SND_KILLENEMY); + + +#define SND_HEADSHOT "announcer/headshot.wav" +precache_sound(SND_HEADSHOT); +} \ No newline at end of file diff --git a/share/physics.qc b/share/physics.qc new file mode 100644 index 000000000..8a3e6bb2e --- /dev/null +++ b/share/physics.qc @@ -0,0 +1,348 @@ +#define PHYS_DEBUG 0 + +static const float phys_tic = 0.005; + +.float phys_time; // Current physics time + +enumflags { + PHYSB_ENABLED, +}; + +enumflags { + PHYSF_CONSUME_ALL, + PHYSF_REWIND_PLAYERS, + PHYSF_FORWARD_KNOCK, + PHYSF_FAIL_STARTSOLID, + PHYSF_SIM, +}; + +const float FL_FORWARD_KNOCK = FL_GODMODE; + +static inline float PhysFlagEnabled(float flag) { + return fo_config.qc_physics & flag; +} +void AugmentGrenadeImpact(); + +#ifdef SSQC + +static void Phys_Impact(entity e, float dt, float phys_flags) { + other = trace_ent; + + if (other.solid == SOLID_NOT) + return; + + if (pointcontents(e.origin) == CONTENT_SKY) { + dremove_sent(e); + return; + } + + if (e.touch) { + if (phys_flags & PHYSF_FORWARD_KNOCK) + e.flags |= FL_FORWARD_KNOCK; + + entity held_self = self; + self = e; + other = trace_ent; + self.touch(); + self = held_self; + } + + AugmentGrenadeImpact(); + +#if PHYS_DEBUG + printf("SPhys: Impact t=%0.3f pt=%0.3f [%0.3f]\n", + time, e.phys_time + dt, e.phys_time + frametime); + printf("Imp: e=%p e.o=%p te=%p\n", e, e.owner, other); +#endif +} + +static vector get_followed_origin() { + return self.aiment.origin; +} + +static void Phys_Expired(entity e, float phys_flags) {} +#else +DEFCVAR_FLOAT(fo_phys_debug, 0); + +.entity groundentity; +.float aiment_num; +.float voided; + +.float phys_sim_last; +.float phys_sim_voided_at; +var float phys_sim_dt; + +float get_phys_time(entity); +void PM_AddSimExplosion(float itime, entity ent); + +static void Phys_Impact(entity e, float dt, float phys_flags) { + other = trace_ent; + if (other.solid == SOLID_NOT) + return; + + if (e.movetype == MOVETYPE_FLYMISSILE) { + e.voided = TRUE; + if (!e.phys_sim_voided_at) // want only one of sim or !sim + PM_AddSimExplosion(e.phys_time + dt, e); + } + + AugmentGrenadeImpact(); + + if (CVARF(fo_phys_debug) & 1) + printf("CPhys: Impact t=%0.3f pt=%0.3f sim=%d [%0.3f]\n", time, e.phys_time + dt, + phys_flags & PHYSF_SIM, + get_phys_time(e)); +} + +void PM_AddGrenadeExplosion(float itime, entity ent); + +static void Phys_Expired(entity e, float phys_flags) { + e.voided = TRUE; + if (e.phys_sim_voided_at) // Only one of sim or !sim + return; + + PM_AddGrenadeExplosion(e.fpp.expires_at, e); +} + +static vector get_followed_origin() { + if (!(float)getentity(self.aiment_num, GE_ACTIVE)) + return self.origin; + else + return (vector)getentity(self.aiment_num, GE_ORIGIN); +} +#endif + +static inline float get_gravity() { return cvar("sv_gravity"); } + +#define dot(v1, v2) ((vector)v1 * (vector)v2) + +float Phys_Push(entity e, vector offset, float dt, float phys_flags) { + float trace_flags = 0; + + if (e.movetype == MOVETYPE_FLYMISSILE) + trace_flags |= MOVE_MISSILE; + + // Note: Networked entities don't currently intersect on CSQC instantiation + // of this, we fudge this in Phys_Impact from the server side. + traceline(e.origin, e.origin + offset, trace_flags, e.owner); + + if ((phys_flags & PHYSF_FAIL_STARTSOLID) && trace_startsolid) + return FALSE; + + e.origin = trace_endpos; + + if (trace_fraction < 1) { + setorigin(e, trace_endpos); + if ((e.flags & FL_ONGROUND == 0) || e.groundentity != trace_ent) + Phys_Impact(e, trace_fraction * dt, phys_flags); + return FALSE; + } + + return TRUE; +} + +static float epsilon = 0.1; // Matches ftesv +vector Phys_ClipVel(vector orig, vector normal, float overbounce) { + float backoff = -dot(orig, normal) * overbounce; + vector result = orig + backoff * normal; + + if (result[0] > -epsilon && result[0] < epsilon) result[0] = 0; + if (result[1] > -epsilon && result[1] < epsilon) result[1] = 0; + if (result[2] > -epsilon && result[2] < epsilon) result[2] = 0; + + return result; +} + +float Phys_Adv_Bounce(entity e, float dt, float phys_flags) { + float g = get_gravity(); + + if (e.flags & FL_ONGROUND) { + if (-e.velocity_z >= 1.0/32.0) + e.flags &= ~FL_ONGROUND; + else // Consider falling through removed entity here at some point. + return 0; + } + + e.velocity_z -= 0.5 * dt * g; + + phys_flags |= PHYSF_FAIL_STARTSOLID; + float move_time = dt, bounce; + if (Phys_Push(e, move_time * e.velocity, move_time, phys_flags)) { + e.velocity_z -= 0.5 * dt * g; + e.angles += dt * e.avelocity; + return dt; + } + + + move_time = (1 - trace_fraction) * dt; + e.angles += move_time * e.avelocity; + + float bounce_stop = 60 / 800 * g; + float debounce = !IsClownMode(CLOWN_RUBBERGREN) ? 1.5 : 2; + e.velocity = Phys_ClipVel(e.velocity, trace_plane_normal, debounce); + + if (PHYS_DEBUG) + printf("%s v=%0.3f p=%v\n", qc_prefix(), e.velocity_z, e.origin); + + float bounce_speed; + if (cvar("sv_gameplayfix_grenadebouncedownslopes")) + bounce_speed = dot(trace_plane_normal, e.velocity); + else + bounce_speed = e.velocity_z; + + if (trace_plane_normal.z > 0.7 && bounce_speed < bounce_stop) { + e.flags |= FL_ONGROUND; + e.groundentity = trace_ent; + e.velocity = '0 0 0'; + e.avelocity = '0 0 0'; + } else { + e.velocity_z -= 0.5 * dt * g; + } + + return dt - move_time; +} + +float Phys_Adv_Linear(entity e, float dt, float phys_flags) { + Phys_Push(e, dt * e.velocity, dt, phys_flags); + + if (trace_fraction < 1) + dt *= trace_fraction; + + e.angles += dt * e.avelocity; + return dt; +} + +var void(entity e, float target_time) RewindSyncTime; + +static void Phys_CapVel(entity e) { + static const float limit = 32 / phys_tic; + + if (vlen(e.velocity) > limit) { + if (!fo_config.clown_flags) + printf("%s v=%d too fast, consider adaptive scaling\n", + e.model, vlen(e.velocity)); + // normalize(e.velocity) * limit; + e.velocity = '0 0 0'; + } +} + +float Phys_Advance(entity e, float target_time, float phys_flags) { + if (target_time < e.phys_time) { + setorigin(e, e.origin); + return 0; + } + + static float limit_eps = 0.00001; + // If there's an expiry, make sure we get to the exact position. + if (e.fpp.expires_at && target_time >= e.fpp.expires_at) { + target_time = e.fpp.expires_at; + phys_flags |= PHYSF_CONSUME_ALL; + } + + float limit = (phys_flags & PHYSF_CONSUME_ALL) ? limit_eps : phys_tic; + float total = 0, dt = 0, step = 0, delta; + + target_time += limit_eps; + while ((delta = target_time - e.phys_time) >= limit) { + if (e.voided) { + e.phys_time = target_time; + break; + } + + step = min(phys_tic, delta); + if (phys_flags & PHYSF_REWIND_PLAYERS) + RewindSyncTime(e, e.phys_time + step/2); + + if (IsClownMode(CLOWN_PROJ_GRAVITY)) + e.velocity_z += -fo_config.clown_grav * step; + Phys_CapVel(e); + + switch (e.movetype) { + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + dt = Phys_Adv_Linear(e, step, phys_flags); + break; + + case MOVETYPE_BOUNCE: + dt = Phys_Adv_Bounce(e, step, phys_flags); + break; + + case MOVETYPE_FOLLOW: + vector org = get_followed_origin(); + target_time = e.phys_time; + step = target_time - e.phys_time; + dt = step > 0 ?: 0; + + e.origin = org + e.velocity; + break; + + default: + printf("ERROR: %s unsupported movetype %d\n", e.model, e.movetype); + case MOVETYPE_NONE: + step = dt = delta; + break; + } + + e.phys_time += step; + ASSERTF_LE(e.phys_time, target_time); + total += dt; + } + + if (step > 0) // Origin can have moved. + setorigin(e, e.origin); + + if (e.fpp.expires_at && e.phys_time >= e.fpp.expires_at) + Phys_Expired(e, phys_flags); + + return total; +} + +float Phys_Init(entity e, float target_time, float delta, float phys_flags) { + if (delta > 0) { + e.phys_time = target_time - delta; + return Phys_Advance(e, target_time, phys_flags); + } else { + e.phys_time = target_time; + setorigin(e, e.origin); + return 0; + } +} + +#ifdef CSQC +void Phys_Sim(entity e, float target_time = 0) { + // TODO: Unvoid+undo if we make it past the void point. + if (e.phys_sim_voided_at) + return; + + float min_gran = 0; + if (!target_time) { + if (phys_sim_dt < 0) + return; + target_time = time + phys_sim_dt; + min_gran = SERVER_FRAME_DT; + } + + if (target_time < e.phys_sim_last + min_gran) + return; + e.phys_sim_last = target_time; + + float held_phys_time = e.phys_time; + vector held_org = e.origin; + vector held_vel = e.velocity; + vector held_angles = e.angles; + float held_flags = e.flags; + + Phys_Advance(e, target_time, PHYSF_SIM | PHYSF_CONSUME_ALL); + + if (e.voided) { + e.phys_sim_voided_at = e.phys_time; + e.voided = 0; + } + e.phys_time = held_phys_time; + e.origin = held_org; + e.velocity = held_vel; + e.angles = held_angles; + e.flags = held_flags; +} +#endif +#undef dot diff --git a/share/prediction.qc b/share/prediction.qc new file mode 100644 index 000000000..0f1c06517 --- /dev/null +++ b/share/prediction.qc @@ -0,0 +1,1177 @@ +#define ENT_CONFIG 100 +#define ENT_WEAPONPRED 101 +#define ENT_PROJECTILE 102 + +// Below apply to both CSQC & SSQC ents +.float antilag_ms; +.float created_at; + +string wp_version = "v1.1"; + +enumflags { + FOWP_CTIME, + FOWP_PMOVE, + FOWP_IMPULSE, + FOWP_TFSTATE, + FOWP_LASTPRIME, + FOWP_CLASSIC_CONC, + FOWP_CONCFINISH, + FOWP_CLIP, + FOWP_GREN, + FOWP_THINK, + FOWP_WF, + FOWP_AF, + FOWP_CLASS, + FOWP_RELOAD, + FOWP_SPECIAL, + FOWP_RNG0, + FOWP_RNG1, + FOWP_RNG2, + FOWP_PREDICT_FLAGS, + FOWP_FILTER_ENTS, + FOWP_LAST, +}; +const float FOWP_ALL = FOWP_LAST - 1; + +enumflags { + FOPP_INIT, + FOPP_POS, + FOPP_AUX, + FOPP_ANGLES, + FOPP_MOVETYPE, + FOPP_EXPIRY, + FOPP_FORWARDKNOCK, +}; + +.float forward_knock; + +enumflags { + PF_PMOVE, + PF_POS, + PF_PMOVE_ACTIVATING, +}; + +static float MAX_FILTER_ENTS = 4; + +struct predict_tf_state { + int playerclass; + int predict_flags, effects; + int impulse; + Slot current_slot, queue_slot, last_slot; + + ConcState conc_state; + + float tfstate; + float last_prime; + float csqc_maxspeed; + + float conc_amp; + float conc_finished; + float conc_cap_time; + float special_next; + + float client_time, server_time; + float client_ping; + float weaponframe; + + float attack_finished; + float client_nextthink; + float client_thinkindex; + + float reload_started; + float reload_finished; + float clip_fired[4]; + int prng_base[PRNG_NUM_STATES]; + + float no_grenades_1, no_grenades_2, primed_gren_type, primed_gren_exp; + + float num_filter_ents; +#ifdef SSQC + entity filter_ents[MAX_FILTER_ENTS]; +#else + float filter_ents[MAX_FILTER_ENTS]; +#endif + +#ifdef CSQC + // Used for prediction, not actually communicated. Reset each frame. + int seq; + float ammo_used[AMMO_NUM_TYPES]; + float buttons_down, buttons_up, buttons_held; +#endif +}; + +string CF_GetSettingRaw(string ps_short, string ps_setting, string ps_default); +float CF_GetSetting(string ps_short, string ps_setting, string ps_default); + +enum SoundIndex:float { + SND_NONE = 0, + SND_FIRST = 2000, + SND_AXE = SND_FIRST, + SND_SG, + SND_SSG, + SND_RL, + SND_GREN, + SND_THROWGREN, + SND_NAIL, + SND_SNAIL, + SND_FLAMETHROWER, + SND_RAILGUN, + SND_TRANQ, + SND_SNIPER_RIFLE, + SND_ASSCAN_UP, + SND_ASSCAN_FIRE, + SND_ASSCAN_DOWN, + SND_ASSCAN_SPIN, + SND_RELOAD_GREN, + SND_RELOAD_ROCKET, + SND_RELOAD_SHOTGUN, + SND_RELOAD_SUPER_SHOTGUN, + SND_RELOAD_ASSAULT_CANNON +}; + +struct fo_predsnd { + SoundIndex id; + string sound; +}; + +static fo_predsnd snd_types[] = { + { SND_AXE, "ax1.wav" }, + { SND_SG, "guncock.wav" }, + { SND_SSG, "shotgn2.wav" }, + { SND_RL, "sgun1.wav" }, + { SND_GREN, "grenade.wav" }, + { SND_THROWGREN, "ax1.wav" }, + { SND_NAIL, "rocket1i.wav" }, + { SND_SNAIL, "spike2.wav" }, + { SND_FLAMETHROWER, "flmfire2.wav" }, + { SND_RAILGUN, "railgun.wav" }, + { SND_TRANQ, "dartgun.wav" }, + { SND_SNIPER_RIFLE, "sniper.wav" }, + { SND_ASSCAN_UP, "asscan1.wav" }, + { SND_ASSCAN_FIRE, "asscan2.wav" }, + { SND_ASSCAN_DOWN, "asscan3.wav" }, + { SND_ASSCAN_SPIN, "asscan4.wav" }, + { SND_RELOAD_GREN, "reload_gren.wav" }, + { SND_RELOAD_ROCKET, "reload_rocket.wav" }, + { SND_RELOAD_SHOTGUN, "reload_shotgun.wav" }, + { SND_RELOAD_SUPER_SHOTGUN, "reload_super_shotgun.wav" }, + { SND_RELOAD_ASSAULT_CANNON, "reload_assault_cannon.wav" }, +}; + +enum { + FPP_NONE, + FPP_FIRST = 50, + FPP_ROCKET = FPP_FIRST, + FPP_GRENADE, // Grenade launcher + FPP_HANDGRENADE, // TF Grenade + FPP_INCENDIARY, + FPP_NAIL, + FPP_SUPER_NAIL, + FPP_FLAMETHROWER, + FPP_TRANQ, + FPP_RAILGUN, + FPP_ASSAULT_CANNON, +}; + +struct fo_projectile { + int id; + int movetype; + float speed; + string model; + string trail; + SoundIndex snd; + float fixed_project; // No dynamic proportioning of newmis projection + + // Automatically initialized below this line. + float modelindex; + float trailindex; +}; + +static fo_projectile fpp_types[] = { + { FPP_ROCKET, MOVETYPE_FLYMISSILE, PC_SOLDIER_ROCKET_SPEED, + "missile.mdl", "t_rocket", SND_RL, TRUE }, + { FPP_GRENADE, MOVETYPE_BOUNCE, 600, + "grenade2.mdl", "t_grenade", SND_GREN, TRUE}, + { FPP_HANDGRENADE, MOVETYPE_BOUNCE, 600, + "hgren2.mdl", "t_grenade", SND_THROWGREN}, + { FPP_INCENDIARY, MOVETYPE_FLYMISSILE, 800, + "lavaball.mdl", "t_lavaball", SND_RL, TRUE }, + { FPP_NAIL, MOVETYPE_FLYMISSILE, 1500, + "spike.mdl", "tr_spike", SND_NAIL }, + { FPP_SUPER_NAIL, MOVETYPE_FLYMISSILE, 1500, + "s_spike.mdl", "tr_spike", SND_SNAIL }, + { FPP_FLAMETHROWER, MOVETYPE_FLYMISSILE, 600, + "s_explod.spr", "explodesprite", SND_FLAMETHROWER }, + { FPP_TRANQ, MOVETYPE_FLYMISSILE, PC_SPY_TRANQSPEED, + "spike.mdl", "tr_spike", SND_TRANQ }, + { FPP_RAILGUN, MOVETYPE_FLYMISSILE, PC_ENGINEER_RAILSPEED, + "e_spike1.mdl", "te_railtrail", SND_RAILGUN }, + { FPP_ASSAULT_CANNON, MOVETYPE_FLYMISSILE, 3000, + "proj_diam2.mdl", "tr_asscan", SND_NONE /* in anim */ }, +}; + +inline fo_projectile* FPP_Get(int fpp_type) { return &fpp_types[fpp_type - FPP_FIRST]; } +inline fo_predsnd* Snd_Get(int snd_type) { return &snd_types[snd_type - SND_FIRST]; } +inline fo_predsnd* FPP_Sound(int fpp_type) { return Snd_Get(FPP_Get(fpp_type)->snd); } + +inline float FPP_IsGrenade(int fpp_type) { + return fpp_type == FPP_GRENADE || fpp_type == FPP_HANDGRENADE; +} + +void InitFppProjectiles() { + float i; + static int once; + ASSERTD_EQ(once, 0); + once = 1; + + for (i = 0; i < fpp_types.length; i++) { + fo_projectile* desc = &fpp_types[i]; + ASSERTD_EQ(i + FPP_FIRST, desc->id); + desc->model = strcat("progs/", desc->model); + desc->modelindex = getmodelindex(desc->model); + if (desc->trail != "") + desc->trailindex = particleeffectnum(strcat("fo-particles.",desc->trail)); + } + + for (i = 0; i < snd_types.length; i++) { + fo_predsnd* snd = &snd_types[i]; + + ASSERTD_EQ(SND_FIRST + i, snd->id); + snd->sound = strcat("weapons/", snd->sound); + precache_sound(snd->sound); + } +} + +float max_rewind_credit_ms(int fpp_type) { + return (FPP_Get(fpp_type)->speed < fo_config.rewind_fast_projectile_thresh) ? + fo_config.max_rewind_slow_projectile_ms : + fo_config.max_rewind_fast_projectile_ms; +} + + +// The original QW implementation always forwards projectiles by 50ms, in a +// similar way to our own antilag projection. However, this is done in a +// difficult to correct for fashion (especially with more complicated +// projectiles such as grenades) as well as not reflecting the ping times of +// today. We split this into a ping independent and ping dependent portion, to +// slightly widen the range at which feel and timing is uniform. +// +// NOTE: DO NOT USE NEWMIS FOR FO PROJECTILES. THE NEWMIS CORRECTION IS NOW +// MADE EXPLICITLY RATHER THAN IMPLICITLY. [WE AUTOMATICALLY STRIP NEWMIS +// FROM PROJECTILES PASSED TO US.] +static float static_newmis_ms(int fpp_type) { + if (fpp_type == FPP_FLAMETHROWER || fpp_type == FPP_ASSAULT_CANNON) + return 0; + + return fo_config.static_newmis_ms - fo_config.dynamic_newmis_ms; +} + +inline float dynamic_newmis_ms() { + return fo_config.dynamic_newmis_ms; +} + +inline float fixed_newmis_ms(int fpp_type) { + return fo_config.static_newmis_ms; +} + +float FO_RewindGrenMs(int gren_type) { + if (!RewindFlagEnabled(REWIND_GRENADES)) + return 0; + + float result = fo_config.max_rewind_grenade_ms; + if (gren_type == GREN_CONC) + result += 25; + + return result; +} + +float FO_RewindGrenDt(int gren_type) { + if (!RewindFlagEnabled(REWIND_GRENADES)) + return 0; + + return FO_RewindGrenMs(gren_type) / 1000.0; +} + +float FO_RewindGrenWinDt(int gren_type) { + if (!RewindFlagEnabled(REWIND_GRENADES) || + fo_config.max_rewind_ms <= 50) + return 0; + + // There's no benefit to holding a conc grenade -- and it's beneficial to + // hold it late, so we open the window slightly further when needed. + return gren_type == GREN_CONC ? 0.05 : 0.025; +} + +float FO_MaxRewindGrenWinDt() { + if (!RewindFlagEnabled(REWIND_GRENADES) || + fo_config.max_rewind_ms <= 50) + return 0; + + // Large rewind possible, open up the window. + return 0.1; // An always correct value +} + +#ifdef SSQC + +.predict_tf_state predict_state; +.entity predict_entity; +.float conc_cap_time; + +inline float *self_tf_state() { return &self.tfstate; } +float self_class() { return self.playerclass; } +Slot self_current_slot() { return self.current_slot; } +Slot self_queue_slot() { return self.queue_slot; } +float* self_clip_fired(Slot slot) { return &self.clip_fired[SlotIndex(slot)]; } + +#else + +predict_tf_state pstate_pred, pstate_server; + +inline float *self_tf_state() { return &pstate_pred.tfstate; } +float self_class() { return pstate_pred.playerclass; } +Slot self_current_slot() { return pstate_pred.current_slot; } +Slot self_queue_slot() { return pstate_pred.queue_slot; } +float* self_clip_fired(Slot slot) { return &pstate_pred.clip_fired[SlotIndex(slot)]; } + +#define MASK_PRED_ENT 256 +#define MASK_PRED_PROJECTILE 512 +#define MASK_OUTLINE 1024 + +#endif + +#ifdef SSQC +#define OP1(_op, _f1) (player.predict_state.##_f1 _op src.##_f1) +#define OP2(_op, _j, _f1, _f2) OP1(_op, _f2) _j OP1(_op, _f1) +#define OP3(_op, _j, _f1, _f2, _f3) OP2(_op, _j, _f2, _f3) _j OP1(_op, _f1) +#define OP4(_op, _j, _f1, _f2, _f3, _f4) OP3(_op, _j, _f2, _f3, _f4) _j OP1(_op, _f1) +#define M1(_bit, _f1) if (OP1(!=, _f1)) { mask |= _bit; OP1(=, _f1); } +#define M2(_bit, _f1, _f2) if (OP2(!=, ||, _f1, _f2)) { mask |= _bit; OP2(=, ;, _f1, _f2); } +#define M3(_bit, _f1, _f2, _f3) if (OP3(!=, ||, _f1, _f2, _f3)) { mask |= _bit; OP3(=, ;, _f1, _f2, _f3); } +#define M4(_bit, _f1, _f2, _f3, _f4) if (OP4(!=, ||, _f1, _f2, _f3, _f4)) { mask |= _bit; OP4(=, ;, _f1, _f2, _f3, _f4); } + +.float pred_lastforce; +.float pred_forcebit; +.entity last_pred_src; +.float predict_flags; + +static float Prediction_ChangedMask(entity player, entity src) { + if (src != player.last_pred_src) { + player.last_pred_src = src; + return FOWP_ALL; + } + + float mask = FOWP_CTIME; + + player.predict_state.server_time = time; + player.predict_state.client_time = src.client_time; + player.predict_state.client_ping = src.client_ping; + + if (player.predict_flags & (PF_PMOVE | PF_POS)) + mask |= FOWP_PMOVE; + + if (player == src) + mask |= FOWP_FILTER_ENTS; + + M1(FOWP_CLASS, playerclass); + M4(FOWP_IMPULSE, impulse, current_slot.id, queue_slot.id, last_slot.id); + M2(FOWP_TFSTATE, tfstate, csqc_maxspeed); + M1(FOWP_LASTPRIME, last_prime); + M2(FOWP_CLASSIC_CONC, conc_state.next, conc_state.mag); + M3(FOWP_CONCFINISH, conc_finished, conc_amp, conc_cap_time); + M1(FOWP_WF, weaponframe); + M1(FOWP_AF, attack_finished); + M2(FOWP_THINK, client_nextthink, client_thinkindex); + M4(FOWP_CLIP, clip_fired[0], clip_fired[1], clip_fired[2], clip_fired[3]); + M2(FOWP_RELOAD, reload_started, reload_finished); + M1(FOWP_SPECIAL, special_next); + M1(FOWP_RNG0, prng_base[PRNG_WEAP]); + M1(FOWP_RNG1, prng_base[PRNG_HWGUY]); + M1(FOWP_RNG2, prng_base[PRNG_CONC]); + M2(FOWP_PREDICT_FLAGS, predict_flags, effects); + M4(FOWP_GREN, no_grenades_1, no_grenades_2, primed_gren_type, primed_gren_exp) + + // Rotate through forced update fields. + if (time - player.pred_lastforce >= 100 * MSEC) { + player.pred_lastforce = time; + + player.pred_forcebit <<= 1; + if (player.pred_forcebit >= FOWP_LAST) + player.pred_forcebit = 1; + + if (player.pred_forcebit & (FOWP_PMOVE | FOWP_FILTER_ENTS) == 0) + mask |= player.pred_forcebit; + } + + return mask; +} + +void Predict_AddFilterEnt(entity p, entity filter) { + if (p.predict_flags & PF_PMOVE == 0) + return; + + predict_tf_state* ps = &p.predict_state; + if (ps->num_filter_ents >= MAX_FILTER_ENTS) + return; + ps->filter_ents[ps->num_filter_ents++] = filter; +} + +void Predict_InitPlayer(entity player); +void Predict_SyncPmove(float is_player); + +void Predict_Update(float is_player) { + Predict_SyncPmove(is_player); + Predict_InitPlayer(self); + + // When spectating, goalentity is our target. + entity src = self.goalentity; + if (!src) + src = self; + + self.predict_entity.SendFlags |= Prediction_ChangedMask(self, src); +} + +#undef OP1 +#undef OP2 +#undef OP3 +#undef OP4 +#undef M1 +#undef M2 +#undef M3 +#undef M4 + +void sprint_pred(entity client, float msglevel, string s) { + if (ClientPred_Enabled(client, CSQC_WEAP_PRED)) + return; // Message generated client-side. + sprint(client, msglevel, s); +} +#endif + +#ifdef CSQC +float() ReadByte = #360; +float() ReadShort = #362; +float() ReadCoord = #364; +float() ReadAngle = #365; +float() ReadFloat = #367; +float() ReadEntity = #368; +int() ReadInt = #0:readint; + +void InitWeapPredEnt(entity e); +void WP_ServerUpdate(float sendflags); +void InitProjectileEnt(float sendflags); +void WPP_UpdateEnable(float force); + +.float owner_entnum; +.float s_time; +#endif + +#ifdef SSQC +#define COMM(_type, _field) Write##_type(MSG_ENTITY, fo_config.##_field) +float WP_SendConfig(entity to_player, float sendflags) { + WriteByte(MSG_ENTITY, ENT_CONFIG); +#else +#define COMM(_type, _field) fo_config.##_field = Read##_type() +void EntUpdate_Config() { +#endif + COMM(Byte, qc_physics); + COMM(Byte, min_ping_ms); + COMM(Float, static_newmis_ms); + COMM(Float, dynamic_newmis_ms); + COMM(Byte, max_rewind_ms); + COMM(Byte, max_rewind_slow_projectile_ms); + COMM(Byte, max_rewind_fast_projectile_ms); + COMM(Byte, max_rewind_grenade_ms); + COMM(Short, rewind_fast_projectile_thresh); + COMM(Byte, rewind_flags); + COMM(Short, tfx_flags); + COMM(Byte, clown_flags); + COMM(Float, clown_grav); + COMM(Byte, wp_default_min_ping_ms); + COMM(Byte, wp_global_disable); + COMM(Byte, gren_beta_disable); + COMM(Byte, old_ng_rof); + COMM(Short, fo_concuss); + COMM(Byte, new_balance); + COMM(Byte, new_balance_flags); + +#ifdef SSQC + return TRUE; +#else + WPP_UpdateEnable(TRUE); +#endif +} +#undef COMM + +#ifdef SSQC +#define GETF(_field) self.owner.predict_state.##_field +#define COMM(_type, _field) Write##_type(MSG_ENTITY, GETF(_field)) +#define COMM_PM(_type, _field) Write##_type(MSG_ENTITY, self.owner.##_field) +float WP_SendEntity(entity to_player, float sendflags) { + if (to_player != self.owner) + return FALSE; + + WriteByte(MSG_ENTITY, ENT_WEAPONPRED); + WriteFloat(MSG_ENTITY, sendflags); +#else +entity PM_Ent(); +#define GETF(_field) pstate_server.##_field +#define COMM(_type, _field) GETF(_field) = Read##_type() +#define COMM_PM(_type, _field) PM_Ent().##_field = Read##_type() + +void EntUpdate_WeaponPred(float isnew) { + float sendflags = readfloat(); +#endif + if (sendflags & FOWP_PREDICT_FLAGS) { + COMM(Byte, predict_flags); + COMM(Short, effects); + } + + if (sendflags & FOWP_CTIME) { + COMM(Float, client_time); + COMM(Float, server_time); + COMM(Short, client_ping); + } + + if (sendflags & FOWP_PMOVE) { + COMM_PM(Coord, origin[0]); + COMM_PM(Coord, origin[1]); + COMM_PM(Coord, origin[2]); + COMM_PM(Short, velocity[0]); + COMM_PM(Short, velocity[1]); + COMM_PM(Short, velocity[2]); + } + + if (sendflags & FOWP_FILTER_ENTS) { + COMM(Byte, num_filter_ents); + for (float i = 0; i < GETF(num_filter_ents); i++) + COMM(Entity, filter_ents[i]); +#ifdef SSQC + GETF(num_filter_ents) = 0; // Destructive transmission +#endif + } + + if (sendflags & FOWP_CLASS) { + COMM(Byte, playerclass); + } + + if (sendflags & FOWP_IMPULSE) { + COMM(Byte, impulse); + COMM(Byte, current_slot.id); + COMM(Byte, queue_slot.id); + COMM(Byte, last_slot.id); + } + + if (sendflags & FOWP_TFSTATE) { + COMM(Float, tfstate); + COMM(Short, csqc_maxspeed); + } + + if (sendflags & FOWP_LASTPRIME) { + COMM(Float, last_prime); + } + + if (sendflags & FOWP_CLASSIC_CONC) { + COMM(Float, conc_state.next); + COMM(Byte, conc_state.mag); + } + + if (sendflags & FOWP_CONCFINISH) { + COMM(Byte, conc_amp); + COMM(Float, conc_finished); + COMM(Float, conc_cap_time); + } + + if (sendflags & FOWP_THINK) { + COMM(Float, client_nextthink); + COMM(Float, client_thinkindex); + } + + if (sendflags & FOWP_WF) { + COMM(Byte, weaponframe); + } + + if (sendflags & FOWP_AF) { + COMM(Float, attack_finished); + } + + if (sendflags & FOWP_CLIP) { + COMM(Byte, clip_fired[0]); + COMM(Byte, clip_fired[1]); + COMM(Byte, clip_fired[2]); + COMM(Byte, clip_fired[3]); + } + + if (sendflags & FOWP_RELOAD) { + COMM(Float, reload_started); + COMM(Float, reload_finished); + } + + if (sendflags & FOWP_SPECIAL) { + COMM(Float, special_next); + } + + if (sendflags & FOWP_RNG0) + COMM(Int, prng_base[PRNG_WEAP]); + if (sendflags & FOWP_RNG1) + COMM(Int, prng_base[PRNG_HWGUY]); + if (sendflags & FOWP_RNG2) + COMM(Int, prng_base[PRNG_CONC]); + + if (sendflags & FOWP_GREN) { + COMM(Short, no_grenades_1); + COMM(Short, no_grenades_2); + COMM(Byte, primed_gren_type); + COMM(Float, primed_gren_exp); + } + +#ifdef SSQC + return TRUE; +#else + if (isnew) + InitWeapPredEnt(self); + else + WP_ServerUpdate(sendflags); +#endif +} +#undef COMM +#undef COMM_PM +#undef GETF + +#ifdef SSQC +#define COMM(_type, _field) Write##_type(MSG_ENTITY, self.##_field) +#define COMMD(_type, _dest, _field) COMM(_type, _field) +#define COMMO(_type, _dest, _src) Write##_type(MSG_ENTITY, _src) +float PP_SendEntity(entity to_player, float sendmask) { + float sendflags = sendmask; + + if (sendflags & FOPP_INIT) { + sendflags = FOPP_INIT | FOPP_POS; // Note: replaces sendmask + if (self.fpp.aux) + sendflags |= FOPP_AUX; + if (self.fpp.expires_at) + sendflags |= FOPP_EXPIRY; + if (FPP_Get(self.fpp.index)->movetype != self.movetype) + sendflags |= FOPP_MOVETYPE; + if (self.forward_knock) + sendflags |= FOPP_FORWARDKNOCK; + } + + WriteByte(MSG_ENTITY, ENT_PROJECTILE); + WriteByte(MSG_ENTITY, sendflags); +#else +#define COMMD(_type, _dest, _field) self.##_dest = Read##_type() +#define COMMO(_type, _dest, _src) ##_dest = Read##_type() +#define COMM(_type, _field) COMMD(_type, _field, _field) + +void EntUpdate_Projectile(float isnew) { + float sendflags = readbyte(); +#endif + if (sendflags & FOPP_INIT) { + COMM(Byte, fpp.index); + COMM(Short, antilag_ms); + COMM(Float, created_at); + COMMD(Entity, owner_entnum, owner); + } + + if (sendflags & FOPP_AUX) + COMM(Short, fpp.aux); + if (sendflags & FOPP_EXPIRY) + COMM(Float, fpp.expires_at); + + if (sendflags & FOPP_POS) { + COMM(Coord, origin[0]); + COMM(Coord, origin[1]); + COMM(Coord, origin[2]); + COMM(Coord, velocity[0]); + COMM(Coord, velocity[1]); + COMM(Coord, velocity[2]); + + COMM(Float, phys_time); +#ifdef CSQC + self.s_origin = self.origin; + self.s_time = self.phys_time; +#endif + } + + if (sendflags & FOPP_ANGLES) { + COMM(Coord, angles[0]); + COMM(Coord, angles[1]); + COMM(Coord, angles[2]); + COMM(Short, avelocity[0]); + COMM(Short, avelocity[1]); + COMM(Short, avelocity[2]); + } + + if (sendflags & FOPP_MOVETYPE) { + COMM(Byte, movetype); + if (self.movetype == MOVETYPE_FOLLOW) + COMMD(Entity, aiment_num, aiment); + } + + if (sendflags & FOPP_FORWARDKNOCK) + COMM(Float, forward_knock); + +#ifdef SSQC + return TRUE; +#else + if (isnew) + InitProjectileEnt(sendflags); +#endif +} +#undef COMMD +#undef COMMO +#undef COMM + + +void Predict_InitDefaultConfig() { + fo_config.min_ping_ms = 0; + fo_config.qc_physics = 1; + fo_config.static_newmis_ms = 50; + fo_config.dynamic_newmis_ms = 0; + fo_config.rewind_flags = REWIND_DEFAULT_FLAGS; + fo_config.tfx_flags = TFX_DEFAULT_FLAGS; + fo_config.max_rewind_ms = 250; + fo_config.max_rewind_slow_projectile_ms = 150; + fo_config.max_rewind_fast_projectile_ms = 150; + fo_config.max_rewind_grenade_ms = 50; // Only affects REWIND_GREN + fo_config.rewind_fast_projectile_thresh = 1500; + fo_config.wp_default_min_ping_ms = 25; + fo_config.clown_flags = 0; + fo_config.clown_grav = 400; + fo_config.fo_concuss = 0; + fo_config.new_balance = 0; + fo_config.new_balance_flags = 0; +} + +#ifdef SSQC + +entity config_entity; + +#define CONFIG_UPDATE(_flag, _ps_short, _field) \ + do { string _cur = ftos(fo_config.##_field); \ + float _val; \ + if (_flag) \ + _val = ParseFlags(CF_GetSettingRaw(_ps_short, #_field, _cur)); \ + else \ + _val = CF_GetSetting(_ps_short, #_field, _cur); \ + if (_val != fo_config.##_field) { \ + fo_config.##_field = _val; update = TRUE; \ + } \ + } while (0) +#define CLAMP_UPDATE(_field, _clamp_low, _clamp_high) \ + do { if (fo_config.##_field < _clamp_low || fo_config.##_field > _clamp_high) { \ + fo_config.##_field = max(min(fo_config.##_field, _clamp_high), _clamp_low); \ + update = TRUE; \ + } } while (0) + +float ForwardDoorsPossible() { + static float once; + static float possible; + + if (!once) { + possible = CF_GetSetting("rfd", "rewind_forward_doors", "off"); + once = TRUE; + } + + return possible; +} + +// Equivalent to stof(str) when not splittable (e.g. single OR'd val). +static float ParseFlags(string str) { + float n = tokenize(str); + float result = 0; + for (int i = 0; i < n; i++) + result |= stof(argv(i)); + return result; +} + +static void WeaponPred_CheckConfigUpdate() { + float update = FALSE; + + // Target is also the long form localinfo name. + CONFIG_UPDATE(FALSE, "qcp", qc_physics); + CONFIG_UPDATE(FALSE, "mpm", min_ping_ms); + CONFIG_UPDATE(FALSE, "snm", static_newmis_ms); + CONFIG_UPDATE(FALSE, "dnm", dynamic_newmis_ms); + CONFIG_UPDATE(FALSE, "mrt", max_rewind_ms); + CONFIG_UPDATE(FALSE, "mrsp", max_rewind_slow_projectile_ms); + CONFIG_UPDATE(FALSE, "mrfp", max_rewind_fast_projectile_ms); + CONFIG_UPDATE(FALSE, "mrg", max_rewind_grenade_ms); + CONFIG_UPDATE(FALSE, "rfpt", rewind_fast_projectile_thresh); + CONFIG_UPDATE(FALSE, "wpdmp", wp_default_min_ping_ms); + CONFIG_UPDATE(FALSE, "wpgd", wp_global_disable); + CONFIG_UPDATE(FALSE, "gbd", gren_beta_disable); + + if (fo_concuss != fo_config.fo_concuss) { + fo_config.fo_concuss = fo_concuss; + update = TRUE; + } + + if ((new_balance & 1) != fo_config.new_balance) { + fo_config.new_balance = new_balance & 1; + update = TRUE; + } + + // Not dynamically updatable. + static float read_rof_once = FALSE; + if (!read_rof_once) { + if (!fo_config.old_ng_rof) // Don't overwrite huetf setting this. + CONFIG_UPDATE(FALSE, "ongrof", old_ng_rof); + read_rof_once = TRUE; + } + + CONFIG_UPDATE(TRUE, "rewind", rewind_flags); + CONFIG_UPDATE(TRUE, "tfx", tfx_flags); + CONFIG_UPDATE(TRUE, "clown", clown_flags); + CONFIG_UPDATE(FALSE, "clown_grav", clown_grav); + CONFIG_UPDATE(FALSE, "rj", rj); + + CLAMP_UPDATE(dynamic_newmis_ms, 0, fo_config.static_newmis_ms); + CLAMP_UPDATE(max_rewind_ms, 0, 250); + CLAMP_UPDATE(max_rewind_slow_projectile_ms, 0, fo_config.max_rewind_ms); + CLAMP_UPDATE(max_rewind_fast_projectile_ms, 0, fo_config.max_rewind_ms); + CLAMP_UPDATE(max_rewind_grenade_ms, 0, 125); + + if (!ForwardDoorsPossible()) + fo_config.rewind_flags &= ~REWIND_FORWARD_DOORS; + + static float last_force_refresh; + if (time > last_force_refresh + 5) { + update = TRUE; + last_force_refresh = time; + } + + if (update) + config_entity.SendFlags = 1; + config_entity.nextthink = time + 0.25; +}; +#undef CLAMP_UPDATE +#undef CONFIG_UPDATE + +void Predict_Init() { + Predict_InitDefaultConfig(); + + config_entity = spawn(); + + config_entity.dimension_seen = DMN_FLASH | DMN_NOFLASH; + config_entity.pvsflags = PVSF_IGNOREPVS; + config_entity.SendEntity = WP_SendConfig; + + config_entity.think = WeaponPred_CheckConfigUpdate; + config_entity.nextthink = time + 0.5; +} + +void Predict_InitPlayer(entity player) { + if (player.predict_entity) + return; + + entity pe = spawn(); + pe.owner = player; + pe.classname = "WeaponPred"; + pe.dimension_seen = DMN_FLASH | DMN_NOFLASH; + pe.pvsflags = PVSF_IGNOREPVS; + pe.SendEntity = WP_SendEntity; + setorigin(pe, [0, 0, 0]); + + player.pred_forcebit = 1; + player.predict_entity = pe; +} + +void Predict_Destroy(entity player) { + if (player.predict_entity != __NULL__) + dremove(player.predict_entity); +} + +void WeaponPred_DoServerClientThink() { + while (self.client_think && self.client_time > self.client_nextthink) { + float held_client_time = self.client_time; + + self->client_time = self->client_nextthink; + self->client_nextthink = 0; + self->client_think(); + + self->client_time = held_client_time; + } +} + +void ProjRewindForTravel(entity e, float target_time); +void NoRewindSyncFn(entity e, float target_time); +void RW_StashAll(); +void RW_RestoreAll(); + +void FO_CustomPhysics() { + if (!self.voided) { + float physf = 0; + if (RewindFlagEnabled(REWIND_PROJ_TRAVEL)) { + // This is a little inside out, it would be nice to do this by time + // then by projectile/player, but structure makes that difficult + // without more aggressive change. + RW_StashAll(); + physf = PHYSF_REWIND_PLAYERS; + RewindSyncTime = ProjRewindForTravel; + } + + Phys_Advance(self, time, physf); + + if (RewindFlagEnabled(REWIND_PROJ_TRAVEL)) { + RewindSyncTime = NoRewindSyncFn; + RW_RestoreAll(); + } + + // Moving platforms don't get properly reflected into the entities CSQC + // sees so just blast the client with updates when on one (in motion). + // Basically what the stock netcode does anyway. It's not perfect but, + // better than nothing. + entity ground = self.groundentity; + if ((self.flags & FL_ONGROUND) && (ground.movetype == MOVETYPE_PUSH) && + (vlen(ground.velocity) > 0)) + self.SendFlags |= FOPP_POS; + } + + float held_time, thinktime; + // Once we lift physics, think execution comes with it. + do { + thinktime = self.nextthink; + if (thinktime <= 0 || thinktime > time) + return; + + held_time = time; + self.nextthink = 0; + time = thinktime; + self.think(); + time = held_time; + + if (self.is_removed || self.nextthink < thinktime /* no loop on rev */) + return; + } while (1); +} + +void Pred_Sound(SoundIndex snd, float vol = 1) { + if (snd == SND_NONE) + return; + + ASSERTD_GE(snd, SND_FIRST); + snd -= SND_FIRST; + + entity target = self; + string wav = snd_types[snd].sound; + + if (ClientPred_Enabled(self, CSQC_PROJ_PRED)) { + target = self.predict_entity; + setorigin(target, self.origin); + } + + FO_Sound(target, CHAN_WEAPON, wav, vol, ATTN_NORM); +} + +void PredProj_Sound(int proj_type, float vol = 1) { + Pred_Sound(FPP_Get(proj_type)->snd, vol); +} + +void Forward_Projectile(int fpp_type, entity projectile, float use_ctime=0); + +entity FOProj_Create(int fpp_type) { + entity prj = spawn(); + + prj.fpp.index = fpp_type; + prj.dimension_seen = DMN_NOFLASH; + prj.created_at = time; + setmodel(prj, FPP_Get(fpp_type)->model); + setsize(prj, '0 0 0', '0 0 0'); + + return prj; +} + +void FOProj_Finalize(entity mis, float use_ctime=0) { + int fpp_type = mis.fpp.index; + // We explicitly do not use newmis. Newmis internally implements a fixed + // 50m forward of physics handling (equivalent to our projection below) + // which cannot be nicely unwound or accounted for.ammo_shells + // + // For our projectiles we continue to always project by at least 25ms. + // The remaining 25ms is moved to ping dependent correction. + if (newmis == mis) { + PRINT_ONCE("Error: fpp=%d used with newmis\n", (float)fpp_type); + newmis = __NULL__; + } + PredProj_Sound(fpp_type); + float csqc_networking = fo_projectiles; + + if (FPP_IsGrenade(fpp_type)) { + FO_GrenInfo* gdesc = FO_GrenDesc(mis.fpp.gren_type); + + setmodel(mis, gdesc->model); + mis.avelocity = gdesc->avelocity; + mis.skin = gdesc->skin; + mis.fpp.gren_type = gdesc->id; + + // If we don't have physics synchronized between client and server, use + // engine-side for both. + csqc_networking = (csqc_networking && fo_config.qc_physics); + } + + // After any model updates have occurred + setsize(mis, '0 0 0', '0 0 0'); + + // We always need to init some of the state here (e.g. phys_time) as we + // always use the physics code client-side. + if (fo_config.qc_physics) + mis.customphysics = FO_CustomPhysics; + + Forward_Projectile(mis.fpp.index, mis, use_ctime); + + // Below is conditional on using custom SendEntity. + if (csqc_networking) { + mis.SendEntity = PP_SendEntity; + mis.SendFlags = FOPP_INIT; + } else if (fpp_type == FPP_HANDGRENADE) { + entity target = self; + if (target.classname != "player") + target = target.owner; // Handle call from timer on latched throw. + FO_Sound(target, CHAN_WEAPON, Snd_Get(SND_THROWGREN)->sound, 1, ATTN_NORM); + } +} + +.float pmt_nexttime; +.float pmt_nextstate; + +enum PmtState { + PMT_INACTIVE, + PMT_ACTIVE, + PMT_ACTIVATE1, + PMT_ACTIVATE2, + PMT_DEACTIVATE1, + PMT_DEACTIVATE2, +}; + +float PmtBlockMaxSpeed() { + return self.pmt_nexttime != 0; // When set, we're in transition edges. +} + +void TeamFortress_SetSpeed(entity ent); +void Predict_SyncPmove(float is_player) { + if (time < self.pmt_nexttime) + return; + + float active = self.predict_flags & PF_PMOVE; + float want_active = (ClientPred_Enabled(self, CSQC_PMOVE)) && is_player; + float state = self.pmt_nextstate; + + if (!active && want_active && state == PMT_INACTIVE) + state = PMT_ACTIVATE1; + else if (active && !want_active && state == PMT_ACTIVE) + state = PMT_DEACTIVATE1; + + if (state == PMT_ACTIVE || state == PMT_INACTIVE) { + self.pmt_nexttime = 0; + + if (state == PMT_INACTIVE) { + if (ClientPred_Enabled(self, CSQC_FORCE_POS)) + self.predict_flags |= PF_POS; + else + self.predict_flags &= ~PF_POS; + } + return; + } + + float next_step = 0.1; + switch (state) { + case PMT_ACTIVATE1: + self.predict_flags |= PF_POS; + self.maxspeed = SPEC_MAXSPEED; + self.pmt_nextstate = PMT_ACTIVATE2; + break; + case PMT_ACTIVATE2: + self.predict_flags |= PF_PMOVE; + self.nodrawtoclient = self; + TeamFortress_SetSpeed(self); + sprint(self, PRINT_HIGH, "*** PMOVE ACTIVATED\n"); + self.pmt_nextstate = PMT_ACTIVE; + break; + case PMT_DEACTIVATE1: + next_step = 2; + self.nodrawtoclient = world; + TeamFortress_SetSpeed(self); + self.pmt_nextstate = PMT_DEACTIVATE2; + break; + case PMT_DEACTIVATE2: + self.predict_flags &= ~(PF_PMOVE | PF_POS); + sprint(self, PRINT_HIGH, "*** PMOVE DEACTIVATED\n"); + self.pmt_nextstate = PMT_INACTIVE; + break; + } + self.pmt_nexttime = time + next_step; +} +#endif + +struct ProjectResult { + float static_ms; + float dynamic_ms; +}; + +ProjectResult Forward_ProjectOffset(int fpp_type, float ping) { + float max_ping_credit = max_rewind_credit_ms(fpp_type); + float static_credit; + + if (FPP_Get(fpp_type)->fixed_project) { + static_credit = fixed_newmis_ms(fpp_type); + } else if (fpp_type == FPP_HANDGRENADE) { + static_credit = static_newmis_ms(fpp_type); + if (RewindFlagEnabled(REWIND_GRENADES)) + max_ping_credit = fo_config.max_rewind_grenade_ms; + else + max_ping_credit = 0; + } else { + static_credit = static_newmis_ms(fpp_type); + max_ping_credit += dynamic_newmis_ms(); + } + + if (IsClownMode(CLOWN_FAST_PROJECTILES) && !FPP_IsGrenade(fpp_type)) + static_credit = 9999; + + // Everything lower than SERVER_FRAME_MS falls into the next frame (and we + // have no resolution anyway), linearize beyond that. + float adj_ping = max(ping - SERVER_FRAME_MS, 0); + + ProjectResult result; + result.static_ms = static_credit; + result.dynamic_ms = min(adj_ping, max_ping_credit); + return result; +} + +void AugmentGrenadeImpact() { + if (!FPP_IsGrenade(self.fpp.index) || self.voided || + self.fpp.gren_type == GREN_RED) + return; + + float old_move = self.movetype; + + if (IsClownMode(CLOWN_STICKY_PIPES) && self.fpp.gren_type == GREN_PIPE && + other.solid == SOLID_BSP) + self.movetype = MOVETYPE_NONE; + + if (IsClownMode(CLOWN_STICKY_GRENS) && other.solid == SOLID_SLIDEBOX) { + self.movetype = MOVETYPE_FOLLOW; + self.velocity = self.origin - other.origin; +#ifdef SSQC + self.aiment = other; + self.SendFlags |= FOPP_POS | FOPP_MOVETYPE; // Need velocity for + // offset +#else + self.aiment_num = other.entnum; +#endif + } + +#ifdef SSQC + if (other.solid != SOLID_BSP) { + // Networked (non-csqc) entities don't appear to exist within the + // client-side CSQC world for our physics simulation. Until we either + // (a) migrate them there or (b) find a way to propagate them there, we + // cheat by sending an update to the client when such a collision + // occurs. + self.SendFlags |= FOPP_POS | FOPP_ANGLES; + } + if (self.movetype != old_move) + self.SendFlags |= FOPP_MOVETYPE; +#endif +} + diff --git a/share/weapons.qc b/share/weapons.qc new file mode 100644 index 000000000..acc8ee22f --- /dev/null +++ b/share/weapons.qc @@ -0,0 +1,867 @@ +#ifdef CSQC +/* + * This is a hack so that we can have our client entity be closer to server for + * shared code. We'll refactor this on both sides to be cleaner incrementally, + * but it gets us started. + */ +.float waterlevel; + +.float ammo_shells; +.float ammo_nails; +.float ammo_rockets; +.float ammo_cells; +#endif + +// Use SlotIndex() to convert a slot if you're indexing an array. +Slot MakeSlot(float slot_num) { + if (slot_num < 1 || slot_num > TF_NUM_SLOTS) + errorf("invalid slot %d\n", slot_num); + + Slot result; + result.id = slot_num; + return result; +} + +const Slot SlotMelee = { 4 }; +const Slot SlotNull = { 0 }; +inline float IsSlotNull(Slot slot) { return slot.id == 0; } +inline float IsSlotMelee(Slot slot) { return slot.id == 4; } +inline float IsSameSlot(Slot slot1, Slot slot2) { return slot1.id == slot2.id; } + +// Returns dense [0..TF_NUM_SLOTS-1] +float SlotIndex(Slot slot) { + if (slot.id > 4) { + printf("ERROR: OOB slot id (%d) found. Continuing.\n", (float)slot.id); + return TF_NUM_SLOTS - 1; + } + return slot.id - 1; +} + +enum AmmoType { + AMMO_NONE, + AMMO_SHELLS, + AMMO_NAILS, + AMMO_CELLS, + AMMO_ROCKETS, + AMMO_NUM_TYPES = AMMO_ROCKETS + 1, +}; + +struct FO_WeapModels; +struct FO_WeapToItem; + +enum PredictionType { + NO_PREDICT, + PRED_MODEL, + PRED_PROJ, +}; + +struct FO_WeapInfo { + int weapon; // Verification + int predict_type; + AmmoType ammo_type; + float clip_size; + float ammo_per_shot; + float attack_time; + float full_reload_time; + float fire_in_anim; // firing triggered from animation (e.g. continuous fire) + + // Fields below this are automatically initialized by Init. + // ** Do not include in table. ** + float needs_reload; + FO_WeapModels* models; + FO_WeapToItem* items; +}; + +// -ve values are placeholders signifying conditional init based on game modes. +FO_WeapInfo weapon_info[] = { + { WEAP_NONE, NO_PREDICT, AMMO_NONE, 0, 0, 0.5, 0 }, + { WEAP_HOOK, NO_PREDICT, AMMO_NONE, 0, 0, 0.5, 0 }, + { WEAP_KNIFE, PRED_MODEL, AMMO_NONE, 0, 0, 0.5, 0 }, + { WEAP_MEDIKIT, PRED_MODEL, AMMO_CELLS, 0, 0, 0.5, 0 }, + { WEAP_SPANNER, PRED_MODEL, AMMO_CELLS, 0, 0, 0.5, 0 }, + { WEAP_AXE, PRED_MODEL, AMMO_NONE, 0, 0, 0.5, 0 }, + { WEAP_SNIPER_RIFLE, PRED_MODEL, AMMO_SHELLS, -9, 1, 1.5, 4 }, + { WEAP_AUTO_RIFLE, PRED_MODEL, AMMO_SHELLS, 0, 1, 0.1, 0 }, + { WEAP_SHOTGUN, PRED_MODEL, AMMO_SHELLS, 8, 1, 0.5, 2 }, + { WEAP_SUPER_SHOTGUN, PRED_MODEL, AMMO_SHELLS, 16, 2, 0.7, 3 }, + { WEAP_NAILGUN, PRED_PROJ, AMMO_NAILS, 0, 1, 0.2, 0, 1 }, + { WEAP_SUPER_NAILGUN, PRED_PROJ, AMMO_NAILS, 0, 2, 0.2, 0, 1 }, + { WEAP_GRENADE_LAUNCHER , PRED_PROJ, AMMO_ROCKETS, 6, 1, 0.6, 4 }, + { WEAP_PIPE_LAUNCHER, PRED_PROJ, AMMO_ROCKETS, 6, 1, 0.6, 4 }, // Overlaps GL + { WEAP_FLAMETHROWER, PRED_PROJ, AMMO_CELLS, 0, 1, 0.15, 0, 1 }, + { WEAP_ROCKET_LAUNCHER, PRED_PROJ, AMMO_ROCKETS, 4, 1, 0.8, 5 }, + { WEAP_INCENDIARY, PRED_PROJ, AMMO_ROCKETS, 0, 3, 0.9, 0 }, + { WEAP_ASSAULT_CANNON, PRED_PROJ, AMMO_SHELLS, 100, 1, 0.2, 4, 1 }, + { WEAP_LIGHTNING, NO_PREDICT, AMMO_CELLS, 0, 1, 0.1, 0 }, + { WEAP_DETPACK, NO_PREDICT, AMMO_NONE, 0, 0, 0, 0 }, + { WEAP_TRANQ, PRED_PROJ, AMMO_SHELLS, 0, 1, 1.5, 0 }, + { WEAP_RAILGUN, PRED_PROJ, AMMO_NAILS, 0, 1, 0.4, 0 }, + { WEAP_IMPELLER, PRED_PROJ, AMMO_NAILS, 0, 1, 0.6, 0 }, +}; + +inline var FO_WeapInfo* FO_GetWeapInfo(float weapon) { + return &weapon_info[weapon]; +} + +struct FO_ClassWeapons { + float id; + float slots[4]; + FO_WeapInfo *info[4]; +}; + +// Indexed by class ID, e.g. PC_SOLDIER +FO_ClassWeapons class_weapons[] = { + { PC_UNDEFINED, { 0, 0, 0, WEAP_AXE } }, + { PC_SCOUT, { WEAP_NAILGUN, WEAP_SHOTGUN, 0, WEAP_AXE } }, + { PC_SNIPER, { WEAP_SNIPER_RIFLE, WEAP_AUTO_RIFLE, WEAP_NAILGUN, WEAP_AXE } }, + { PC_SOLDIER, { WEAP_ROCKET_LAUNCHER, WEAP_SUPER_SHOTGUN, WEAP_SHOTGUN, WEAP_AXE } }, + { PC_DEMOMAN, { WEAP_GRENADE_LAUNCHER, WEAP_PIPE_LAUNCHER, WEAP_SHOTGUN, WEAP_AXE } }, + { PC_MEDIC, { WEAP_SUPER_NAILGUN, WEAP_SUPER_SHOTGUN, WEAP_SHOTGUN, WEAP_MEDIKIT } }, + { PC_HVYWEAP, { WEAP_ASSAULT_CANNON, WEAP_SUPER_SHOTGUN, WEAP_SHOTGUN, WEAP_AXE } }, + { PC_PYRO, { WEAP_INCENDIARY, WEAP_FLAMETHROWER, WEAP_SHOTGUN, WEAP_AXE } }, + { PC_SPY, { WEAP_TRANQ, WEAP_SUPER_SHOTGUN, WEAP_NAILGUN, WEAP_KNIFE } }, + { PC_ENGINEER, { WEAP_RAILGUN, WEAP_SUPER_SHOTGUN, WEAP_IMPELLER, WEAP_SPANNER } }, + { PC_RANDOM, { 0, 0, 0, WEAP_AXE } }, // TODO: Probably needs attention + { PC_CIVILIAN, { 0, 0, 0, WEAP_AXE } }, +}; + +enum { + GREN_FIRST = 100, + GREN_NORMAL = GREN_FIRST, + GREN_CONC, + GREN_BLAST, + GREN_NAIL, + GREN_SHOCK, + GREN_BURST, + GREN_MIRV, + GREN_NAPALM, + GREN_FLARE, // Not correctly predicted + GREN_GAS, + GREN_EMP, + GREN_FLASH, + GREN_CALTROP, + GREN_MIRVLET, + GREN_RED, + GREN_PIPE, + GREN_NONE, +}; + +struct FO_GrenInfo { + int id; + string name; + string model; + int skin; + string icon; + vector avelocity; + + // Automatically initialized below this line. + float modelindex; + string logname; +}; + +FO_GrenInfo fo_grenades[] = { + { GREN_NORMAL, "normal", "hgren2.mdl", 0, ICON_GREN_NORMAL, '300 300 300',}, + { GREN_CONC, "concussion", "hgren2.mdl", 1, ICON_GREN_CONCUSSION, '300 300 300'}, + { GREN_BLAST, "blast", "blastgren.mdl", 1, ICON_GREN_NAIL, '300 300 300'}, + { GREN_NAIL, "nail", "biggren.mdl", 1, ICON_GREN_NAIL, '0 300 0'}, + { GREN_SHOCK, "shock", "biggren.mdl", 1, ICON_GREN_NAIL, '0 300 0'}, + { GREN_BURST, "burst", "biggren.mdl", 1, ICON_GREN_NAIL, '0 300 0'}, + { GREN_MIRV, "mirv", "biggren.mdl", 0, ICON_GREN_MIRV, '0 300 0'}, + { GREN_NAPALM, "napalm", "biggren.mdl", 2, ICON_GREN_NAPALM, '0 300 0'}, + { GREN_FLARE, "flare", "flare.mdl", 1, ICON_GREN_FLARE, '300 300 300'}, + { GREN_GAS, "gas", "grenade2.mdl", 3, ICON_GREN_GAS, '300 300 300'}, + { GREN_EMP, "emp", "grenade2.mdl", 4, ICON_GREN_EMP, '300 300 300'}, + { GREN_FLASH, "flash", "flashgren.mdl", 0, ICON_GREN_FLASH, '300 300 300'}, + { GREN_CALTROP, "caltrop", "", 0, ICON_GREN_CALTROP, '300 300 300'}, + { GREN_MIRVLET, "mirv", "grenade2.mdl", 1, ICON_GREN_NONE, '250 300 400'}, + { GREN_RED, "gl", "grenade2.mdl", 1, ICON_GREN_NONE, '300 300 300'}, + { GREN_PIPE, "pipe", "grenade2.mdl", 2, ICON_GREN_NONE, '300 300 300'}, + { GREN_NONE, "none", "", 0, ICON_GREN_NONE, '0 0 0'}, +}; + +string FO_GrenName(float gtype) { + switch (gtype) { + case GREN_CONC: return "Concussion grenade"; + case GREN_NAIL: return "Nail grenade"; + case GREN_MIRV: return "Mirv grenade"; + case GREN_NAPALM: return "Napalm grenade"; + case GREN_FLARE: return "Flare"; + case GREN_GAS: return "Gas grenade"; + case GREN_EMP: return "EMP grenade"; + case GREN_FLASH: return "Flash grenade"; + case GREN_CALTROP: return "Caltrop canister"; + case GREN_BLAST: return "Blast grenade"; + case GREN_SHOCK: return "Shock grenade"; + case GREN_BURST: return "Burst grenade"; + default: return "Grenade"; + } +} + +enum { + kRadiusDamage, + kRadiusBounce, +}; + +struct FO_GrenExp { + int type; + int dmg; + int deathmsg; + +}; + +float FO_GrenGetExp(int gren_type, __out FO_GrenExp exp) { + exp.type = kRadiusDamage; + switch (gren_type) { + case GREN_NORMAL: + exp.dmg = 180; + exp.deathmsg = DMSG_GREN_HAND; + break; + case GREN_MIRVLET: + exp.dmg = 180; + exp.deathmsg = DMSG_GREN_MIRV; + break; + case GREN_CONC: + exp.dmg = 240; + exp.type = kRadiusBounce; + break; + case GREN_MIRV: + exp.dmg = 100; + exp.deathmsg = DMSG_GREN_MIRV; + break; + default: + return FALSE; + } + + return TRUE; +} + +struct FO_ClassGrenades { + float id; + float gren[2]; +}; + +FO_ClassGrenades class_grenades[] = { + { PC_UNDEFINED, {GREN_NONE, GREN_NONE} }, + { PC_SCOUT, {GREN_FLASH, GREN_CONC} }, + { PC_SNIPER, {GREN_NORMAL, GREN_FLARE} }, + { PC_SOLDIER, {GREN_NORMAL, GREN_SHOCK} }, + { PC_DEMOMAN, {GREN_NORMAL, GREN_MIRV} }, + { PC_MEDIC, {GREN_NORMAL, GREN_CONC} }, + { PC_HVYWEAP, {GREN_NORMAL, GREN_MIRV} }, + { PC_PYRO, {GREN_NORMAL, GREN_NAPALM} }, + { PC_SPY, {GREN_NORMAL, GREN_GAS} }, + { PC_ENGINEER, {GREN_NORMAL, GREN_EMP} }, + { PC_RANDOM, {GREN_NONE, GREN_NONE} }, // TODO: Probably needs attention + { PC_CIVILIAN, {GREN_NONE, GREN_NONE} }, +}; + +FO_GrenInfo* FO_GrenDesc(float gren_type) { + if (gren_type < GREN_FIRST) { + printf("proj missing gren_type [%d], tell newby\n", gren_type); + gren_type = GREN_FIRST; + } + return &fo_grenades[gren_type - GREN_FIRST]; +} + +FO_GrenInfo* FO_ClassGren(int playerclass, int index) { + return FO_GrenDesc(class_grenades[playerclass].gren[index]); +} + +#ifdef SSQC +float (entity ent, string ps_short, string ps_setting, string ps_default) FO_GetUserSetting; +float IsUsingOldImpulses(); + +static float IsPyroSlotSwapped() { + return FO_GetUserSetting(self, "cf_pyro_impulses", "cfpi", "off"); +} + +FO_GrenInfo* FO_PlayerGren(entity player, int index) { + return FO_ClassGren(player.playerclass, index); +} +#else +string(entity e, string key) infokey = #80; + +float csqc_get_user_setting(string s_short, string s_long, string def) { + string s = getplayerkeyvalue(player_localnum, s_short); + if (s == "") { + s = getplayerkeyvalue(player_localnum, s_long); + if (s == "") + s = def; + } + + switch (s) { + case "on": return TRUE; + case "off": return FALSE; + default: return stof(s); + } +} + +float IsUsingOldImpulses() { + return csqc_get_user_setting("owi", "old_weapon_impulses", "off"); +} + +static float IsPyroSlotSwapped() { + return csqc_get_user_setting("cfpi", "cf_pyro_impulses", "off"); +} + +float IsHoldGrenades() { + return csqc_get_user_setting("hg", "hold_grens", "off"); +} +#endif +// We keep these out of the main table just to keep things a little more +// wieldly/readable. +static string weapon_names[] = { + "None", "Hook", "Knife", "Medikit", "Spanner", "Axe", "Sniper Rifle", + "Auto Rifle", "Shotgun", "Super Shotgun", "Nailgun", "Super Nailgun", + "Grenade Launcher", "Pipebomb Launcher", "Flamethrower", "Rocket Launcher", + "Incendiary Launcher", "Assault Cannon", "Lightning Gun", "Detpack", + "Tranquilizer", "Railgun", "Impeller" +}; + +string FO_GetWeapName(float weapon) { + return weapon_names[weapon]; +} + + +struct FO_WeapModels { + int weapon; + string model; + + // Automatically populated. + int modelindex; + +}; + +static FO_WeapModels weapon_models[] = { + { WEAP_NONE, ""}, + { WEAP_HOOK, "progs/v_grap.mdl" }, + { WEAP_KNIFE, "progs/v_knife.mdl" }, + { WEAP_MEDIKIT, "progs/v_medi.mdl" }, + { WEAP_SPANNER, "progs/v_span.mdl" }, + { WEAP_AXE, "progs/v_axe.mdl" }, + { WEAP_SNIPER_RIFLE, "progs/v_srifle.mdl" }, + { WEAP_AUTO_RIFLE, "progs/v_srifle.mdl" }, + { WEAP_SHOTGUN, "progs/v_shot.mdl" }, + { WEAP_SUPER_SHOTGUN, "progs/v_shot2.mdl" }, + { WEAP_NAILGUN, "progs/v_nail.mdl" }, + { WEAP_SUPER_NAILGUN, "progs/v_nail2.mdl" }, + { WEAP_GRENADE_LAUNCHER, "progs/v_rock.mdl" }, + { WEAP_PIPE_LAUNCHER, "progs/v_pipe.mdl" }, + { WEAP_FLAMETHROWER, "progs/v_flame.mdl" }, + { WEAP_ROCKET_LAUNCHER, "progs/v_rock2.mdl" }, + { WEAP_INCENDIARY, "progs/v_irock.mdl" }, + { WEAP_ASSAULT_CANNON, "progs/v_asscan.mdl" }, + { WEAP_LIGHTNING, "" }, + { WEAP_DETPACK, "" }, + { WEAP_TRANQ, "progs/v_tranq.mdl" }, + { WEAP_RAILGUN, "progs/v_rail.mdl" }, + { WEAP_IMPELLER, "progs/v_light.mdl" }, +}; + +// REQUIRES: Order must match above. +static string AMMO_to_s[] = {"none", "shells", "nails", "cells", "rockets"}; + +Slot WEAP_to_slot(float playerclass, float weapon) { + for (float i = 0; i < TF_NUM_SLOTS; i++) { + if (class_weapons[playerclass].slots[i] == weapon) + return MakeSlot(i + 1); + } + return SlotNull; +} + +struct FO_WeapToItem { + int weapon; + float it_weapon; + + // Fields below this line automatically populated. + float ammo_mask; +}; + +FO_WeapToItem weapon_to_items[] = { + { WEAP_NONE, 0}, + { WEAP_HOOK, IT_HOOK}, + { WEAP_KNIFE, 0}, + { WEAP_MEDIKIT, 0}, + { WEAP_SPANNER, 0}, + { WEAP_AXE, 0}, + { WEAP_SNIPER_RIFLE, IT_SHOTGUN}, + { WEAP_AUTO_RIFLE, IT_SUPER_SHOTGUN}, + { WEAP_SHOTGUN, IT_SHOTGUN}, + { WEAP_SUPER_SHOTGUN, IT_SUPER_SHOTGUN}, + { WEAP_NAILGUN, IT_NAILGUN}, + { WEAP_SUPER_NAILGUN, IT_SUPER_NAILGUN}, + { WEAP_GRENADE_LAUNCHER, IT_GRENADE_LAUNCHER}, + { WEAP_PIPE_LAUNCHER, IT_GRENADE_LAUNCHER}, + { WEAP_FLAMETHROWER, IT_GRENADE_LAUNCHER}, + { WEAP_ROCKET_LAUNCHER, IT_ROCKET_LAUNCHER}, + { WEAP_INCENDIARY, IT_ROCKET_LAUNCHER}, + { WEAP_ASSAULT_CANNON, IT_ROCKET_LAUNCHER}, + { WEAP_LIGHTNING, IT_LIGHTNING}, + { WEAP_DETPACK, 0}, + { WEAP_TRANQ, IT_SHOTGUN}, + { WEAP_RAILGUN, IT_SHOTGUN}, + { WEAP_IMPELLER, IT_SHOTGUN}, +}; + +float FO_WeapAvailable(float weapon) { + if (weapon == WEAP_IMPELLER && !NewBalanceActive()) + return FALSE; + + return TRUE; +} + +FO_WeapInfo* FO_SlotWeapInfo(float playerclass, Slot slot) { + if (IsSlotNull(slot)) + return FO_GetWeapInfo(WEAP_NONE); + + float si = SlotIndex(slot); + if (playerclass == PC_ENGINEER && si == 2 && !NewBalanceActive()) + return FO_GetWeapInfo(WEAP_NONE); + + float idx = SlotIndex(slot); + float cf_pyro_impulses = IsPyroSlotSwapped(); + if (playerclass == PC_PYRO && cf_pyro_impulses && (idx == 0 || idx == 1)) + idx = 1 - idx; + + FO_WeapInfo* wi = class_weapons[playerclass]->info[idx]; + + if (!wi || !FO_WeapAvailable(wi->weapon)) + return FO_GetWeapInfo(WEAP_NONE); + + return wi; +} + +#ifndef CSQC +float FO_WeaponsMask(entity player) { + float mask = 0; + for (float i = 1; i <= TF_NUM_SLOTS; i++) { + FO_WeapInfo* wi = FO_SlotWeapInfo(player.playerclass, MakeSlot(i)); + if (wi) + mask |= wi->weapon; + } + return mask; +} +#endif + +float OldNgRof() { + return fo_config.old_ng_rof; +} + +// Slightly awkward construction, but convenient for calling from CSQC and SSQC. +float FO_NumClipStillLoading(FO_WeapInfo* wi, float now, float reload_finished) { + float tick = (wi->ammo_per_shot / wi->clip_size) * wi->full_reload_time; + return ceil((reload_finished - now) / tick) * wi->ammo_per_shot; +} + +float FO_CanReloadMsg(FO_WeapInfo* wi, int ammo_remaining, int clip_fired, + string* msg = __NULL__) { + if (clip_fired == 0) { + if (msg) *msg = "Clip full\n"; + } else if (ammo_remaining == 0) { + if (msg) *msg = strcat("Out of ", AMMO_to_s[wi->ammo_type], "\n"); + } else if (wi->clip_size - clip_fired == ammo_remaining) { + if (msg) *msg = strcat("All ", AMMO_to_s[wi->ammo_type], " are in the clip\n"); + } else + return TRUE; + + return FALSE; // msg filled in above. +} + +float self_class(); +Slot self_current_slot(); +float* self_clip_fired(Slot slot); + +float FO_CurrentWeapon() { + FO_WeapInfo* wi = FO_SlotWeapInfo(self_class(), self_current_slot()); + if (!wi) + return WEAP_NONE; + return wi->weapon; +} + +#ifdef SSQC +float FO_PlayerCurrentWeapon(entity player) { + FO_WeapInfo* wi = FO_SlotWeapInfo(player.playerclass, player.current_slot); + if (!wi) + return WEAP_NONE; + return wi->weapon; +} + +struct FO_WeapState { + float weapon; + Slot slot; + + // Constant and relative to weapon. TODO, migrate similar to WeapInfo. + FO_WeapInfo* wi; + + /* These are relative to the player passed. */ + float* ammo_remaining; + float* clip_fired; +}; + + +float* W_ammo_to_p(entity player, AmmoType ammo_type); + +void FO_FillWeapState(entity player, Slot slot, FO_WeapState* result) { + FO_WeapInfo* wi = FO_SlotWeapInfo(player.playerclass, slot); + + result->weapon = wi->weapon; + result->slot = slot; + result->wi = wi; + + result->ammo_remaining = W_ammo_to_p(player, wi->ammo_type); + + if (!IsSlotNull(slot)) + result->clip_fired = &player.clip_fired[SlotIndex(slot)]; + else + result->clip_fired = __NULL__; + + // Special case: Overlap with grenade launcher. + if (wi->weapon == WEAP_PIPE_LAUNCHER) + result->clip_fired = &player.clip_fired[0]; +} + + +inline void FO_FillCurrentWeapState(FO_WeapState* result) { + FO_FillWeapState(self, self.current_slot, result); +} + +void (entity pl) Status_Refresh; +void (float att_delay) Attack_Finished; + +// Hack until we fix Status_Refresh() to not be awful. +.float last_still_loading; +void W_UpdateCurrentWeapon(entity pl); +void sprint_pred(entity client, float msglevel, string s); + +void (entity pl, float weapon) UpdateClientReloadSound; + +void FO_ReloadSound() { + if (!ClientPred_Enabled(self, CSQC_WEAP_PRED)) + UpdateClientReloadSound(self, FO_CurrentWeapon()); +} + +void FO_ReloadFrame() { + if (self.tfstate & TFSTATE_RELOADING == 0) + return; + + FO_WeapState ws; + FO_FillWeapState(self, self.current_slot, &ws); + FO_WeapInfo* wi = ws.wi; + + if (self.reload_finished && (self.client_time >= self.reload_finished)) { + self.tfstate &= ~TFSTATE_RELOADING; + W_UpdateCurrentWeapon(self); + self.last_still_loading = 0; + Status_Refresh(self); + sprint_pred(self, PRINT_HIGH, "Finished reloading\n"); + + return; + } + + if (wi->weapon == WEAP_NONE) + return; + + float still_loading = FO_NumClipStillLoading(wi, self.client_time, + self.reload_finished); + + if (self.last_still_loading != still_loading) { + self.last_still_loading = still_loading; + FO_ReloadSound(); + Status_Refresh(self); + } +} + +float FO_CanReload(Slot slot) { + FO_WeapState ws; + FO_FillWeapState(self, slot, &ws); + FO_WeapInfo* wi = ws->wi; + + if (wi->ammo_type == AMMO_NONE) + return FALSE; + + if (wi->needs_reload && *ws->clip_fired > 0 + && *ws->ammo_remaining > wi->ammo_per_shot) + return TRUE; + + return FALSE; +} + +void FO_ReloadSlot(Slot slot, float force) { + if (self.tfstate & TFSTATE_RELOADING || IsSlotNull(slot)) + return; + + FO_WeapState ws; + FO_FillWeapState(self, slot, &ws); + FO_WeapInfo* wi = ws->wi; + + if (!wi->needs_reload) + return; + + string msg; + if (!FO_CanReloadMsg(wi, *ws->ammo_remaining, *ws->clip_fired, &msg)) { + sprint_pred(self, PRINT_HIGH, msg); + return; + } + + sprint_pred(self, PRINT_HIGH, + strcat("Reloading ", FO_GetWeapName(ws->weapon), "...\n")); + + Attack_Finished(wi->attack_time); // Carried.. but not sure it's necessary. + + FO_ReloadSound(); + + self.tfstate |= TFSTATE_RELOADING; + Status_Refresh(self); + + float reload_count = min(*ws->clip_fired, *ws->ammo_remaining); + float reload_duration = + (reload_count / wi->clip_size) * wi->full_reload_time; + + // TODO: Make ammo in clip independent from ammo_remaining (so you can't + // have shots in clip that are discarded and other weirdness.) + (*ws->clip_fired) -= reload_count; + + self.last_still_loading = reload_count; + self.reload_started = self.client_time; + self.reload_finished = self.client_time + reload_duration; +} + +float FO_CheckForReload() { + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + FO_WeapInfo* wi = ws->wi; + + if (wi->needs_reload && *ws->clip_fired >= wi->clip_size && + *ws->ammo_remaining > 0) { + if (force_reload) + FO_ReloadSlot(self.current_slot, TRUE); + return TRUE; + } else { + return FALSE; + } +} + +void FO_InstantReloadAllWeapons(entity player) { + player.tfstate &= ~TFSTATE_RELOADING; + player.reload_finished = 0; + player.last_still_loading = 0; + for (int i = 0; i < TF_NUM_SLOTS; i++) + player.clip_fired[i] = 0; +} +#endif + +struct ImpulseWeapon { + int class; + float weapons[7]; + + // Autogenerated + Slot slots[7]; +}; + +ImpulseWeapon impulse_weapons[] = { + { PC_UNDEFINED, { WEAP_AXE } }, + { PC_SCOUT, + { WEAP_AXE, WEAP_SHOTGUN, 0, WEAP_NAILGUN } }, + { PC_SNIPER, + { WEAP_AXE, WEAP_SNIPER_RIFLE, WEAP_AUTO_RIFLE, WEAP_NAILGUN } }, + { PC_SOLDIER, + { WEAP_AXE, WEAP_SHOTGUN, WEAP_SUPER_SHOTGUN, 0, 0, 0, WEAP_ROCKET_LAUNCHER } }, + { PC_DEMOMAN, + { WEAP_AXE, WEAP_SHOTGUN, 0, 0, 0, WEAP_GRENADE_LAUNCHER, WEAP_PIPE_LAUNCHER} }, + { PC_MEDIC, + { WEAP_MEDIKIT, WEAP_SHOTGUN, WEAP_SUPER_SHOTGUN, 0, WEAP_SUPER_NAILGUN } }, + { PC_HVYWEAP, + { WEAP_AXE, WEAP_SHOTGUN, WEAP_SUPER_SHOTGUN, 0, 0, 0, WEAP_ASSAULT_CANNON } }, + { PC_PYRO, + { WEAP_AXE, WEAP_SHOTGUN, 0, 0, 0, WEAP_FLAMETHROWER, WEAP_INCENDIARY } }, + { PC_SPY, + { WEAP_KNIFE, WEAP_TRANQ, WEAP_SUPER_SHOTGUN, WEAP_NAILGUN } }, + { PC_ENGINEER, + { WEAP_SPANNER, WEAP_RAILGUN, WEAP_SUPER_SHOTGUN, WEAP_IMPELLER } }, + { PC_RANDOM, { WEAP_AXE } }, // Never instantiated + { PC_CIVILIAN, { WEAP_AXE } }, +}; + +Slot FO_ImpulseToSlot(float playerclass, float impulse) { + if (impulse < 1 || impulse > 7) return SlotNull; + Slot r = impulse_weapons[playerclass].slots[impulse - 1]; + return r; +} + +Slot FO_SlotByInput(float playerclass, float input) { + if (input >= TF_SLOT1 && input <= TF_SLOT4) + return MakeSlot(input - TF_SLOT1 + 1); + else if (IsUsingOldImpulses() && (input >= 1 && input <= 7)) { + return impulse_weapons[playerclass].slots[input - 1]; + } else if (input >= 1 && input <= TF_NUM_SLOTS) { + return MakeSlot(input); + } else { + return SlotNull; + } +} + +Slot FO_FindPrevNextWeaponSlot(float playerclass, Slot slot, float is_prev) { + float i, len; + float direction = is_prev ? -1 : 1; + + if (IsUsingOldImpulses()) { + ImpulseWeapon* impulse_table = &impulse_weapons[playerclass]; + // For reasons that seem to rhyme with -ompilerbug, reads from + // impulse_table[offset] return junk so we use a pointer cursor. + Slot* cur; + len = 7; + direction = (direction + len) % len; + + for (i = 0; i < len; i++) { + cur = &impulse_table.slots[i]; + if (IsSameSlot(*cur, slot)) + break; + } + do { + i = (i + direction) % len; + if (!FO_WeapAvailable(impulse_table.weapons[i])) + continue; + cur = &impulse_table.slots[i]; + } while (IsSlotNull(*cur)); + + // Technically this will flip OWI+CFPI, but this is a nonsense combo. + return *cur; + } else { + len = 4; + direction = (direction + len) % len; + float idx = SlotIndex(slot); + for (i = 0; i < len - 1; i++) { + idx += direction; + Slot c = MakeSlot(1 + (idx % len)); + FO_WeapInfo* wi = FO_SlotWeapInfo(playerclass, c); + if (wi->weapon != WEAP_NONE) + return c; + } + + return slot; + } +} + + +void FO_Weapons_Init() { + float i, j; + + ASSERTD_EQ(weapon_names.length, weapon_info.length); + + FO_WeapInfo* WI_sr = FO_GetWeapInfo(WEAP_SNIPER_RIFLE); + + for (i = 0; i < weapon_info.length; i++) { + FO_WeapInfo* wi = &weapon_info[i]; + + if (wi->weapon == WEAP_PIPE_LAUNCHER) { + FO_WeapInfo* parent = FO_GetWeapInfo(WEAP_GRENADE_LAUNCHER); + wi->ammo_type = parent->ammo_type; + wi->clip_size = parent->clip_size; + wi->full_reload_time = parent->full_reload_time; + wi->needs_reload = parent->needs_reload; + } else if (wi->clip_size > 0) { + wi->needs_reload = TRUE; + } else { + wi->needs_reload = FALSE; + } + + FO_WeapModels* wm = &weapon_models[i]; + precache_model(wm->model); +#ifdef CSQC + wm->modelindex = getmodelindex(wm->model); +#endif + wi->models = wm; + + FO_WeapToItem* wti = &weapon_to_items[i]; + + wi->items = wti; + switch (wi->ammo_type) { + case AMMO_SHELLS: wti->ammo_mask = IT_SHELLS; break; + case AMMO_CELLS: wti->ammo_mask = IT_CELLS; break; + case AMMO_NAILS: wti->ammo_mask = IT_NAILS; break; + case AMMO_ROCKETS: wti->ammo_mask = IT_ROCKETS; break; + } + } + + if (OldNgRof()) { + FO_WeapInfo* wi = FO_GetWeapInfo(WEAP_NAILGUN); + wi->ammo_per_shot = 2; + FO_WeapInfo* wi = FO_GetWeapInfo(WEAP_SUPER_NAILGUN); + wi->ammo_per_shot = 4; + } + + for (i = 0; i < class_weapons.length; i++) { + for (j = 0; j < 4; j++) { + FO_ClassWeapons* cw = &class_weapons[i]; + + if (cw->slots[j]) + cw->info[j] = &weapon_info[cw->slots[j]]; + else + cw->info[j] = &weapon_info[0]; // WEAP_NONE + } + } + + for (i = 0; i < impulse_weapons.length; i++) { + ImpulseWeapon* iw = &impulse_weapons[i]; + + for (j = 0; j < 7; j++) + if (iw->weapons[j]) + iw->slots[j] = WEAP_to_slot(iw->class, iw->weapons[j]); + } + + for (i = 0; i < fo_grenades.length; i++) { + FO_GrenInfo* gdesc = &fo_grenades[i]; + ASSERTD_EQ(GREN_FIRST + i, gdesc->id); + + gdesc->logname = gdesc->name; + if (i < GREN_RED) + gdesc->logname = strcat(strtolower(gdesc->logname), "grenade"); + + if (gdesc->model != "") { + gdesc->model = strcat("progs/", gdesc->model); + gdesc->modelindex = getmodelindex(gdesc->model); + } + } + + for (i = 0; i < class_grenades.length; i++) { + FO_ClassGrenades* cg = &class_grenades[i]; + ASSERTD_EQ(i, cg->id); + } +} + +enumflags { + KF_SRC_PLAYER, + KF_TARG_PLAYER, + KF_SELF, + KF_QUAD, +}; + +const float KF_BOTH_PLAYER = KF_SRC_PLAYER | KF_TARG_PLAYER; + +// (*) Unbelievably knock-direction and moment calculation use different origins +// (centroid vs origin). For now just preserve what I can only assume is an old +// terrible mistake. We undo half of this by ignoring on src as mins/maxs==0. + +float CalcRadiusDamage(vector src, entity targ, float flags, float damage) { + vector targ_origin = targ.origin + (targ.mins + targ.maxs) * 0.5; // (*) + damage -= vlen(src - targ_origin) / 2; + + if (flags & KF_SELF) + damage *= 0.75; + + return damage; +} + +vector CalcKnock(vector src, entity targ, float flags, float moment) { + float mult = 8; + if (moment < 60 && (flags & (KF_BOTH_PLAYER | KF_SELF) == KF_BOTH_PLAYER)) + mult = 11; + + vector dir = normalize(targ.origin - src); + + vector result = dir * moment * mult; + if (fo_config.rj > 1) + result += dir * moment * fo_config.rj; + + return result; +} diff --git a/sniper.qc b/sniper.qc deleted file mode 100644 index e30023503..000000000 --- a/sniper.qc +++ /dev/null @@ -1,168 +0,0 @@ -//======================================================== -// Functions for the SNIPER class and associated weaponry -//======================================================== - -void (float zoom_to) Sniper_Zoom = { - self.default_fov = stof(infokey(self, "df")); - self.default_sensitivity = stof(infokey(self, "ds")); - self.zoom_fov = stof(infokey(self, "zf")); - - if (self.default_fov == 0) - return; - - if (zoom_to >= self.default_fov) - Sniper_ZoomReset(self); - else if (zoom_to != self.current_fov) { - if (zoom_to < self.default_fov) - self.zoom_last = zoom_to; - self.current_fov = zoom_to; - stuffcmd(self, "fov "); - stuffcmd(self, ftos(zoom_to)); - stuffcmd(self, "\n"); - - if (self.default_sensitivity > 0) { - stuffcmd(self, "sensitivity "); - stuffcmd(self, ftos(self.default_sensitivity * zoom_to / self.default_fov)); - stuffcmd(self, "\n"); - } - } -}; - -void (entity pl) Sniper_ZoomReset = { - pl.default_sensitivity = stof(infokey(pl, "ds")); - pl.default_fov = stof(infokey(pl, "df")); - - pl.is_zooming = 0; - - if (pl.default_fov > 0) { - pl.current_fov = pl.default_fov; - stuffcmd(pl, "fov "); - stuffcmd(pl, ftos(pl.default_fov)); - stuffcmd(pl, "\n"); - } - - if (pl.default_sensitivity > 0) { - stuffcmd(pl, "sensitivity "); - stuffcmd(pl, ftos(pl.default_sensitivity)); - stuffcmd(pl, "\n"); - } -}; - -void () Sniper_ZoomToggle = { - local float magnification = 0; - local float zoom_to = 0; - - if (self.playerclass != PC_SNIPER) - return; - - self.default_fov = stof(infokey(self, "df")); - self.default_sensitivity = stof(infokey(self, "ds")); - self.zoom_fov = stof(infokey(self, "zf")); - - if (self.default_fov == 0) { - sprint(self, PRINT_HIGH, "Use \"setinfo df \" to set default fov to use sniper zoom. Use \"setinfo ds \" to set default sensitivity for sensitivity scaling.\n"); - return; - } - - if (self.is_zooming) { - self.is_zooming = 0; - zoom_to = self.default_fov; - sprint(self, PRINT_HIGH, "Zoomed out\n"); - } else { - self.is_zooming = 1; - if (self.zoom_last && self.zoom_last < self.default_fov) - zoom_to = self.zoom_last; - else if (self.zoom_fov) - zoom_to = self.zoom_fov; - else - zoom_to = 30; - - magnification = floor(self.default_fov / zoom_to); - sprint(self, PRINT_HIGH, ftos(magnification), "x zoom\n"); - } - - Sniper_Zoom(zoom_to); -}; - -void (float zoom_in) Sniper_ZoomAdjust = { - local float zoom_to = 0; - local float zoom_steps = 0; - - if (self.playerclass != PC_SNIPER || !self.is_zooming) - return; - - zoom_steps = stof(infokey(self, "zs")); - if (!zoom_steps) - zoom_steps = 5; - - if (self.default_fov == 0) { - sprint(self, PRINT_HIGH, "Use \"setinfo df \" to set default fov to use sniper zoom. Use \"setinfo ds \" to set default sensitivity for sensitivity scaling.\n"); - return; - } - - if (zoom_in) - zoom_to = self.current_fov - zoom_steps; - else - zoom_to = self.current_fov + zoom_steps; - - if (zoom_to <= 10) - zoom_to = 10; - else if (zoom_to >= self.default_fov) - zoom_to = self.default_fov; - - self.zoom_last = zoom_to; - - Sniper_Zoom(zoom_to); -}; - -void () SniperSight_Update = { - local vector org; - - if (!(self.owner.tfstate & TFSTATE_AIMING) || - (self.owner.current_weapon != WEAP_SNIPER_RIFLE)) { - - self.owner.tfstate = - self.owner.tfstate - (self.owner.tfstate & TFSTATE_AIMING); - TeamFortress_SetSpeed(self.owner); - self.owner.heat = 0; - dremove(self); - return; - } - - makevectors(self.owner.v_angle); - org = self.owner.origin + v_forward * 10; - org_z = self.owner.absmin_z + self.owner.size_z * 0.7; - - traceline(org, org + v_forward * 9192, FALSE, self); - - if (trace_fraction == 1) { - setorigin(self, self.owner.origin); - return; - } - self.angles = vectoangles(v_forward); - setorigin(self, trace_endpos); - self.nextthink = time + 0.1; -}; - -void () SniperSight_Create = { - local entity sight; - - if (self.has_disconnected == TRUE || (self.tfstate & TFSTATE_RELOADING)) - return; - - self.tfstate = self.tfstate | TFSTATE_AIMING; - - sight = spawn(); - sight.owner = self; - sight.movetype = MOVETYPE_NOCLIP; - sight.solid = SOLID_NOT; - - setmodel(sight, "progs/sight.spr"); - - sight.classname = "timer"; - - setorigin(sight, self.origin); - - sight.think = SniperSight_Update; - sight.nextthink = time + 0.05; -}; diff --git a/ssqc/actions.qc b/ssqc/actions.qc new file mode 100644 index 000000000..753824c78 --- /dev/null +++ b/ssqc/actions.qc @@ -0,0 +1,539 @@ +//======================================================== +// Non Class-Specific Impulse Commands +//======================================================== + +void () TeamFortress_Discard = { + local string s = infokey(self,"keepcells"); + local float c = stof(s); + if(nokeepcells) //check if disabled serverside + c = -1; + else if (c == 0 && s != "0") + c = -1; //using this to track if player doesnt want to use keepcells setinfo + else if (c < 0) + c = -1; + else + { + if(self.ammo_cells > c) //changing c to track how many cells to include in discard + c = self.ammo_cells - c; + else + c = 0; + } + + + newmis = spawn(); + switch (self.playerclass) { + case PC_SCOUT: + newmis.ammo_rockets = self.ammo_rockets; + if(c > 0) + newmis.ammo_cells = c; + break; + case PC_ENGINEER: + newmis.ammo_rockets = self.ammo_rockets; + if(c > 0) + newmis.ammo_cells = c; + break; + case PC_MEDIC: + newmis.ammo_rockets = self.ammo_rockets; + if(c > 0) + newmis.ammo_cells = c; + break; + case PC_SNIPER: + newmis.ammo_rockets = self.ammo_rockets; + newmis.ammo_cells = self.ammo_cells; + break; + case PC_SPY: + newmis.ammo_rockets = self.ammo_rockets; + newmis.ammo_cells = self.ammo_cells; + break; + case PC_SOLDIER: + newmis.ammo_nails = self.ammo_nails; + newmis.ammo_cells = self.ammo_cells; + break; + case PC_DEMOMAN: + newmis.ammo_cells = self.ammo_cells; + newmis.ammo_nails = self.ammo_nails; + break; + case PC_HVYWEAP: + newmis.ammo_rockets = self.ammo_rockets; + newmis.ammo_nails = self.ammo_nails; + if(c > 0) + newmis.ammo_cells = c; + break; + case PC_PYRO: + newmis.ammo_nails = self.ammo_nails; + if(c > 0) + newmis.ammo_cells = c; + break; + default: + } + if ((newmis.ammo_rockets + newmis.ammo_cells + newmis.ammo_nails + newmis.ammo_shells) == 0) { + remove(newmis); + return; + } + + if (newmis.ammo_shells) { + self.ammo_shells = 0; + } + if (newmis.ammo_nails) { + self.ammo_nails = 0; + } + if (newmis.ammo_rockets) { + self.ammo_rockets = 0; + } + if (newmis.ammo_cells) { + if(self.playerclass == PC_SCOUT || self.playerclass == PC_MEDIC || self.playerclass == PC_PYRO || self.playerclass == PC_HVYWEAP || self.playerclass == PC_ENGINEER && c > 0) + self.ammo_cells = self.ammo_cells - c; + else + self.ammo_cells = 0; + } + FO_Sound(self, CHAN_AUTO, "weapons/lock4.wav", 1, ATTN_NORM); + newmis.dropped_by = self; + newmis.dropped_at = time; + newmis.weapon = 0; + newmis.movetype = MOVETYPE_TOSS; + newmis.solid = SOLID_TRIGGER; + newmis.classname = "ammobox"; + newmis.team_no = self.team_no; + makevectors(self.v_angle); + if (self.v_angle_x) { + newmis.velocity = v_forward * 400 + v_up * 200; + } else { + newmis.velocity = aim(self, 10000); + newmis.velocity = newmis.velocity * 400; + newmis.velocity_z = 200; + } + newmis.avelocity = '0 300 0'; + setsize(newmis, '-16 -16 0', '16 16 56'); + setorigin(newmis, self.origin); + newmis.nextthink = time + 30; + newmis.think = SUB_Remove; + newmis.touch = TeamFortress_AmmoboxTouch; + + if(splitbackpackmodels) + FO_SetModel(newmis, "progs/discard.mdl"); + else + FO_SetModel(newmis, "progs/backpack.mdl"); + +}; + +void () TeamFortress_Discard_DropAmmo = { + // if nothing to discard, do dropammo instead + float disc; + disc = 0; + + switch (self.playerclass) { + case PC_SCOUT: + case PC_ENGINEER: + disc = self.ammo_rockets; + break; + case PC_MEDIC: + disc = self.ammo_rockets; + break; + case PC_SNIPER: + case PC_SPY: + disc = self.ammo_rockets + self.ammo_cells; + break; + case PC_SOLDIER: + case PC_DEMOMAN: + disc = self.ammo_cells + self.ammo_nails; + break; + case PC_HVYWEAP: + disc = self.ammo_rockets + self.ammo_nails; + break; + case PC_PYRO: + disc = self.ammo_nails; + break; + default: + } + + if (disc == 0) { + if (self.menu_input != Menu_Drop_Input) + Menu_Drop(); + else + Menu_Close(self); + return; + } else { + TeamFortress_Discard(); + } +}; + +void () TeamFortress_SaveMe = { + local entity te, tl; + + if (time > self.next_saveme_sound) { + if (random() < 0.8) + FO_Sound(self, CHAN_WEAPON, "speech/saveme1.wav", 1, ATTN_NORM); + else + FO_Sound(self, CHAN_WEAPON, "speech/saveme2.wav", 1, ATTN_NORM); + + self.next_saveme_sound = time + 4; + } + te = find(world, classname, "player"); + while (te) { + if ((self == te) + || (te.playerclass == PC_MEDIC) + || (te.playerclass == PC_ENGINEER) + || (te.playerclass == PC_SPY)) { + if (((te.team_no == self.team_no) && (self.team_no != 0)) || + (te.playerclass == PC_SPY)) { + if (visible(te)) { + msg_entity = te; + tl = spawn(); + tl.origin = self.origin; + tl.origin_z = tl.origin_z + 32; + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_LIGHTNING3); + WriteEntity(MSG_ONE, tl); + WriteCoord(MSG_ONE, tl.origin_x); + WriteCoord(MSG_ONE, tl.origin_y); + WriteCoord(MSG_ONE, tl.origin_z + 24); + WriteCoord(MSG_ONE, self.origin_x); + WriteCoord(MSG_ONE, self.origin_y); + WriteCoord(MSG_ONE, self.origin_z); + dremove(tl); + } + } + } + te = find(te, classname, "player"); + } + self.saveme_time = time; +}; + +void (entity pe_player) FO_SpecTrackPoint = { + if(pe_player.classname != "observer") { + return; + } + + makevectors(pe_player.v_angle); + traceline(pe_player.origin, pe_player.origin + v_forward * 4096, 0, pe_player); + + if (trace_ent != world) { + if(trace_ent.classname == "player") { + stuffcmd(pe_player, strcat("track \"", trace_ent.netname, "\"\n")); + } + } +} + +void (entity pe_player) FO_Spectator_Identify = { + //ezquake draws player ids over their heads + if(pe_player.classname != "observer" || !infokeyf(pe_player, INFOKEY_P_CSQCACTIVE)) { + return; + } + + makevectors(pe_player.v_angle); + //start just forward of the player in case you're speccing someone + traceline(pe_player.origin + v_forward * 96, pe_player.origin + v_forward * 4096, MOVE_EVERYTHING, pe_player); + + if (trace_ent != world) { + local string s_id_string = ""; + if(trace_ent.classname == "player") { + //s_id_string = strcat(trace_ent.netname, "\n", TeamFortress_TeamGetColorString(trace_ent.team_no), " ", TeamFortress_GetClassName(trace_ent.playerclass), "\n"); + s_id_string = strcat(trace_ent.netname, "\n"); + if(trace_ent.playerclass == PC_SPY) { + if (trace_ent.undercover_team || trace_ent.undercover_skin) { + s_id_string = strcat(s_id_string, "\sDisguised as: \s "); + if (trace_ent.undercover_team != 0) + s_id_string = strcat(s_id_string, TeamFortress_TeamGetColorString(trace_ent.undercover_team)); + if (trace_ent.undercover_skin != 0) + s_id_string = strcat(s_id_string, " ", TeamFortress_GetClassName(trace_ent.undercover_skin)); + s_id_string = strcat(s_id_string, "\n"); + } + } + s_id_string = strcat(s_id_string, "\sH:\s ", ftos(trace_ent.health)); + s_id_string = strcat(s_id_string, " \sA:\s ", ftos(trace_ent.armorvalue), "\n"); + } else if(trace_ent.classname == "building_sentrygun") { + s_id_string = strcat(trace_ent.real_owner.netname, "'s Sentry Gun (", TeamFortress_TeamGetColorString(trace_ent.team_no), ")\n"); + s_id_string = strcat(s_id_string, "\sLevel:\s ", ftos(trace_ent.weapon), "\n"); + s_id_string = strcat(s_id_string, "\sH:\s ", ftos(rint(trace_ent.health)), "\n"); + } else if(trace_ent.classname == "building_sentrygun_base") { + s_id_string = strcat(trace_ent.real_owner.netname, "'s Sentry Gun (", TeamFortress_TeamGetColorString(trace_ent.team_no), ")\n"); + s_id_string = strcat(s_id_string, "\sLevel:\s ", ftos(trace_ent.oldenemy.weapon), "\n"); + s_id_string = strcat(s_id_string, "\sH:\s ", ftos(rint(trace_ent.oldenemy.health)), "\n"); + } else if(trace_ent.classname == "building_dispenser") { + s_id_string = strcat(trace_ent.real_owner.netname, "'s Dispenser (", TeamFortress_TeamGetColorString(trace_ent.team_no), ")\n"); + s_id_string = strcat(s_id_string, "\sH:\s ", ftos(rint(trace_ent.health)), "\n"); + } else if(trace_ent.classname == "detpack") { + s_id_string = strcat(trace_ent.owner.netname, "'s Detpack (", TeamFortress_TeamGetColorString(trace_ent.team_no), ")\n"); + s_id_string = strcat(s_id_string, "\sTime Left:\s ", ftos(trace_ent.detpack_left), " seconds\n"); + } + + // refresh status bar + pe_player.ident_time = time + 0.5; + if(pe_player.ident_string != s_id_string) { + pe_player.ident_string = s_id_string; + UpdateClientIDString(pe_player); + } + } +} + +void (entity pe_player, float f_type) CF_Identify = { + if(pe_player.classname == "observer") { + FO_Spectator_Identify(pe_player); + return; + } + + local vector v_source; + + makevectors(pe_player.v_angle); + v_source = pe_player.origin + v_forward * 10; + v_source_z = pe_player.absmin_z + pe_player.size_z * 0.7; + + traceline(v_source, v_source + v_forward * 2048, MOVE_LAGGED, pe_player); + if (trace_ent != world) { + local string s_id_string = "", s_class = "", s_name = ""; + local float f_health = 0, f_maxhealth = 0, f_armor = 0, f_maxarmor = 0, f_friendly = 0, f_fakefriendly = 0, f_sentryhealth = 0, f_maxsentryhealth = 0; + + // don't identify targets above water if player is under water + if (pe_player.waterlevel == 3 && !trace_ent.waterlevel) + return; + + // don't identify targets under water if player is above water + if (pe_player.waterlevel < 3 && trace_ent.waterlevel == 3) + return; + + // show as friendly if target is on your team or disguised as your team + if (pe_player.team_no) { + + if (pe_player.team_no == trace_ent.team_no) { + + // ignore teammates if type is set to enemies only + if (f_type == 3) + return; + + f_friendly = 1; + + } else if (pe_player.team_no == trace_ent.undercover_team) { + + // ignore teammates if type is set to enemies only + if (f_type == 3) + return; + + f_fakefriendly = 1; + + // ignore enemies if type is set to team only + } else if (f_type == 2) + return; + + } + + // alive player is found + if (trace_ent.classname == "player" && trace_ent.health) { + + s_name = trace_ent.netname; + if(votemode) { + if(trace_ent.vote_map) { + s_class = trace_ent.vote_map.netname; + } else { + s_class = "Has not voted"; + } + } else { + // set class and name + s_class = TeamFortress_GetClassName(trace_ent.playerclass); + + // set health if you're a medic + if (pe_player.playerclass == PC_MEDIC) { + f_health = trace_ent.health; + f_maxhealth = trace_ent.max_health; + } + + // set armor if you're an engineer + else if (pe_player.playerclass == PC_ENGINEER) { + f_armor = trace_ent.armorvalue; + f_maxarmor = trace_ent.maxarmor; + } + + // target is an enemy spy + if (trace_ent.playerclass == PC_SPY && !f_friendly) { + + // don't identify feigning enemy spies + if (IsFeigned(trace_ent)) + return; + + // use undercover name if available + if (trace_ent.undercover_name != string_null) + s_name = trace_ent.undercover_name; + + // set class to undercover skin + if (trace_ent.undercover_skin) + s_class = TeamFortress_GetClassName(trace_ent.undercover_skin); + + } + } + + // dispenser is found + } else if (trace_ent.classname == "building_dispenser") { + + if (pe_player == trace_ent.real_owner) + s_name = "Your dispenser"; + else + s_name = strcat(trace_ent.real_owner.netname, "'s dispenser"); + + s_class = ""; + + // sentry gun is found + } else if (trace_ent.classname == "building_sentrygun" || trace_ent.classname == "building_sentrygun_base") { + if (pe_player == trace_ent.real_owner) + s_name = "Your sentry gun"; + else { + s_name = strcat(trace_ent.real_owner.netname, "'s sentry gun"); + + if (pe_player.team_no == trace_ent.team_no) { + f_sentryhealth = trace_ent.health; + f_maxsentryhealth = trace_ent.max_health; + } + } + + s_class = ""; + } else { + return; + } + + s_name = strdecolorize(s_name); + + // set name + health (if medic) + if (f_maxhealth && (f_friendly || f_fakefriendly)) { + s_id_string = strcat(s_name, "\n"); + s_id_string = strcat(s_id_string, ftos(f_health)); + if (id_extended) { + s_id_string = strcat(s_id_string, "/"); + s_id_string = strcat(s_id_string, ftos(f_maxhealth)); + } + s_id_string = strcat(s_id_string, " hp\n"); + + // set name + armor (if engineer) + } else if (f_maxarmor && (f_friendly || f_fakefriendly)) { + s_id_string = strcat(s_name, "\n"); + s_id_string = strcat(s_id_string, ftos(f_armor)); + if (id_extended) { + s_id_string = strcat(s_id_string, "/"); + s_id_string = strcat(s_id_string, ftos(f_maxarmor)); + } + s_id_string = strcat(s_id_string, " armor\n"); + + // set name + health (if sentry + engineer) + } else if (f_maxsentryhealth) { + s_id_string = strcat(s_name, "\n"); + if (id_extended) { + s_id_string = strcat(s_id_string, ftos(floor(f_sentryhealth))); + s_id_string = strcat(s_id_string, "/"); + s_id_string = strcat(s_id_string, ftos(floor(f_maxsentryhealth))); + s_id_string = strcat(s_id_string, " health"); + } + s_id_string = strcat(s_id_string, "\n"); + + // just set name (if other class) + } else { + s_id_string = strcat("\n", s_name); + s_id_string = strcat(s_id_string, "\n"); + } + + if (votemode) { + // in a voting scenario, set same vs different vote indicator + if(pe_player.vote_map && trace_ent.vote_map) { + if(pe_player.vote_map == trace_ent.vote_map) { + s_id_string = strcat(s_id_string, "\bComrade\b\n"); + } else { + s_id_string = strcat(s_id_string, "\bOpposition\b\n"); + } + } + } else { + // set friendly/enemy + if (f_friendly || f_fakefriendly) + s_id_string = strcat(s_id_string, "Friendly"); + else + s_id_string = strcat(s_id_string, "Hostile"); + } + + // set class + if (s_class != "") { + s_id_string = strcat(s_id_string, " "); + s_id_string = strcat(s_id_string, s_class); + } + + pe_player.ident_time = time + 0.5; + + // don't update memory when the id string is the same + if (pe_player.ident_string == s_id_string) { + Status_Refresh(pe_player); + return; + } + + // refresh status bar + pe_player.ident_string = strzone(s_id_string); + Status_Refresh(pe_player); + } +}; + +void () TeamFortress_ReloadNext = { + if (self.playerclass < 1 || self.playerclass > 9) + return; + + Slot slot = self.current_slot; + + do { + slot = FO_FindPrevNextWeaponSlot(self.playerclass, slot, FALSE); + + if (FO_CanReload(slot)) { + FO_ReloadSlot(slot, FALSE); + return; + } + } while (!IsSameSlot(slot, self.current_slot)); + + sprint_pred(self, PRINT_HIGH, "All clips full\n"); +} + +entity TeamFortress_GetPracticeSpawn(entity e) = +{ + local entity spt = find(world, classname, "practicespawn"); + while (spt != world) + { + if (spt.owner == e) + { + return spt; + } + spt = find(spt, classname, "practicespawn"); + } + + return world; +}; + +void TeamFortress_RemovePracticeSpawn(entity e) = +{ + if(e == world) + return; + if(!allowpracspawns) + return; + + local entity spt = TeamFortress_GetPracticeSpawn(e); + + if(spt != world) + { + spawn_tfog(spt.origin); + dremove(spt); + } +}; + + +void TeamFortress_PlacePracticeSpawn(entity e) = +{ + if(e == world) + return; + if(!allowpracspawns) + return; + + TeamFortress_RemovePracticeSpawn(e); + + local entity ps = spawn(); + ps.owner = e; + ps.movetype = MOVETYPE_NONE; + ps.solid = SOLID_NOT; + ps.classname = "practicespawn"; + ps.angles = e.angles; + setsize(ps, '0 0 0', '0 0 0'); + setorigin(ps, e.origin + '0 0 8'); + FO_SetModel(ps, "progs/s_light.spr"); + sprint(e, PRINT_HIGH, "your practice spawn has been placed @ ", vtos(e.origin),".\n"); + spawn_tfog(ps.origin); +}; + + diff --git a/ssqc/admin.qc b/ssqc/admin.qc new file mode 100644 index 000000000..c8b278e24 --- /dev/null +++ b/ssqc/admin.qc @@ -0,0 +1,322 @@ + +void (entity p) BanCheater; +void (string halias, string commands) TeamFortress_AliasString; + +void () Admin_CountPlayers = { + local string st; + local float nump; + + nump = TeamFortress_GetNoPlayers(); + st = ftos(nump); + sprint(self, PRINT_HIGH, "Players in game: ", st, "\n"); + if (number_of_teams > 0) { + nump = TeamFortress_TeamGetNoPlayers(1); + st = ftos(nump); + sprint(self, PRINT_HIGH, "Players in blue team: ", st, "\n"); + } + if (number_of_teams > 1) { + nump = TeamFortress_TeamGetNoPlayers(2); + st = ftos(nump); + sprint(self, PRINT_HIGH, "Players in red team: ", st, "\n"); + } + if (number_of_teams > 2) { + nump = TeamFortress_TeamGetNoPlayers(3); + st = ftos(nump); + sprint(self, PRINT_HIGH, "Players in yellow team: ", st, "\n"); + } + if (number_of_teams > 3) { + nump = TeamFortress_TeamGetNoPlayers(4); + st = ftos(nump); + sprint(self, PRINT_HIGH, "Players in green team: ", st, "\n"); + } +}; + +void () Admin_ListIPs = { + if (TeamFortress_GetNoPlayers() <= 1) { + sprint(self, PRINT_HIGH, "No other players in the game\n"); + self.admin_use = world; + return; + } + self.admin_use = find(self.admin_use, classname, "player"); + while (self.admin_use != world) { + if ((self.admin_use.netname != string_null) && + (self.admin_use != self)) { + self.admin_use.ip = infokey(self.admin_use, "ip"); + sprint(self, PRINT_HIGH, self.admin_use.netname, " (", + self.admin_use.ip, ")\n"); + } + self.admin_use = find(self.admin_use, classname, "player"); + } + self.admin_use = find(self.admin_use, classname, "observer"); + while (self.admin_use != world) { + if ((self.admin_use.netname != string_null) && + (self.admin_use != self)) { + self.admin_use.ip = infokey(self.admin_use, "ip"); + sprint(self, PRINT_HIGH, self.admin_use.netname, " (", + self.admin_use.ip, ") (SPECTATOR)\n"); + } + self.admin_use = find(self.admin_use, classname, "observer"); + } + sprint(self, PRINT_HIGH, "End of player list\n"); + self.admin_use = world; +}; + +void () Admin_CycleDeal = { + if (TeamFortress_GetNoPlayers() <= 1) { + sprint(self, PRINT_HIGH, "No other players in the game\n"); + self.admin_use = world; + self.admin_mode = 0; + return; + } + if (self.admin_use.classname != "observer") { + self.admin_use = find(self.admin_use, classname, "player"); + while ((self.admin_use != world) && + ((self.admin_use.netname == string_null) + || (self.admin_use == self))) { + self.admin_use = find(self.admin_use, classname, "player"); + } + if (self.admin_use == world) { + self.admin_use = find(self.admin_use, classname, "observer"); + while ((self.admin_use != world) && ((self.admin_use.netname == string_null) || (self.admin_use == self))) { + self.admin_use = find(self.admin_use, classname, "observer"); + } + } + } + else { + self.admin_use = find(self.admin_use, classname, "observer"); + while ((self.admin_use != world) && ((self.admin_use.netname == string_null) || (self.admin_use == self))) { + self.admin_use = find(self.admin_use, classname, "observer"); + } + } + if (self.admin_use) { + self.admin_use.ip = infokey(self.admin_use, "ip"); + self.admin_mode = 1; + sprint(self, PRINT_HIGH, self.admin_use.netname, " (", self.admin_use.ip, ")"); + if (self.admin_use.classname == "observer") + sprint(self, PRINT_HIGH, " (spectator)"); + sprint(self, PRINT_HIGH, "\n kick/ban/next?\n"); + } + else { + self.admin_mode = 0; + sprint(self, PRINT_HIGH, "End of player list\n"); + } +}; + +void () Admin_DoKick = { + bprint(PRINT_HIGH, self.admin_use.netname, " was kicked by ", self.netname, "\n"); + KickCheater(self.admin_use); + self.admin_mode = 0; + self.admin_use = world; +}; + +void () Admin_DoBan = { + bprint(PRINT_HIGH, self.admin_use.netname, " was banned by ", self.netname, "\n"); + BanCheater(self.admin_use); + self.admin_mode = 0; + self.admin_use = world; +}; + +void () Admin_ForceSpectator = { + bprint(PRINT_HIGH, self.admin_use.netname, " was made spectator by ", self.netname, ". There's probably a good reason for this.\n"); + clientcommand(self.admin_use, "observe"); + self.admin_mode = 0; + self.admin_use = world; +}; + +void () CeaseFire_think = +{ + local entity te; + if (cease_fire == 0) { + dremove(self); + return; + } + te = find(world, classname, "player"); + while (te) { + CenterPrint3(te, "CEASE FIRE\nCalled by: ", self.owner.netname, "\n"); + te = find(te, classname, "player"); + } + self.nextthink = (time + 5); +}; + +void () Admin_CeaseFire = { + local entity te; + + if (!cease_fire) { + if (cb_prematch) { + StopTimer(); + } + cease_fire = 1; + cs_paused = 1; + bprint(2, "CEASE FIRE\n"); + te = find(world, classname, "player"); + while (te) { + CenterPrint3(te, "CEASE FIRE\nCalled by: ", self.netname, "\n"); + te.immune_to_check = (time + 5); + te.tfstate |= TFSTATE_CANT_MOVE; + te = find(te, classname, "player"); + } + te = spawn(); + te.owner = self; + te.classname = "ceasefire"; + te.think = CeaseFire_think; + te.nextthink = (time + 5); + } + else { + te = find(world, classname, "ceasefire"); + if (te) { + dremove(te); + } + cease_fire = 0; + cs_paused = 0; + bprint(2, "RESUME FIRE\n"); + if (cb_prematch) { + //Make sure you don't just start the countdown on resume when not everyone's readied up + if (is_countdown || v_ready == TeamFortress_GetNoPlayers () ) { + StartTimer(); + } + } + te = find(world, classname, "player"); + while (te) { + CenterPrint3(te, "RESUME FIRE\nCalled by: ", self.netname, "\n"); + te.immune_to_check = (time + 5); + te.tfstate &= ~TFSTATE_CANT_MOVE; + te = find (te, classname, "player"); + } + } +}; + +void () Admin_UpdateServer = { + bprint(PRINT_HIGH, "Server updating...\n"); + + float filehandle; + filehandle = fopen("upload_ready", FILE_WRITE); + fclose(filehandle); +}; + +void NotifyPauseUnpause(float is_pause) { + int count; + entity* p = find_list(classname, "player", EV_STRING, count); + + for (float i = 0; i < count; i++) { + if (p[i].netname == "" || !infokeyf(p[i], INFOKEY_P_CSQCACTIVE)) + continue; + + msg_entity = p[i]; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, is_pause ? MSG_PAUSE : MSG_UNPAUSE); + multicast('0 0 0', MULTICAST_ONE_R_NOSPECS); + } +} + +void () Admin_Pause = { + if (!is_paused) { + setpause(1); + NotifyPauseUnpause(TRUE); + is_paused = 1; + cs_paused = 1; + pause_actor = self.netname; + if (cb_prematch && is_countdown) { + StopTimer(); + bprint(2, pause_actor, " stops the countdown\n"); + return; + } + bprint(2, pause_actor, " paused the game\n"); + } + else { + if (cb_prematch) { + //Make sure you don't just start the countdown on resume when not everyone's readied up + if (is_countdown || v_ready == TeamFortress_GetNoPlayers () ) { + StartTimer(); + } + setpause(0); + is_paused = 0; + cs_paused = 0; + return; + } + pause_actor = self.netname; + if (!unpause_requested) { + bprint(2, pause_actor, " has requested to unpause the game in 5 seconds\n"); + unpause_requested = 1; + } + else { + bprint(2, pause_actor, " cancels unpause request\n"); + unpause_requested = 0; + unpause_countdown = 0; + unpause_lastcountnumber = 0; + } + } +} + +void (entity p) CheckAutoKick = { + local float rnum; + local entity te; + + if ((p.teamkills >= autokick_kills) && (autokick_kills != 0)) { + bprint(PRINT_HIGH, p.netname, + " was kicked for killing teammates\n"); + sprint(p, PRINT_HIGH, "You were kicked for killing teammates\n"); + KickCheater(p); + } + else if (autokick_kills != 0) { + if (p.teamkills == (autokick_kills - 1)) { + sprint(p, PRINT_HIGH, + "Kill one more teammate, and you will get kicked out\n"); + } + rnum = 0; + te = find(world, classname, "ak_timer"); + while (te) { + if (te.owner == p) { + rnum = 1; + te = world; + } else + te = find(te, classname, "ak_timer"); + } + if (rnum == 0) { + te = spawn(); + te.classname = "ak_timer"; + te.owner = p; + te.think = autokick_think; + te.nextthink = time + autokick_time; + } + } +}; + +void (string cl_pwd) Admin_Check = +{ + if (self.is_admin) { + sprint(self, PRINT_HIGH, "You are already an admin\n"); + return; + } + local string st2; + + st2 = infokey(world, "adminpwd"); + if (cl_pwd != string_null) { + stuffcmd(self, "setinfo adminpwd \""); + stuffcmd(self, "\"\n"); + if (st2 != string_null && cl_pwd == st2) { + self.is_admin = TRUE; + forceinfokey(self,"*admin", ftos(self.is_admin)); + bprint(2, self.netname, "\s gains full admin status!\s\n"); + sprint(self, 2, "Type \scmd list\s for admin commands.\n"); + sprint(self, 2, "You can also use the \sadminmenu\s command for adjusting some settings.\n"); + } + } +}; + +void () Admin_Aliases = { + TeamFortress_Alias("countplayers", TF_ADMIN_COUNTPLAYERS, 0); + TeamFortress_Alias("deal", TF_ADMIN_CYCLEDEAL, 0); + TeamFortress_Alias("kick", TF_ADMIN_KICK, 0); + TeamFortress_Alias("ban", TF_ADMIN_BAN, 0); + TeamFortress_Alias("next", TF_ADMIN_NEXT, 0); + TeamFortress_Alias("ceasefire", TF_ADMIN_CEASEFIRE, 0); + TeamFortress_Alias("listips", TF_ADMIN_LISTIPS, 0); + TeamFortress_Alias("clan", TF_ADMIN_CLANMODE, 0); + TeamFortress_Alias("quadmode", TF_ADMIN_QUADMODE, 0); + //TeamFortress_Alias("adminmenu", TF_ADMIN_ADMINMENU, 0); + TeamFortress_AliasString("adminmenu", "cmd adminmenu"); + TeamFortress_Alias("startmatch", TF_ADMIN_FORCESTARTMATCH, 0); + TeamFortress_Alias("readystatus", TF_ADMIN_READYSTATUS, 0); + stuffcmd(self,"alias ceasefire \"cmd ceasefire\"\n"); +}; + diff --git a/boss.qc b/ssqc/boss.qc similarity index 95% rename from boss.qc rename to ssqc/boss.qc index 66183b449..50d12de41 100644 --- a/boss.qc +++ b/ssqc/boss.qc @@ -14,11 +14,11 @@ void () boss_face = { }; void () boss_rise1 =[0, boss_rise2] { - sound(self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); }; void () boss_rise2 =[1, boss_rise3] { - sound(self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); }; void () boss_rise3 =[2, boss_rise4] { @@ -374,7 +374,7 @@ void () boss_shockc10 =[105, boss_death1] { }; void () boss_death1 =[48, boss_death2] { - sound(self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); }; void () boss_death2 =[49, boss_death3] { @@ -399,7 +399,7 @@ void () boss_death8 =[55, boss_death9] { }; void () boss_death9 =[56, boss_death10] { - sound(self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); WriteByte(MSG_MULTICAST, TE_LAVASPLASH); WriteCoord(MSG_MULTICAST, self.origin_x); @@ -436,14 +436,14 @@ void (vector p) boss_missile = { vec = normalize(d - org); launch_spike(org, vec); - setmodel(newmis, "progs/lavaball.mdl"); + FO_SetModel(newmis, "progs/lavaball.mdl"); newmis.avelocity = '200 100 300'; setsize(newmis, '0 0 0', '0 0 0'); newmis.velocity = vec * 300; newmis.touch = T_MissileTouch; - sound(self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); if (self.enemy.health <= 0) boss_idle1(); @@ -454,7 +454,7 @@ void () boss_awake = { self.movetype = MOVETYPE_STEP; self.takedamage = DAMAGE_NO; - setmodel(self, "progs/boss.mdl"); + FO_SetModel(self, "progs/boss.mdl"); setsize(self, '-128 -128 -24', '128 128 256'); if (skill == 0) @@ -552,7 +552,7 @@ void () lightning_use = { lightning_end = time + 1; - sound(self, CHAN_VOICE, "misc/power.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "misc/power.wav", 1, ATTN_NORM); lightning_fire(); self = find(world, classname, "monster_boss"); @@ -561,7 +561,7 @@ void () lightning_use = { self.enemy = activator; if ((le1.state == 0) && (self.health > 0)) { - sound(self, CHAN_VOICE, "boss1/pain.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "boss1/pain.wav", 1, ATTN_NORM); self.health = self.health - 1; if (self.health >= 2) { diff --git a/buttons.qc b/ssqc/buttons.qc similarity index 77% rename from buttons.qc rename to ssqc/buttons.qc index 0c6ab1f38..bacf0813e 100644 --- a/buttons.qc +++ b/ssqc/buttons.qc @@ -3,6 +3,7 @@ #define BUTTON_START_OUT 32 void () button_return; +void (entity pe_player, float dontstopdead) Spy_CheckForFuncTouch; void () button_wait = { self.state = STATE_TOP; @@ -18,6 +19,14 @@ void () button_done = { }; void () button_return = { + // q3f support + string targname; + entity targ; + targ = world; + targname = self.target; + targ = find(targ, targetname, targname); + if(targ) targ.active = TFGS_INACTIVE; + self.goal_state = TFGS_INACTIVE; self.state = STATE_DOWN; SUB_CalcMove(self.pos1, self.speed, button_done); @@ -34,7 +43,15 @@ void () button_fire = { if ((self.state == STATE_UP) || (self.state == STATE_TOP)) return; - sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + // q3f support + string targname; + entity targ; + targ = world; + targname = self.target; + targ = find(targ, targetname, targname); + if(targ) targ.active = TFGS_ACTIVE; + + FO_Sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); self.state = STATE_UP; SUB_CalcMove(self.pos2, self.speed, button_wait); @@ -48,12 +65,14 @@ void () button_use = { void () button_touch = { local entity te; - if (cb_prematch_time > time) + if (cb_prematch) return; if (other.classname != "player") return; + Spy_CheckForFuncTouch(other, 0); + if (self.goal_activation & TFGA_SPANNER) return; @@ -98,12 +117,24 @@ void () func_button = { precache_sound("buttons/switch02.wav"); self.noise = "buttons/switch02.wav"; } else if (self.sounds == 3) { - precache_sound("buttons/switch04.wav"); + //precache_sound("buttons/switch04.wav"); self.noise = "buttons/switch04.wav"; } SetMovedir(); + // q3 support + if (self.activetarget != "") { + self.target = self.activetarget; + } + if (self.allowteams == "blue") { + self.team_no = 1; + } + if (self.allowteams == "red") { + self.team_no = 2; + } + + self.movetype = MOVETYPE_PUSH; self.solid = SOLID_BSP; setmodel(self, self.model); diff --git a/camera.qc b/ssqc/camera.qc similarity index 96% rename from camera.qc rename to ssqc/camera.qc index 12756e2e1..06f9b07ef 100644 --- a/camera.qc +++ b/ssqc/camera.qc @@ -6,7 +6,7 @@ void () CamLock = { te = find(world, netname, self.netname); while (te) { if (te != self) { - sprint3(self, PRINT_HIGH, "Locked onto ", te.netname, + sprint(self, PRINT_HIGH, "Locked onto ", te.netname, "\n"); self.enemy = te; self.heat = vlen((self.enemy.origin - self.origin)); @@ -14,7 +14,7 @@ void () CamLock = { te = find(te, netname, self.netname); } if (self.enemy == world) - sprint2(self, PRINT_HIGH, te.netname, " not found\n"); + sprint(self, PRINT_HIGH, te.netname, " not found\n"); } else { sprint(self, PRINT_HIGH, "Removed lock\n"); self.enemy = world; @@ -22,12 +22,12 @@ void () CamLock = { }; void () CamDistLock = { - if (self.tfstate & 4096) { + if (self.tfstate & TFSTATE_LOCK) { sprint(self, PRINT_HIGH, "Distance lock off\n"); - self.tfstate = self.tfstate - (self.tfstate & 4096); + self.tfstate = self.tfstate - (self.tfstate & TFSTATE_LOCK); } else { sprint(self, PRINT_HIGH, "Distance lock on\n"); - self.tfstate = self.tfstate | 4096; + self.tfstate = self.tfstate | TFSTATE_LOCK; if (self.enemy) { self.camdist = vlen((self.enemy.origin - self.origin)); } @@ -242,7 +242,7 @@ void () CamDrop = { } prevte.camera_list = cam; st = ftos(tf); - sprint3(self, PRINT_HIGH, "Camera ", st, " dropped\n"); + sprint(self, PRINT_HIGH, "Camera ", st, " dropped\n"); }; void () fadetoblack = { diff --git a/ssqc/clan.qc b/ssqc/clan.qc new file mode 100644 index 000000000..ce5140c14 --- /dev/null +++ b/ssqc/clan.qc @@ -0,0 +1,1176 @@ +void () ReturnItem; + +void () MatchThink = +{ + local string tmp; + + if (self.cnt == -1) { + return; + } + if (cease_fire) { + self.nextthink = (time + 1); + return; + } + self.cnt2 = (self.cnt2 - 1); + if (!TeamFortress_GetNoPlayers()) { + NextLevel(); + return; + } + + if (self.cnt2 == 1) { + localcmd("serverinfo status \""); + tmp = ftos(self.cnt); + localcmd(tmp); + localcmd(" min left\"\n"); + } + + if (self.cnt2 == -1) { + self.cnt2 = 59; + self.cnt = (self.cnt - 1); + } + if (!self.cnt2) { + if (self.cnt == 1 || self.cnt == 5) { + tmp = ftos (self.cnt); + bprint(2, Q"\s[\s", tmp, "\s]\s minute"); + if (self.cnt != 1) { + bprint(2, "s"); + } + bprint(2, " remaining\n"); + } + if (!self.cnt) { + self.think = SUB_Remove; + self.nextthink = (time + 0.1); + NextLevel(); + return; + } + } + if (!self.cnt && (((self.cnt2 == 30) || (self.cnt2 == 15)) || (self.cnt2 <= 10))) { + tmp = ftos(self.cnt2); + bprint(2, Q"\s[\s", tmp, "\s]\s second"); + if (self.cnt2 != 1) { + bprint(2, "s"); + } + bprint(2, " remaining\n"); + } + gametime++; + self.nextthink = (time + 1); +}; + +void () StartMatch = +{ + local string st; + local entity te; + local entity oldself; + local entity gren; + + lightstyle(0, "m"); + bprint(2, "MATCH BEGINS NOW\n"); + + team4score = 0; + team3score = 0; + team2score = 0; + team1score = 0; + team4frags = 0; + team3frags = 0; + team2frags = 0; + team1frags = 0; + gren = find(world, classname, "grenade"); + while (gren) + { + //gren.think = GrenadeExplode; + //gren.nextthink = (time + 0.1); + dremove(gren); + gren = find(gren, classname, "grenade"); + } + te = find(world, classname, "detpack"); + while (te){ + if (te.is_disarming == 1) { + TeamFortress_SetSpeed (te.enemy); + dremove(te.oldenemy); + dremove(te.observer_list); + } + dremove(te.linked_list); + dremove(te); + te = find (te, classname, "detpack"); + } + if(duelmode && duel_no_packs) { + //Hide all packs + te = find(world, classname, "info_tfgoal"); + while (te){ + if (te.mdl) { + setmodel(te, string_null); + } + te = find (te, classname, "info_tfgoal"); + } + } + cb_prematch = 0; + cease_fire = 0; + cs_paused = 0; + + te = find(world, classname, "player"); + while (te) { + oldself = self; + self = te; + + TeamFortress_RemoveTimers(); + self.frags = 0; + self.real_frags = 0; + setspawnparms(self); + if (!quadmode) + PutClientInServer(); + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(self, TRUE); + } + self = oldself; + te = find(te, classname, "player"); + } + te = find(world, classname, "observer"); + while (te) { + if(infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(te, TRUE); + } + te = find(te, classname, "observer"); + } + + te = find(world, classname, "prematch"); + te.classname = "match"; + te.cnt = (timelimit / 60); + te.cnt2 = 60; + if (te.cnt == 0) + { + bprint(2, "Warning no timelimit set!\n"); + localcmd("\nserverinfo status \"0 min left\"\n"); + te.cnt = -1; + te.cnt2 = -1; + } + else + { + te.cnt = (te.cnt - 1); + localcmd("\nserverinfo status \""); + st = ftos(te.cnt); + localcmd(st); + localcmd(" min left\"\n"); + if (!quadmode) { + te.think = MatchThink; + te.nextthink = (time + 1); + } + } + + if (quadmode) { + if (rounds) { + StartQuadRound(); + } + } + if (duelmode) { + if(duel_spawn_guard) { + StartSpawnGuard(); + } + //ResetPlayers(); + CountMatchPlayersAndReset(); + } + + FilterBodyQueue(TRUE); +}; + +float () GetPlayerCount +{ + local entity te; + local float tmp = 0; + te = find (world, classname, "player"); + while (te != world) { + if (te.playerclass) { + tmp = tmp + 1; + } + te = find (te, classname, "player"); + } + return tmp; +} + +string () GetPlayersName +{ + local entity te; + local float tmp = 0; + local float tmp2 = 0; + local string st = ""; + local string plname; + + while (tmp < number_of_teams) { + tmp = tmp + 1; + tmp2 = 0; + te = find (world, classname, "player"); + while (te != world) { + if (te.team_no == tmp && te.playerclass) { + tmp2 = 1; + if (loginRequired) + plname = te.login; + else + plname = te.netname; + st = strcat (st, "_"); + if (strlen(plname) <= 13) { + st = strcat( st, plname ); + } else { + st = strcat( st, substring(plname,0,12)); + } + } + te = find (te, classname, "player"); + } + if (tmp2 == 1 && tmp != number_of_teams) { + st = strcat ( st, "_VS"); + } + } + return (st); +}; + +string () GetGameFileName = { + local float i; + local string tmp; + local entity t; + local string str; + + str = ""; + i = 0; + t = find (world, classname, "prematch"); + if (t != world) + { + str = strftime(TRUE, "%Y-%m-%d-%H-%M-%S"); + } + str = strcat(str, "_["); + str = strcat(str, mapname); + str = strcat(str, "]_"); + + while ((i < number_of_teams)) + { + i = (i + 1); + tmp = GetTeamName (i); + str = strcat(str,"_"); + str = strcat(str,tmp); + if ((i < number_of_teams)) + { + str = strcat(str, "_vs"); + } + } + str = clearString(str); + return str; +} + +void () PreMatch_Think = { + local entity p; + local string num, tmp, str; + local float fl; + + self.cnt2 = (self.cnt2 - 1); + if (self.cnt2 == 1) { + p = find(world, classname, "player"); + while (p != world) { + if (p.netname != "" && p.team_no && p.playerclass) { + p.respawn_time = (time + 2); + p.takedamage = 0; + p.solid = 0; + p.movetype = 0; + p.modelindex = 0; + p.model = string_null; + } + p = find(p, classname, "player"); + } + } + else { + if (!self.cnt2) { + is_countdown = 0; + self.nextthink = (time + 0.1); + self.think = SUB_Remove; + gametime = 0; + canlog = 1; + LogEventGameStart(); + p = find(world, classname, "player"); + while (p != world) { + if (p.netname != "" && p.team_no && p.playerclass) { + p.takedamage = 2; + p.solid = 3; + p.movetype = 3; + } + p.classtime = gametime; + LogEventPlayerStart(p); + p = find(p, classname, "player"); + } + if (!quadmode) + StartMatch(); + else { + StartQuadRound(); + } + return; + } + } + fl = (self.cnt2 / 60); + if (fl == 1 || fl == 2 || fl == 3 || fl == 4 || fl == 5 || fl == 6 || fl == 7 || fl == 8 || fl == 9 || fl == 10) { + num = ftos(fl); + bprint(2, "Match will begin in ", num, " minute(s).\n"); + } + if (self.cnt2 == 30) { + num = ftos(self.cnt2 / 60); + bprint(2, "Match will begin in 30 seconds.\n"); + } + if (self.cnt2 <= 10) { + if (self.cnt2 == 10) { + is_countdown = 1; + lightstyle(0, "e"); + //preventing double demo recording + localcmd("cancel\n"); + if (stof(infokey(world, "demo_auto_left")) > 0) { + if (infokey(world, "serverdemo") == "on") { + + localcmd("record \""); + str = strzone(GetGameFileName()); + game_filename = str; + localcmd(strzone(str)); + localcmd("\"\n"); + + str = strcat(str,".json"); + str = strcat(str); + fclose(logfilehandle); + logfilehandle = fopen(str,FILE_WRITE); + strunzone(str); + } + } + } + num = strzone(ftos(self.cnt2)); + p = find(world, classname, "player"); + tmp = strcat(num ,"\n\n\b:[\b"); + tmp = strcat(tmp, mapname); + tmp = strcat(tmp, "\b]:\b"); + strcat(tmp, "\n"); + + while (p != world) { + if (p.netname != "") { + CenterPrint3 (p, "Countdown ", tmp, "\n"); + if (self.cnt2 < 6) { + cease_fire = 0; + cs_paused = 0; + stuffcmd (p, "play buttons/switch04.wav\n"); + } + } + p = find(p, classname, "player"); + } + if (self.cnt2 > 1) { + bprint(2, num, " seconds \n"); + } + else { + bprint(2, "1 second \n"); + } + strunzone(num); + } + + self.nextthink = (time + 1); +}; + +void () ResetBreakAndReady = { + local entity te; + te = find (world, classname, "player"); + while (te != world) { + te.bvote = 0; + te.stat_flags &= ~PLAYER_READY; + UpdateReadyStatus(); + v_break = 0; + v_ready = 0; + + if(infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(te, TRUE); + } + te = find (te, classname, "player"); + } + te = find(world, classname, "observer"); + while (te) { + if(infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(te, TRUE); + } + te = find(te, classname, "observer"); + } +} + +void () StartTimer = +{ + local entity timer; + local entity p; + local entity te; + + if (clanbattle == 0) { + if (self != world) { + sprint(self, 2, "Clan mode off....\n"); + sprint(self, 2, "Match cannot be started.\n"); + } + else { + dprint("Clan mode off....\n"); + dprint("Match cannot be started.\n"); + } + return; + } + if (clanbattle == 1 && cb_prematch == 0) { + if (self != world) { + sprint(self, 2, "Clan Battle in progress....\n"); + sprint(self, 2, "Type break to stop the current battle.\n"); + } + else { + dprint("Clan Battle in progress....\n"); + dprint("Type break to stop the current battle.\n"); + } + return; + } + timer = find(world, classname, "prematch"); + while (timer != world) { + dremove(timer); + timer = find(timer, classname, "prematch"); + } + te = find(world,classname,"pmmessage"); + if (te != world) { + te.think = SUB_Remove; + te.nextthink = 0.01; + } + + ResetBreakAndReady(); + timer = spawn(); + timer.owner = world; + timer.classname = "prematch"; + timer.cnt = 0; + timer.cnt2 = rint(stof(infokey(world, "count"))); + if (timer.cnt2 < 1) { + //By default, 10 seconds of countdown + timer.cnt2 = 10; + round_end_time = time + 11; + } + timer.cnt2 = (timer.cnt2 + 1); + timer.nextthink = (time + 0.1); + timer.think = PreMatch_Think; + + p = find(world, classname, "player"); + while (p != world) { + if (p.netname != "") { + stuffcmd(p, "play items/protect2.wav\n"); + } + p = find(timer, classname, "player"); + } +}; + +void () StopTimer = +{ + local entity t; + + if (infokey(world, "serverdemo") != string_null) { + localcmd("cancel\n"); + if (logfilehandle > 0) + fclose(logfilehandle); + } + if (cb_prematch) { + t = find(world, classname, "prematch"); + while (t != world) { + t.nextthink = (time + 0.1); + t.think = SUB_Remove; + t = find (t, classname, "prematch"); + } + localcmd("serverinfo status Standby\n"); + lightstyle(0, "m"); + return; + } + t = find(world, classname, "match"); + if (t != world) { + remove(t); + } + localcmd("serverinfo status Normal\n"); +}; + +void () DumpClanScores = { + local float winners; + local float no_teams; + local float printed; + local float ti; + local float tno; + local float teamfrags; + local string st; + local entity te; + local float t1_pl; + local float t1_unacc; + local float t2_pl; + local float t2_unacc; + local float t3_pl; + local float t3_unacc; + local float t4_pl; + local float t4_unacc; + + t1_pl = TeamFortress_TeamGetNoPlayers(1); + t2_pl = TeamFortress_TeamGetNoPlayers(2); + t3_pl = TeamFortress_TeamGetNoPlayers(3); + t4_pl = TeamFortress_TeamGetNoPlayers(4); + + printed = 0; + no_teams = 0; + if (t1_pl) { + no_teams = no_teams + 1; + } + if (t2_pl) { + no_teams = no_teams + 1; + } + if (t3_pl) { + no_teams = no_teams + 1; + } + if (t4_pl) { + no_teams = no_teams + 1; + } + if (no_teams < 2) { + return; + } + t4_unacc = 0; + t3_unacc = 0; + t2_unacc = 0; + t1_unacc = 0; + + ti = 0; + + teamfrags = toggleflags & (TFLAG_TEAMFRAGS | TFLAG_FULLTEAMSCORE); + + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 1) { + t1_unacc = t1_unacc + te.real_frags; + } else if (te.team_no == 2) { + t2_unacc = t2_unacc + te.real_frags; + } else if (te.team_no == 3) { + t3_unacc = t3_unacc + te.real_frags; + } else if (te.team_no == 4) { + t4_unacc = t4_unacc + te.real_frags; + } + te = find(te, classname, "player"); + } + t1_unacc = team1frags - t1_unacc; + t2_unacc = team2frags - t2_unacc; + t3_unacc = team3frags - t3_unacc; + t4_unacc = team4frags - t4_unacc; + + winners = TeamFortress_TeamGetWinner(); + + bprint(PRINT_HIGH, "\n\n=------= Match results =------=\n"); + if ((no_teams == 2) && (((team1score == team2score) && teamfrags) + || ((team1frags == team2frags) && !teamfrags))) { + bprint(PRINT_HIGH, " draw "); + } + else if ((no_teams == 3) && + ((((team1score == team2score) == team3score) && teamfrags) + || (((team1frags == team2frags) == team3frags) && + !teamfrags))) { + bprint(PRINT_HIGH, " draw "); + } + else if ((no_teams == 3) + && + (((((team1score == team2score) == team3score) == team4score) + && teamfrags) + || + ((((team1frags == team2frags) == team3frags) == team4frags) + && !teamfrags))) { + bprint(PRINT_HIGH, " draw "); + } + else { + st = GetTeamName(winners); + bprint(PRINT_HIGH, st, " defeated "); + if ((winners != 1) && (t1_pl != 0)) { + st = GetTeamName(1); + bprint(PRINT_HIGH, st); + printed = printed + 1; + } + if ((winners != 2) && (t2_pl != 0)) { + st = GetTeamName(2); + if (printed == no_teams) { + bprint(PRINT_HIGH, " and ", st); + } else if (printed) { + bprint(PRINT_HIGH, ", ", st); + } else { + bprint(PRINT_HIGH, st); + } + printed = printed + 1; + } + if ((winners != 3) && (t3_pl != 0)) { + st = GetTeamName(3); + if (printed == no_teams) { + bprint(PRINT_HIGH, " and ", st); + } else if (printed) { + bprint(PRINT_HIGH, ", ", st); + } else { + bprint(PRINT_HIGH, st); + } + printed = printed + 1; + } + if ((winners != 4) && (t4_pl != 0)) { + st = GetTeamName(4); + if (printed == no_teams) { + bprint(PRINT_HIGH, " and ", st); + } else if (printed) { + bprint(PRINT_HIGH, ", ", st); + } else { + bprint(PRINT_HIGH, st); + } + } + } + bprint(PRINT_HIGH, "\n\n"); + + st = infokey(world, "dtf"); + if (st == string_null) + st = infokey(world, "dont_tweak_frags"); + + if (st != "on") { + if (teamfrags != 0) { + if (t1_pl > 0) { + printed = floor(team1score / t1_pl); + ti = 0; + if ((printed * t1_pl) != team1score) { + ti = team1score - (printed * t1_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 1) { + te.frags = printed; + } + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + te = find(te, classname, "player"); + } + } + if (t2_pl > 0) { + printed = floor((team2score / t2_pl)); + ti = 0; + if ((printed * t2_pl) != team2score) { + ti = team2score - (printed * t2_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 2) { + te.frags = printed; + } + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + te = find(te, classname, "player"); + } + } + if (t3_pl > 0) { + printed = floor((team3score / t3_pl)); + ti = 0; + if ((printed * t3_pl) != team3score) { + ti = team3score - (printed * t3_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 3) { + te.frags = printed; + } + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + te = find(te, classname, "player"); + } + } + if (t4_pl > 0) { + printed = floor((team4score / t4_pl)); + ti = 0; + if ((printed * t4_pl) != team4score) { + ti = team4score - (printed * t4_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 4) { + te.frags = printed; + } + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + te = find(te, classname, "player"); + } + } + } else { + if ((t1_pl > 0) && (t1_unacc > 0)) { + printed = floor(t1_unacc / t1_pl); + ti = 0; + if ((printed * t1_pl) != t1_unacc) { + ti = t1_unacc - (printed * t1_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 1) { + te.frags = te.real_frags + printed; + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + } + te = find(te, classname, "player"); + } + } + if ((t2_pl > 0) && (t2_unacc > 0)) { + printed = floor((t2_unacc / t2_pl)); + ti = 0; + if ((printed * t2_pl) != t2_unacc) { + ti = t2_unacc - (printed * t2_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 2) { + te.frags = te.real_frags + printed; + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + } + te = find(te, classname, "player"); + } + } + if ((t3_pl > 0) && (t3_unacc > 0)) { + printed = floor((t3_unacc / t3_pl)); + ti = 0; + if ((printed * t3_pl) != t3_unacc) { + ti = t3_unacc - (printed * t3_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 3) { + te.frags = te.real_frags + printed; + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + } + te = find(te, classname, "player"); + } + } + if ((t4_pl > 0) && (t4_unacc > 0)) { + printed = floor((t4_unacc / t4_pl)); + ti = 0; + if ((printed * t4_pl) != t4_unacc) { + ti = t4_unacc - (printed * t4_pl); + } + te = find(world, classname, "player"); + while (te) { + if (te.team_no == 4) { + te.frags = te.real_frags + printed; + if (ti) { + te.frags = te.frags + 1; + ti = ti - 1; + } + } + te = find(te, classname, "player"); + } + } + } + } + if (t1_pl > 0) { + bprint(PRINT_HIGH, "\n=------= Blue team results =------=\n"); + tno = TeamFortress_TeamGetNoPlayers(1); + st = ftos(tno); + bprint(PRINT_HIGH, st, " players\n"); + st = ftos(team1frags); + bprint(PRINT_HIGH, st, " frags, "); + st = ftos(t1_unacc); + bprint(PRINT_HIGH, st, " unaccounted for\n"); + st = ftos(team1score); + bprint(PRINT_HIGH, st, " team score\n"); + } + if (t2_pl > 0) { + bprint(PRINT_HIGH, "\n=------= Red team results =------=\n"); + tno = TeamFortress_TeamGetNoPlayers(2); + st = ftos(tno); + bprint(PRINT_HIGH, st, " players\n"); + st = ftos(team2frags); + bprint(PRINT_HIGH, st, " frags, "); + st = ftos(t2_unacc); + bprint(PRINT_HIGH, st, " unaccounted for\n"); + st = ftos(team2score); + bprint(PRINT_HIGH, st, " team score\n"); + } + if (t3_pl > 0) { + bprint(PRINT_HIGH, "\n=------= Yellow team results =------=\n"); + tno = TeamFortress_TeamGetNoPlayers(3); + st = ftos(tno); + bprint(PRINT_HIGH, st, " players\n"); + st = ftos(team3frags); + bprint(PRINT_HIGH, st, " frags, "); + st = ftos(t3_unacc); + bprint(PRINT_HIGH, st, " unaccounted for\n"); + st = ftos(team3score); + bprint(PRINT_HIGH, st, " team score\n"); + } + if (t4_pl > 0) { + bprint(PRINT_HIGH, "\n=------= Green team results =------=\n"); + tno = TeamFortress_TeamGetNoPlayers(4); + st = ftos(tno); + bprint(PRINT_HIGH, st, " players\n"); + st = ftos(team4frags); + bprint(PRINT_HIGH, st, " frags, "); + st = ftos(t4_unacc); + bprint(PRINT_HIGH, st, " unaccounted for\n"); + st = ftos(team4score); + bprint(PRINT_HIGH, st, " team score\n"); + } + te = find(world, classname, "player"); + while (te) { + st = infokey(te, "take_sshot"); + if (st != string_null) { + stuffcmd(te, "screenshot\n"); + } + + if (te.playerclass != 0) { + local float timeplayed = gametime - te.classtime; + LogEventChangeClass(te, te.playerclass, 0, timeplayed); + te.classtime = gametime; + } + + te = find(te, classname, "player"); + } +}; + +void () TeamFortress_ShowIDs = { + local entity te; + local float got_one; + local string st; + + if (self.team_no == 0) { + sprint(self, PRINT_HIGH, "You are not in a team\n"); + return; + } + got_one = 0; + sprint(self, PRINT_HIGH, "Existing team member IDs:\n"); + + te = find(world, classname, "player"); + while (te) { + if (te.team_no == self.team_no) { + got_one = 1; + st = ftos(te.tf_id); + sprint(self, PRINT_HIGH, te.netname, " : ", st, "\n"); + } + te = find(te, classname, "player"); + } + + if (!got_one) + sprint(self, PRINT_HIGH, "None\n"); + got_one = 0; + sprint(self, PRINT_HIGH, "Disconnected team member IDs:\n"); + + te = find(world, classname, "ghost"); + while (te) { + if (te.team_no == self.team_no) { + got_one = 1; + st = ftos(te.tf_id); + sprint(self, 2, st, "\n"); + } + te = find(te, classname, "ghost"); + } + + if (!got_one) + sprint(self, PRINT_HIGH, "None\n"); +}; + +void () CenterPrint_Players_NotReady = { + string players_not_ready; + entity te; + players_not_ready = "Not ready: "; + + local float first = 0; + te = find (world, classname, "player"); + while (te) { + if (!(te.stat_flags & PLAYER_READY) && !te.has_disconnected) + { + first = first + 1; + if (first == 1) + { + players_not_ready = strcat(players_not_ready, te.netname); + } + else if (first > 1 && first < 5) + { + players_not_ready = strcat(players_not_ready, ", ", te.netname); + } + else if (first >= 5) + { + players_not_ready = strcat(players_not_ready, " and others..."); + break; + } + } + te = find (te, classname, "player"); + } + players_not_ready = strcat(players_not_ready, "\n", "Type \s/ready\s in the console to toggle your ready status\n"); + + te = find(world, classname, "player"); + while (te) + { + if(!infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + CenterPrint(te, players_not_ready); + } + + te = find(te, classname, "player"); + } +} + +void () Broadcast_Players_NotReady = { + local entity te; + if (v_ready != 0) { + local string players_not_ready = "Not ready: "; + local float first = 1; + te = find (world, classname, "player"); + while (te != world) { + if (!(te.stat_flags & PLAYER_READY) && !te.has_disconnected) { + if (!first) + players_not_ready = strcat(players_not_ready, ", "); + else + first = 0; + players_not_ready = strcat(players_not_ready, te.netname); + } + te = find (te, classname, "player"); + } + players_not_ready = strcat(players_not_ready, "\n"); + bprint(1, players_not_ready); + te = find (world, classname, "player"); + while (te != world) { + if (!(te.stat_flags & PLAYER_READY) && !te.has_disconnected) + sprint(te, 2, "If you are ready, type \s/ready\s in the console\n"); + te = find(te, classname, "player"); + } + return; + } + else { + bprint(1, "No player is ready\n"); + } + +} + +void () PlayerNotReady = { + if (self.stat_flags & PLAYER_READY) { + self.stat_flags &= ~PLAYER_READY; + UpdateReadyStatus(); + bprint (2, self.netname, " is NOT ready anymore\n "); + v_ready = (v_ready - 1); + Broadcast_Players_NotReady(); + } +} + +void () UpdateReadyStatus = { + local float players_size = 0; + local float players_ready_size = 0; + local float team_1_size = 0; + local float team_1_ready_size = 0; + local float team_2_size = 0; + local float team_2_ready_size = 0; + local float team_3_size = 0; + local float team_3_ready_size = 0; + local float team_4_size = 0; + local float team_4_ready_size = 0; + + local entity p = find(world, classname, "player"); + while (p != world) { + switch(p.team_no) { + case 1: + team_1_size++; + players_size++; + + if (p.stat_flags & PLAYER_READY) { + team_1_ready_size++; + players_ready_size++; + } + + break; + case 2: + team_2_size++; + players_size++; + + if (p.stat_flags & PLAYER_READY) { + team_2_ready_size++; + players_ready_size++; + } + + break; + case 3: + team_3_size++; + players_size++; + + if (p.stat_flags & PLAYER_READY) { + team_3_ready_size++; + players_ready_size++; + } + + break; + case 4: + team_4_size++; + players_size++; + + if (p.stat_flags & PLAYER_READY) { + team_4_ready_size++; + players_ready_size++; + } + + break; + } + + p = find(p, classname, "player"); + } + + local float team_1_ready = (team_1_size && (team_1_size == team_1_ready_size)); + local float team_2_ready = (team_2_size && (team_2_size == team_2_ready_size)); + local float team_3_ready = (team_3_size && (team_3_size == team_3_ready_size)); + local float team_4_ready = (team_4_size && (team_4_size == team_4_ready_size)); + local float teams_ready_size = (team_1_ready + team_2_ready + team_3_ready + team_4_ready); + local float one_player_not_ready = ((players_size > 1) && ((players_size - players_ready_size) == 1)); + + local entity p = find(world, classname, "player"); + local float team_ready; + + while (p != world) { + team_ready = 0; + + // Check if own team is ready + switch(p.team_no) { + case 1: + if (team_1_ready) { + team_ready = 1; + } + break; + case 2: + if (team_2_ready) { + team_ready = 1; + } + break; + case 3: + if (team_3_ready) { + team_ready = 1; + } + break; + case 4: + if (team_4_ready) { + team_ready = 1; + } + break; + } + + // if at least one other team is ready + if ((teams_ready_size - team_ready) > 0) { + p.stat_flags |= ENEMY_TEAM_READY; + } else { + p.stat_flags &= ~ENEMY_TEAM_READY; + } + + // if you are the last to ready up + if (!(p.stat_flags & PLAYER_READY) && one_player_not_ready) { + p.stat_flags |= LAST_NOT_READY; + } else { + p.stat_flags &= ~LAST_NOT_READY; + } + + p = find(p, classname, "player"); + } +} + +float () CheckAllPlayersReady = { + if (is_countdown) { + return 0; + } + if (infokey (world, "status") != "Standby") { + return 0; + } + if (intermission_running) + return 0; + + local float f1 = TeamFortress_GetNoActivePlayers(); + bprint (PRINT_HIGH, "Total players ready - ",ftos(v_ready),"/",ftos(f1),".\n"); + + if (v_ready == f1 && v_ready > 0) { + if (TeamFortress_TeamGetNoPlayersExcludingAllTime(1) != TeamFortress_TeamGetNoPlayersExcludingAllTime(2)) { + bprint (2, "Teams have uneven number of players, is someone supposed to be all-time attack?\n"); + } else if(cease_fire) { + bprint (2, "All players ready, match will start after ceasefire ends.\n"); + } else { + bprint (2, "All players ready, starting match\n"); + localcmd("localinfo clown 0"); // Disable any clown settings. + StartTimer (); + } + + return 1; + } + + return 0; +} + +void () PlayerReady = { + if (fo_login_required && self.fo_login == string_null) { + sprint(self, PRINT_HIGH, "You need to sign in first. Get your login token at www.fortressone.org\n"); + return; + } + + if (is_countdown) { + sprint(self, 2, "You cannot do this after countdown has started.\n"); + return; + } + if (infokey (world, "status") != "Standby") { + sprint(self, 2, "You cannot do this after the match has started.\n"); + return; + } + if (self.team_no == 0) { + sprint(self, 2, "You must join a team first.\n"); + return; + } + if (self.playerclass == 0) { + sprint(self, 2, "You must choose a class first.\n"); + return; + } + if (intermission_running) { + return; + } + + if (self.classname != "player") { + return; + } + + if (self.stat_flags & PLAYER_READY) { + PlayerNotReady(); + return; + } + + self.stat_flags |= PLAYER_READY; + UpdateReadyStatus(); + v_ready = v_ready + 1; + bprint (3, self.netname, " is ready to start the match\n"); + if(!CheckAllPlayersReady()) + Broadcast_Players_NotReady(); +}; + +void () RemoveVotes = { + if (self.bvote) { + bprint(2, self.netname, " \swithdraws\s his/her vote\n"); + self.bvote = 0; + v_break = (v_break - 1); + } + if (self.stat_flags & PLAYER_READY) { + self.stat_flags &= ~PLAYER_READY; + UpdateReadyStatus(); + bprint(2, self.netname, " is NOT ready anymore\n "); + v_ready = (v_ready - 1); + } +}; + +void () PreMatch_Message = { + self.cnt2 = self.cnt2 + 1; + if (self.cnt2 % 30 == 0) + { + local entity p = find (world, classname, "player"); + while (p != world) { + if (p.netname != "") { + sprint(p, PRINT_HIGH, "Currently in \sprematch\s time\n"); + if (p.stat_flags & PLAYER_READY) + sprint(p, PRINT_HIGH, "You are \sready\s to start the match.\n Type \s/notready\s in the console if you are not ready\n"); + else + sprint(p, PRINT_HIGH, "Type \s/ready\s in the console if you are ready to start the match\n"); + stuffcmd(p, "play buttons/switch04.wav\n"); + if(infokeyf(p, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(p, FALSE); + } + } + p = find(p, classname, "player"); + } + } + CenterPrint_Players_NotReady(); + self.nextthink = time + 1; +} diff --git a/client.qc b/ssqc/client.qc similarity index 51% rename from client.qc rename to ssqc/client.qc index 9aa6d42df..67ca82580 100644 --- a/client.qc +++ b/ssqc/client.qc @@ -10,7 +10,8 @@ float modelindex_null; float (entity pe_player) Spy_CheckArea; -void () TeamFortress_CheckTeamCheats; +void (entity player) TeamFortress_CheckTeamCheats; +void () TeamFortress_CheckforCheats; void (entity Viewer, float pc, float rpc) TeamFortress_PrintClassName; void () TeamFortress_RemoveTimers; void (float Suicided) TeamFortress_SetupRespawn; @@ -19,12 +20,12 @@ float (float pc) IsLegalClass; void () SetupTeamEqualiser; void () CeaseFire_think; -void () RemoveGrenadeTimers; void () RemovePrimeTimers; void () RemoveGasTimers; void () RemoveGrenades; void (entity eng) Engineer_RemoveBuildings; +void (entity eng) Engineer_QuietlyRemoveBuildings; void (string halias, float himpulse1, float himpulse2) TeamFortress_Alias; @@ -49,9 +50,29 @@ entity(float ino) Finditem; void (entity Item, entity AP, entity Goal) tfgoalitem_GiveToPlayer; void () CTF_FlagCheck; -void (entity pl) Sniper_ZoomReset; - -string nextmap; +void () StartTimer; +void () StopTimer; +void () StartQuadRound; +void (string cl_pwd) Admin_Check; +void () Admin_Aliases; +void () PreMatch_Message; +void () UpdateReadyStatus; +float () CheckAllPlayersReady; +string(float)TeamFortress_TeamGetColorString; +void (float tno, float scoretoadd) TeamFortress_TeamIncreaseScore; +float (entity pe, float tno, float skipclasscheck) TeamFortress_TeamSet_Options; +float (float tno) TeamFortress_TeamGetLives; +void () InitReverseCap; +float () RejoinWithTfId; +void () CreateTfIdAndJoin; +entity (entity e)TeamFortress_GetPracticeSpawn; +void () NextLevel; + +void StopAssCan(); +void Predict_InitPlayer(entity player); +void Predict_Destroy(entity player); + +void SetSkinInfoFor(entity pov, entity target); void () info_intermission = { @@ -61,16 +82,22 @@ void () info_intermission = } }; +void () info_player_intermission = +{ + self.classname = "info_intermission"; + if (CheckExistence() == 0) { + dremove(self); + return; + } +}; + void () SetChangeParms = { - if (self.health <= 0) { + if (self.health <= 0 && !cb_keepteams) { SetNewParms(); return; } - self.items = - self.items - - (self. - items & (IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY - | IT_SUIT | IT_QUAD)); + self.items &= ~(IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY | + IT_SUIT | IT_QUAD); if (self.health > 100) self.health = 100; if (self.health < 50) @@ -88,12 +115,12 @@ void () SetChangeParms = { parm5 = self.ammo_nails; parm6 = self.ammo_rockets; parm7 = self.ammo_cells; - parm8 = self.current_weapon; + parm8 = SlotIndex(self.current_slot) + 1; parm9 = self.armortype * 100; parm10 = 0; parm11 = 0; - parm12 = 0; - parm13 = 0; + parm12 = cb_keepteams?self.playerclass:0; + parm13 = self.team_no; parm14 = 0; parm15 = self.is_admin; }; @@ -126,38 +153,178 @@ void () autokick_think = { dremove(self); }; -float (string ps_short, string ps_setting, string ps_default) CF_GetSetting = { +string (entity ent, string ps_short, string ps_setting, string ps_default) FO_GetUserSettingString = { local string st; - st = infokey(world, ps_short); + st = infokey(ent, ps_short); if (st == string_null) { - st = infokey(world, ps_setting); + st = infokey(ent, ps_setting); + if (st == string_null) { + st = ps_default; + } } - if (st == "on") { - return TRUE; - } else if (st == "off") { - return FALSE; - } else if (stof(st) != 0) { - return stof(st); - } else if (ps_default == "on") { - return TRUE; - } else if (ps_default == "off") { - return FALSE; - } else if (stof(ps_default) != 0) { + return st; +}; + +string FO_GetUserSettingRaw(entity ent, string ps_short, string ps_setting, + string ps_default) { + string st = infokey(ent, ps_short); + if (st == string_null) { + st = infokey(ent, ps_setting); + if (st == string_null) { + st = ps_default; + } + } + return st; +} + +//if ent is world, return serverinfo, if ent is a player, return that client's setinfo key +float FO_GetUserSetting(entity ent, string ps_short, string ps_setting, + string ps_default) { + string st = FO_GetUserSettingRaw(ent, ps_short, ps_setting, ps_default); + + switch (st) + { + case "on": + return TRUE; + case "off": + return FALSE; + default: + return stof(st); + } +}; + +string CF_GetSettingRaw(string ps_short, string ps_setting, + string ps_default) { + return FO_GetUserSettingRaw(world, ps_short, ps_setting, ps_default); +}; + +float CF_GetSetting(string ps_short, string ps_setting, string ps_default) { + return FO_GetUserSetting(world, ps_short, ps_setting, ps_default); +}; + +float FO_GetGrenadeSetting(string ps_short, string ps_setting, string ps_default) { + local float max_gren_setting = CF_GetSetting(ps_short, ps_setting, ps_default); + + if (max_gren_setting == -1) return stof(ps_default); + + return max_gren_setting; +}; + +void InitPrematch() +{ + localcmd ("serverinfo status Standby\n"); + + clan_scores_dumped = 0; + game_locked = 0; + cb_prematch = 1; + local entity pm_message = spawn(); + pm_message.owner = world; + pm_message.classname = "pmmessage"; + pm_message.think = PreMatch_Message; + pm_message.nextthink = 1; + + game_locked = CF_GetSetting("lg", "locked_game", "off"); + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(self, !cb_prematch); + UpdateClientMOTD(self); } +} - return 0; +typedef struct { + int player_class; + string short_max_gren1; + string setting_max_gren1; + float default_max_gren1; + string short_max_gren2; + string setting_max_gren2; + float default_max_gren2; +} GrenConfig; + +GrenConfig GrenConfigs[10] = { + {0, "", "", 0, "", "", 0}, + {1, "mg1_1", "max_gren1_scout", PC_SCOUT_GRENADE_MAX_1, "mg2_1", "max_gren2_scout", PC_SCOUT_GRENADE_MAX_2}, + {2, "mg1_2", "max_gren1_sniper", PC_SNIPER_GRENADE_MAX_1, "mg2_2", "max_gren2_sniper", PC_SNIPER_GRENADE_MAX_2}, + {3, "mg1_3", "max_gren1_soldier", PC_SOLDIER_GRENADE_MAX_1, "mg2_3", "max_gren2_soldier", PC_SOLDIER_GRENADE_MAX_2}, + {4, "mg1_4", "max_gren1_demoman", PC_DEMOMAN_GRENADE_MAX_1, "mg2_4", "max_gren2_demoman", PC_DEMOMAN_GRENADE_MAX_2}, + {5, "mg1_5", "max_gren1_medic", PC_MEDIC_GRENADE_MAX_1, "mg2_5", "max_gren2_medic", PC_MEDIC_GRENADE_MAX_2}, + {6, "mg1_6", "max_gren1_hwguy", PC_HVYWEAP_GRENADE_MAX_1, "mg2_6", "max_gren2_hwguy", PC_HVYWEAP_GRENADE_MAX_2}, + {7, "mg1_7", "max_gren1_pyro", PC_PYRO_GRENADE_MAX_1, "mg2_7", "max_gren2_pyro", PC_PYRO_GRENADE_MAX_2}, + {8, "mg1_8", "max_gren1_spy", PC_SPY_GRENADE_MAX_1, "mg2_8", "max_gren2_spy", PC_SPY_GRENADE_MAX_2}, + {9, "mg1_9", "max_gren1_engineer", PC_ENGINEER_GRENADE_MAX_1, "mg2_9", "max_gren2_engineer", PC_ENGINEER_GRENADE_MAX_2} }; +static void SetAllRoles(float pc, float gren, float limit) { + if (pc < 1 || pc > 9) return; + + GrenConfig config = GrenConfigs[pc]; + + if (gren == 1) { + limit = FO_GetGrenadeSetting(config.short_max_gren1, + config.setting_max_gren1, ftos(limit)); + + Role_None.gren1_limits[pc] = Role_Attack.gren1_limits[pc] = + Role_Defence.gren1_limits[pc] = limit; + } else if (gren == 2) { + limit = FO_GetGrenadeSetting(config.short_max_gren2, + config.setting_max_gren2, ftos(limit)); + + Role_None.gren2_limits[pc] = Role_Attack.gren2_limits[pc] = + Role_Defence.gren2_limits[pc] = limit; + } +} + +static float NB_UseMinPing() { + if (ServerIsOCE()) + return FALSE; + + if (ServerRegion() == kRegionUnknown) + return FALSE; // e.g. random server, including LAN + return TRUE; +} + +void ActivateOrgGame() { + disable_resup_gren = 2; // No gren2 pickups + + int i; + for (i = 1; i <= 9; i++) // 3 gren spawn + SetAllRoles(i, 1, 3); + + SetAllRoles(4, 2, 1); // 1 mirv for demoman + SetAllRoles(6, 2, 1); // 1 mirv for hwguy + SetAllRoles(9, 2, 1); // 1 emp for engineer + SetAllRoles(9, 1, 4); // 4 gren1 for engineer + SetAllRoles(3, 1, 4); // 4 gren1 for soldier + + PC_PYRO_AIRBLAST_COOLDOWN = 10; + PC_ENGINEER_GRENADE_TYPE_2_RANGE = 200; + + float use_new_cap = CF_GetSetting("nbcc", "new_balance_conc_cap", "0"); + if (use_new_cap == 4) + use_new_cap = 1; + else + use_new_cap = 0; + + if (use_new_cap) + fo_config.new_balance_flags |= NBF_CONC_NEW_CAP; + + if (CF_GetSetting("nbnc", "new_balance_no_cap", "0")) + fo_config.new_balance_flags |= NBF_NO_CAP; + + if (NB_UseMinPing()) { + if (fo_config.min_ping_ms < 50) // 50 min-ping + localcmd("localinfo mpm 50"); + } +} + void () DecodeLevelParms = { local float fl; local string st; local entity ent; local entity te; - local float ti; if (serverflags) if (world.model == "maps/start.bsp") @@ -173,7 +340,6 @@ void () DecodeLevelParms = { self.armortype = parm9 * 0.01; if (!(toggleflags & TFLAG_FIRSTENTRY)) { - toggleflags = parm10; flagem_checked = 0; invis_only = 0; @@ -181,8 +347,10 @@ void () DecodeLevelParms = { if (coop || !deathmatch) toggleflags = toggleflags | TFLAG_CLASS_PERSIST; - nextmap = strzone(mapname); - + if(nextmap == "") { + nextmap = strzone(mapname); + } + ent = find(world, classname, "info_tfdetect"); if (ent != world) { @@ -241,6 +409,7 @@ void () DecodeLevelParms = { autokick_time = 0; cease_fire = 0; + cs_paused = 0; toggleflags = toggleflags - (toggleflags & TFLAG_TEAMFRAGS); toggleflags = toggleflags - (toggleflags & TFLAG_CHEATCHECK); @@ -252,70 +421,31 @@ void () DecodeLevelParms = { autoteam_time = 30; clanbattle = CF_GetSetting("c", "clan", "off"); - if (clanbattle) { - - clan_scores_dumped = 0; - game_locked = 0; - - cb_prematch_time = CF_GetSetting("pm", "prematch", "0"); - cb_prematch_time = time + cb_prematch_time * 60; - - if (timelimit && ((time + timelimit) < cb_prematch_time)) { - - timelimit = timelimit + cb_prematch_time; - ti = ceil(timelimit / 60); - st = ftos(ti); - cvar_set("timelimit", st); - - } - if (cb_prematch_time > time) { - - cb_prematch_time = cb_prematch_time + 5; - ent = spawn(); - ent.think = PreMatch_Think; - ent.nextthink = time + 5; - - } - - cb_ceasefire_time = CF_GetSetting("cft", "ceasefire_time", "0"); - if (cb_ceasefire_time != 0) { - - cb_ceasefire_time = time + cb_ceasefire_time * 60; - if (cb_prematch_time < cb_ceasefire_time) { + quadmode = CF_GetSetting("quadmode", "quadmode", "off"); + duelmode = CF_GetSetting("duelmode", "duelmode", "off"); - cb_prematch_time = cb_ceasefire_time + 5; - if (timelimit && - ((time + timelimit) < cb_prematch_time)) { + disable_voting = clanbattle || quadmode; + org_game = clanbattle || quadmode; - timelimit = timelimit + cb_ceasefire_time; - ti = ceil(timelimit / 60); - st = ftos(ti); - cvar_set("timelimit", st); + rounds = CF_GetSetting("rounds","rounds","on"); + if (!rounds) + rounds = -1; + else if (rounds > 0) + rounds = rounds + 1; - } - - } - cease_fire = 1; - bprint(PRINT_HIGH, "Cease fire\n"); - - te = find(world, classname, "player"); - while (te) { - centerprint(te, "Cease fire\n"); - te.immune_to_check = time + 10; - te.tfstate = te.tfstate | TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(te); - te = find(te, classname, "player"); - } - te = spawn(); - te.classname = "ceasefire"; - te.think = CeaseFire_think; - te.nextthink = time + 5; - - } - game_locked = CF_GetSetting("lg", "locked_game", "off"); - - } else + if (clanbattle) { + InitPrematch(); + } else { clanbattle = FALSE; + localcmd ("serverinfo status Normal\n"); + } + if (rounds) { + st = infokey (world, "round_time"); + te = spawn (); + te.owner = world; + te.classname = "round"; + te.cnt = stof(st); + } // automatically assign team [off] fl = CF_GetSetting("a", "autoteam", "off"); @@ -345,10 +475,57 @@ void () DecodeLevelParms = { } else { toggleflags = toggleflags - (toggleflags & TFLAG_FULLTEAMSCORE); } + + // climbing spikes + spurs_scout = CF_GetSetting("spscout", "spurs_scout" , "1"); // scouts allowed to spawn spurs + spurs_spy = CF_GetSetting("spspy", "spurs_spy" , "1"); // spy allowed to spawn spurs + spurs_engineer = CF_GetSetting("speng", "spurs_engineer" , "1"); // engineer allowed to spawn spurs + spurs_duration = CF_GetSetting("spd", "spurs_duration" , "2"); // how long until self destroy + spurs_boost = CF_GetSetting("spb", "spurs_boost" , "300"); // height gained from touch + spurs_enabled = CF_GetSetting("spe", "spurs_enabled" , "0"); // 0 = OFF , 1 = useable by owner , 2 = useable by owners team, 3 = useable by both teams + spurs_consume = CF_GetSetting("spc", "spurs_consume" , "0"); // destroy on touch + spurs_flag = CF_GetSetting("spf", "spurs_flag" , "0"); // can climb with flag + + + // nail gren types 0 = NGR_TYPE_NAIL, 1 = NGR_TYPE_LASER, 2 = NGR_TYPE_BURST + nailgren_type = CF_GetSetting("ngt", "nailgren_type", ftos(NGR_TYPE_LASER)); + + // medic types 0 = MEDIC_TYPE_DEFAULT, 1 = MEDIC_TYPE_BLAST + medic_type = CF_GetSetting("mcgt", "medic_type", ftos(MEDIC_TYPE_DEFAULT)); + + // how many rotations the nailgren does [2] + lasergren_rotationcount = CF_GetSetting("lgrc", "lasergren_rotationcount", ftos(NGR_LASER_DEFAULT_ROTATIONCOUNT)); + + // time each rotation takes [1] + lasergren_rotationtime = CF_GetSetting("lgrt", "lasergren_rotationtime", ftos(NGR_LASER_DEFAULT_ROTATIONTIME)); + + // damage [20] + lasergren_damage = CF_GetSetting("lgd", "lasergren_damage", ftos(NGR_LASER_DEFAULT_DAMAGE)); + + // think time for lightning, lower is smoother [0.1] + lasergren_thinktime = CF_GetSetting("lgtt", "lasergren_thinktime", "0.01"); //ftos cuts off after 1 dec place, using ftos here would result in 0 and crash? + + // range of shaft [150] + lasergren_range = CF_GetSetting("lgr", "lasergren_range", ftos(NGR_LASER_DEFAULT_RANGE)); + + // how many bursts of nails [2] + burstgren_count = CF_GetSetting("bgc", "burstgren_count", ftos(NGR_BURST_DEFAULT_COUNT)); + + // time between each burst [0.7] + burstgren_interval = CF_GetSetting("bgi", "burstgren_interval", ftos(NGR_BURST_DEFAULT_INTERVAL)); + + // time before nail projectile destroys itself [0.3] + burstgren_range = CF_GetSetting("bgr", "burstgren_range", ftos(NGR_BURST_DEFAULT_RANGE)); + + // multiplier to blast gren velocity + blastgren_velocity_multiplier = CF_GetSetting("bvm", "blastgren_velocity_multiplier", ftos(BLASTGREN_DEFAULT_VELOCITY_MULTIPLIER)); // use old concussion, gas and flash grenades [off] old_grens = CF_GetSetting("og", "old_grens", "off"); + // use fortress one flash + fo_flash = CF_GetSetting("ff", "fo_flash", "on"); + // drop primed hand grenades to ground when dying [on] drop_grenades = CF_GetSetting("dg", "drop_grenades", "on"); @@ -361,9 +538,6 @@ void () DecodeLevelParms = { // maximum grenades type 2 to drop in backpack [0] drop_gren2 = CF_GetSetting("dg2", "drop_gren2", "0"); - // display grenade timer in status bar [on] - grentimers = CF_GetSetting("gt", "grentimers", "on"); - // show sentry gun health + misc extras in id string [on] id_extended = CF_GetSetting("ie", "id_extended", "on"); @@ -376,45 +550,36 @@ void () DecodeLevelParms = { // use old tf style dropflag (map decides on/off) [off] old_dropflag = CF_GetSetting("odf", "old_dropflag", "off"); - // show ticking clip ammo in sbar when reloading [on] - reload_cliptick = CF_GetSetting("rc", "reload_cliptick", "on"); - // use old tf sniper range (shorter) [off] old_sniperrange = CF_GetSetting("os", "old_sniperrange", "off"); // maximum amount of pipebombs per demoman [6] - detpipe_limit = CF_GetSetting("dl", "detpipe_limit", "6"); + Role_None.detpipe_limit = CF_GetSetting("dl", "detpipe_limit", "6"); // maximum amount of pipebombs in game [unlimited] detpipe_limit_world = CF_GetSetting("dw", "detpipe_limit_world", "-1"); - // use old tf pipebomb cooldown (0.8 seconds) [off] - old_pipecooldown = CF_GetSetting("op", "old_pipecooldown", "off"); + // set pipe cooldown time [0.5] + pipecooldown_time = CF_GetSetting("pcdt", "pipecooldown_time", "0.5"); + + // cooldown applies to all pipes [on] + allpipes_cooldown = CF_GetSetting("apcd", "allpipes_cooldown", "off"); // allow medic aura [on] medicaura = CF_GetSetting("ma", "medicaura", "on"); - // use old tf style medikit (don't insta-heal max+50) [off] - old_medikit = CF_GetSetting("om", "old_medikit", "off"); - // use old tf style bioweapon (less damage) [off] old_biodamage = CF_GetSetting("ob", "old_biodamage", "off"); // use old tf style spanner (non-automated) [off] old_spanner = CF_GetSetting("os", "old_spanner", "off"); - // use old tf style railgun (penetrate target) [off] - old_railgun = CF_GetSetting("or", "old_railgun", "off"); - // use old tf style dispenser (non-auto stocking) [off] old_dispenser = CF_GetSetting("od", "old_dispenser", "off"); // enable flamethrower knockback [on] flame_knockback = CF_GetSetting("fk", "flame_knockback", "on"); - // bigger dispenser explosions [on] - disp_explosion = CF_GetSetting("de", "disp_explosion", "on"); - // allow building in water [on] build_water = CF_GetSetting("bw", "build_water", "on"); @@ -433,9 +598,6 @@ void () DecodeLevelParms = { cannon_move = TRUE; // required for this to work } - // make hwguy susceptible to concussion grenade while firing assault cannon [on] - cannon_conc = CF_GetSetting("cc", "cannon_conc", "on"); - // assault cannon accuracy (0 = cf, 1 = tf 2.8, 2 = tf 2.9) [0] cannon_accuracy = CF_GetSetting("cac", "cannon_accuracy", "0"); @@ -448,8 +610,21 @@ void () DecodeLevelParms = { // print fake death message when feigning death [on] feign_msg = CF_GetSetting("fm", "feign_msg", "on"); + // rate limit on feign + feign_rate_limit = CF_GetSetting("frl", "feign_rate_limit", "1"); + // turn off spy [off] spy_off = CF_GetSetting("spy", "spy", "off"); + + // Scout Scanner spike on / off + scoutscanspike = CF_GetSetting("sss", "scoutscanspike", "on"); + + // HwGuy Assault Cannon Range default as 0 to disable, and die time once range hit. + asscanrange = CF_GetSetting("acr", "asscanrange", "0"); + asscanrangedie = CF_GetSetting("acrd", "asscanrangedie", "0"); + + // force reload [on] + force_reload = CF_GetSetting("fr", "forcereload", "on"); // enable spy invisibility [off] invis_only = CF_GetSetting("s", "spyinvis", "off"); @@ -457,34 +632,73 @@ void () DecodeLevelParms = { // allow scout dash [on] scoutdash = CF_GetSetting("sd", "scoutdash", "on"); - // enable sniper rifle reload [on] - sniperreload = CF_GetSetting("sr", "sniperreload", "on"); - // spawn with full ammo/armor [off] spawnfull = CF_GetSetting("sf", "spawnfull", "off"); // stock full ammo/armor [on] stockfull = CF_GetSetting("stf", "stockfull", "on"); + // stock on cap [on] + stock_on_cap = CF_GetSetting("soc", "stock_on_cap", "on"); + + // stock on cap [off] + cap_strip = CF_GetSetting("cs", "cap_strip", "off"); + + // stock clip to full for reloading guns [on] + stock_reload = CF_GetSetting("srd", "stock_reload", "on"); + // display class tips [on] classtips = CF_GetSetting("ct", "classtips", "on"); + // concussion grenade effect time [10] + cussgrentime = CF_GetSetting("cgt", "cussgrentime", "10"); + + // concussion grenade effect time proportional to distance from explosion + distance_based_cuss_duration = CF_GetSetting("dbcd", "distance_based_cuss_duration", "off"); + + // medic immune to concussion grenade effects [on] + medicnocuss = CF_GetSetting("mnc", "medicnocuss", "on"); + + // aim based concussion grenade effect + fo_concuss = CF_GetSetting("foc", "fo_concuss", "0"); + if (fo_concuss & FOC_ON_DEF) + fo_concuss |= foc_defaults; + + // Tie some of these other options together for convenience / other + if (fo_concuss & FOC_DISTANCE) + distance_based_cuss_duration = TRUE; + if (fo_concuss & FOC_AFF_MED) + medicnocuss = FALSE; + // display sniper rifle power in status bar [on] sniperpower = CF_GetSetting("sp", "sniperpower", "on"); - // display sniper rifle reload percentage in status bar [on] - sniperreloadpercent = CF_GetSetting("rp", "sniperreloadpercent", "on"); - // display build status in status bar [on] buildstatus = CF_GetSetting("bs", "buildstatus", "on"); + // use old init/max hp/armor [off] + old_hp_armor = CF_GetSetting("oh", "old_hp_armor", "off"); + + // configure max armor of heavy weapons guy [300] + max_armor_hwguy = CF_GetSetting("mah", "max_armor_hwguy", ftos(PC_HVYWEAP_MAXARMOR)); + + // Scout Scanner spike on / off + scoutscanspike = CF_GetSetting("sss", "scoutscanspike", "on"); + + // HwGuy Assault Cannon Range default as 0 to disable, and die time once range hit. + asscanrange = CF_GetSetting("acr", "asscanrange", "0"); + asscanrangedie = CF_GetSetting("acrd", "asscanrangedie", "0"); + + // CSQC projectiles + fo_projectiles = CF_GetSetting("focp", "fo_csqc_projectiles", "on"); + // delay respawning by this many seconds [0] - respawn_delay_time = CF_GetSetting("rd", "respawn_delay", "0"); - if (respawn_delay_time) { + Role_None.respawn_delay_time = CF_GetSetting("rd", "respawn_delay", "0"); + if (Role_None.respawn_delay_time) { toggleflags = toggleflags | TFLAG_RESPAWNDELAY; } - if ((toggleflags & TFLAG_RESPAWNDELAY) && !respawn_delay_time) { - respawn_delay_time = RESPAWN_DELAY_TIME; + if ((toggleflags & TFLAG_RESPAWNDELAY) && !Role_None.respawn_delay_time) { + Role_None.respawn_delay_time = RESPAWN_DELAY_TIME; } if (toggleflags & TFLAG_AUTOTEAM) { @@ -494,108 +708,440 @@ void () DecodeLevelParms = { ent.think = autoteam_think; } + // fortress one + ng_damage = CF_GetSetting("ngd", "ng_damage", "9"); + sng_damage = CF_GetSetting("sngd", "sng_damage", "13"); + + // allow axe to hit on any frame of animation + superaxe = CF_GetSetting("ss", "superaxe", "on"); + + // allow medikit to hit on any frame of animation + supermedikit = CF_GetSetting("sm", "supermedikit", "on"); + + // allow spanner to hit on any frame of animation + superspanner = CF_GetSetting("ss", "superspanner", "off"); + + // allow knife to hit on any frame of animation + superknife = CF_GetSetting("sk", "superknife", "on"); + + // allow knife to hit on every frame of animation + superknife_multihit = CF_GetSetting("skmh", "superknife_multihit", "off"); + + // impulse command queueing + // ensures detpipe impulse isn't cleared at end of function + impulse_queue = CF_GetSetting("impulsequeue", "impulse_queue", "off"); + + // whitelists detpack impulses on the list of commands that you're allowed to use while reloading [off] + detpack_when_reloading = CF_GetSetting("detreload", "detpack_while_reloading", "off"); + + // ceasefire type - 0: default; 1: pause + ceasefire_type = CF_GetSetting("ceasefiretype", "cft", "1"); + + // enable flag model following the player who has it + flag_follow = CF_GetSetting("flagfollow", "flag_follow", "on"); + + // override map-set class restrictions [off] + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + + Role_None.gren1_limits[0] = 0; + Role_None.gren1_limits[1] = FO_GetGrenadeSetting("mg1_1", "max_gren1_scout", ftos(PC_SCOUT_GRENADE_MAX_1)); + Role_None.gren1_limits[2] = FO_GetGrenadeSetting("mg1_2", "max_gren1_sniper", ftos(PC_SNIPER_GRENADE_MAX_1)); + Role_None.gren1_limits[3] = FO_GetGrenadeSetting("mg1_3", "max_gren1_soldier", ftos(PC_SOLDIER_GRENADE_MAX_1)); + Role_None.gren1_limits[4] = FO_GetGrenadeSetting("mg1_4", "max_gren1_demoman", ftos(PC_DEMOMAN_GRENADE_MAX_1)); + Role_None.gren1_limits[5] = FO_GetGrenadeSetting("mg1_5", "max_gren1_medic", ftos(PC_MEDIC_GRENADE_MAX_1)); + Role_None.gren1_limits[6] = FO_GetGrenadeSetting("mg1_6", "max_gren1_hwguy", ftos(PC_HVYWEAP_GRENADE_MAX_1)); + Role_None.gren1_limits[7] = FO_GetGrenadeSetting("mg1_7", "max_gren1_pyro", ftos(PC_PYRO_GRENADE_MAX_1)); + Role_None.gren1_limits[8] = FO_GetGrenadeSetting("mg1_8", "max_gren1_spy", ftos(PC_SPY_GRENADE_MAX_1)); + Role_None.gren1_limits[9] = FO_GetGrenadeSetting("mg1_9", "max_gren1_engineer", ftos(PC_ENGINEER_GRENADE_MAX_1)); + + // Maximum number of secondary grenades per class + Role_None.gren2_limits[1] = FO_GetGrenadeSetting("mg2_1", "max_gren2_scout", ftos(PC_SCOUT_GRENADE_MAX_2)); + Role_None.gren2_limits[2] = FO_GetGrenadeSetting("mg2_2", "max_gren2_sniper", ftos(PC_SNIPER_GRENADE_MAX_2)); + Role_None.gren2_limits[3] = FO_GetGrenadeSetting("mg2_3", "max_gren2_soldier", ftos(PC_SOLDIER_GRENADE_MAX_2)); + Role_None.gren2_limits[4] = FO_GetGrenadeSetting("mg2_4", "max_gren2_demoman", ftos(PC_DEMOMAN_GRENADE_MAX_2)); + Role_None.gren2_limits[5] = FO_GetGrenadeSetting("mg2_5", "max_gren2_medic", ftos(PC_MEDIC_GRENADE_MAX_2)); + Role_None.gren2_limits[6] = FO_GetGrenadeSetting("mg2_6", "max_gren2_hwguy", ftos(PC_HVYWEAP_GRENADE_MAX_2)); + Role_None.gren2_limits[7] = FO_GetGrenadeSetting("mg2_7", "max_gren2_pyro", ftos(PC_PYRO_GRENADE_MAX_2)); + Role_None.gren2_limits[8] = FO_GetGrenadeSetting("mg2_8", "max_gren2_spy", ftos(PC_SPY_GRENADE_MAX_2)); + Role_None.gren2_limits[9] = FO_GetGrenadeSetting("mg2_9", "max_gren2_engineer", ftos(PC_ENGINEER_GRENADE_MAX_2)); + + // disable resupply giving grenades (1-bit for gren1, 2-bit for gren2) + disable_resup_gren = CF_GetSetting("drg", "disable_resup_gren", "0"); + + // solid detpack toggle [0] + solid_detpack = CF_GetSetting("sdp", "solid_detpack", "0"); + // All Walls Block EMP [0] + walls_block_emp = CF_GetSetting("wbp", "walls_block_emp", "0"); + + // new emp range and set damage values (so no dropammo to reduce) + new_emp = CF_GetSetting("nemp", "new_emp", "1"); + + fo_sentry_targeting = CF_GetSetting("fosgt", "fo_sentry_targ", "1"); + + // For duelmode, how soon to reset the winning player (allows for double KOs) + duel_reset_delay = CF_GetSetting("drd", "duel_reset_delay", "3.9"); + // remove packs in duelmode [1] + duel_no_packs = CF_GetSetting("dnp", "duel_no_packs", "1"); + // spawn with all possible grenades [0] + duel_all_grens = CF_GetSetting("dag", "duel_all_grens", "0"); + // After a duel round, print the winners' health stats + round_winner_print_health = CF_GetSetting("dph", "duel_print_health", "0"); + // After respawning in duel mode, nobody shoots until + // 0 - immediately + // 1 - all players have moved away from spawn + duel_spawn_guard = CF_GetSetting("dsg", "duel_spawn_guard", "1"); + // If 0, first to reach fraglimit wins + // If 1, a double-ko (with delay) will end the match in a draw + duel_allow_draw = CF_GetSetting("dad", "duel_allow_draw", "1"); + // Sets the number of frags difference required to break a tie after a double-ko upon fraglimit + duel_tie_break = CF_GetSetting("dtb", "duel_tie_break", "2"); + // If 0, in case of a double-ko, reset immediately + // If 1, round countdown always happens, even with a double-ko + duel_draw_countdown = CF_GetSetting("ddc", "duel_draw_countdown", "1"); + // If 1, clients that have 'setinfo dap 1' will prime a gren as soon as they leave spawn + duel_autoprime = CF_GetSetting("dap", "duel_autoprime", "0"); + + //Proportion required to trigger a map change + vote_threshold = CF_GetSetting("vt", "vote_threshold", "0.5"); + if(vote_threshold <= 0 || vote_threshold > 1) { + dprint("localinfo vote_threshold was outside of valid range, defaulting to 0.5\n"); + vote_threshold = 0.5; + } + + //default round interval for quadmode (seconds) + round_delay_time = CF_GetSetting("round_delay_time", "rdt", "30"); + + map_restart_time = CF_GetSetting("map_restart_Time", "mrt", "120"); + + // if 1, quad round will not finish immediately if result is determined [off] + play_to_completion = CF_GetSetting("ptc", "play_to_completion", "0"); + + // enforces login + loginRequired = CF_GetSetting("loginrequired", "logreq", "0"); + + // login request https endpoint + loginUrl = infokey(world, "loginurl"); + + // server webpage address (required for login to work) + webpageUrl = infokey(world, "webpageurl"); + + if (loginRequired && (!loginUrl || !webpageUrl)) { + loginRequired = 0; + } + + // specify demo filehost address e.g. https://fortressone-demos.s3.amazonaws.com/sydney/staging/ + demo_files_address = FO_GetUserSettingString(world, "demo_files_address", "demofileaddy", ""); + + // specify stats filehost address e.g. http://fortressone-stats.s3-ap-southeast-2.amazonaws.com/sydney/staging/ + stats_files_address = FO_GetUserSettingString(world, "stats_files_address", "statsfileaddy", ""); + + // specify discord channel [""] + discord_channel_id = infokey(world, "discord_channel_id"); + + // fortress one flash time in seconds + fo_flashtime = CF_GetSetting("ff", "fo_flashtime", "2"); + + // health repair + fo_repair_ratio = CF_GetSetting("forep", "fo_repair_ratio", "2"); + + // keep teams on map change [off] + cb_keepteams = CF_GetSetting("kt", "keep_teams", "0"); + + // use att/def roles in quad mode [off] + quad_roles = CF_GetSetting("qr", "quad_roles", "0"); + + LoadRole(&Role_Attack); + LoadRole(&Role_Defence); + InitTeamRoles(); + + + // whether the nailgren can be jumped off of + solid_nailgren = CF_GetSetting("sng", "solid_nailgren", "1"); + + // never return the goalitems [off] + noreturn = CF_GetSetting("nr", "noreturn", "0"); + + // disable hit sounds [off] + nohitsounds = CF_GetSetting("nhs", "nohitsounds", "0"); + + //test zutmode for testing stuff [off] + zutmode = CF_GetSetting("zm", "zutmode", "1"); + + // disable hit text [off] + nohittext = CF_GetSetting("nht", "nohittext", "0"); + + // disable setinfo keepcells [off] + nokeepcells = CF_GetSetting("nkc", "nokeepcells", "0"); + + // allow clients to place a personal practice spawn [off] + allowpracspawns = CF_GetSetting("aps", "allowpracspawns", "0"); + + // split backpack.mdl into deathbag.mdl and discard.mdl [on] (visual change only) + splitbackpackmodels = CF_GetSetting("sbm", "splitbackpackmodels", "on"); + + // standardize contents of backpack dropped on a players death [on] + standardizedeathammo = CF_GetSetting("sda", "standardizedeathammo", "on"); + + // if standardizedeathbags set to 1 use these settings instead of players ammo to fill bag on death + deathammo_shells = CF_GetSetting("das", "deathammo_shells", "25"); + deathammo_nails = CF_GetSetting("dan", "deathammo_nails", "25"); + deathammo_cells = CF_GetSetting("dac", "deathammo_cells", "50"); + deathammo_rockets = CF_GetSetting("dar", "deathammo_rockets", "10"); + + // enable server-side flaginfo on statusbar [on] + // server_sbflaginfo = CF_GetSetting("ssbfi", "server_sbflaginfo", "1"); + + reverse_cap = CF_GetSetting("rcap","reverse_cap", "0"); + + if (reverse_cap) + { + InitReverseCap(); + } + + engineer_move = CF_GetSetting("em","engineer_move", "1"); + grenade_lockout = CF_GetSetting("gl","grenade_lockout", "0"); + + max_active_gren2_soldier = CF_GetSetting("mg2s","max_active_gren2_soldier", "3"); + st = infokey(world, "default"); if (st == "on") { + impulse_queue = FALSE; server_default = TRUE; old_grens = FALSE; drop_grenades = TRUE; drop_grenpack = TRUE; drop_gren1 = 1; drop_gren2 = 0; - grentimers = TRUE; id_extended = TRUE; remember_weapon = TRUE; discammo_pickup = FALSE; old_dropflag = FALSE; - reload_cliptick = TRUE; scoutdash = TRUE; - sniperreload = TRUE; old_sniperrange = FALSE; - detpipe_limit = 6; + Role_None.detpipe_limit = 6; detpipe_limit_world = -1; - old_pipecooldown = FALSE; + pipecooldown_time = 0.5; medicaura = TRUE; - old_medikit = FALSE; old_biodamage = FALSE; cannon_lock = TRUE; cannon_air = TRUE; cannon_move = TRUE; cannon_movespin = FALSE; - cannon_conc = TRUE; cannon_accuracy = 0; flame_knockback = TRUE; old_spanner = FALSE; - old_railgun = FALSE; old_dispenser = FALSE; - disp_explosion = TRUE; build_water = TRUE; feign_air = TRUE; feign_pack = TRUE; feign_msg = TRUE; - spawnfull = FALSE; + spawnfull = TRUE; stockfull = TRUE; + stock_on_cap = TRUE; + cap_strip = FALSE; + stock_reload = TRUE; classtips = TRUE; sniperpower = TRUE; - sniperreloadpercent = TRUE; buildstatus = TRUE; + old_hp_armor = FALSE; + superaxe = TRUE; + supermedikit = TRUE; + superspanner = FALSE; + superknife = TRUE; + superknife_multihit = FALSE; + Role_None.gren1_limits[1] = PC_SCOUT_GRENADE_MAX_1; + Role_None.gren1_limits[2] = PC_SNIPER_GRENADE_MAX_1; + Role_None.gren1_limits[3] = PC_SOLDIER_GRENADE_MAX_1; + Role_None.gren1_limits[4] = PC_DEMOMAN_GRENADE_MAX_1; + Role_None.gren1_limits[5] = PC_MEDIC_GRENADE_MAX_1; + Role_None.gren1_limits[6] = PC_HVYWEAP_GRENADE_MAX_1; + Role_None.gren1_limits[7] = PC_PYRO_GRENADE_MAX_1; + Role_None.gren1_limits[8] = PC_SPY_GRENADE_MAX_1; + Role_None.gren1_limits[9] = PC_ENGINEER_GRENADE_MAX_1; + Role_None.gren2_limits[1] = PC_SCOUT_GRENADE_MAX_2; + Role_None.gren2_limits[2] = PC_SNIPER_GRENADE_MAX_2; + Role_None.gren2_limits[3] = PC_SOLDIER_GRENADE_MAX_2; + Role_None.gren2_limits[4] = PC_DEMOMAN_GRENADE_MAX_2; + Role_None.gren2_limits[5] = PC_MEDIC_GRENADE_MAX_2; + Role_None.gren2_limits[6] = PC_HVYWEAP_GRENADE_MAX_2; + Role_None.gren2_limits[7] = PC_PYRO_GRENADE_MAX_2; + Role_None.gren2_limits[8] = PC_SPY_GRENADE_MAX_2; + Role_None.gren2_limits[9] = PC_ENGINEER_GRENADE_MAX_2; + solid_detpack = FALSE; + walls_block_emp = FALSE; + solid_nailgren = FALSE; + scoutscanspike = TRUE; } st = infokey(world, "faithful"); if (st == "on") { + impulse_queue = FALSE; server_faithful = TRUE; drop_grenades = FALSE; drop_grenpack = FALSE; drop_gren1 = 0; drop_gren2 = 0; - grentimers = FALSE; id_extended = FALSE; remember_weapon = FALSE; discammo_pickup = TRUE; old_dropflag = TRUE; - reload_cliptick = FALSE; scoutdash = FALSE; - sniperreload = FALSE; old_sniperrange = TRUE; - detpipe_limit = 7; + Role_None.detpipe_limit = 7; detpipe_limit_world = 7; - old_pipecooldown = TRUE; + pipecooldown_time = 0.8; medicaura = FALSE; - old_medikit = TRUE; old_biodamage = TRUE; cannon_lock = FALSE; cannon_air = TRUE; cannon_move = TRUE; cannon_movespin = TRUE; - cannon_conc = TRUE; cannon_accuracy = 1; flame_knockback = FALSE; old_spanner = TRUE; - old_railgun = TRUE; old_dispenser = TRUE; - disp_explosion = FALSE; build_water = FALSE; feign_air = FALSE; feign_pack = FALSE; feign_msg = FALSE; spawnfull = FALSE; stockfull = FALSE; + stock_on_cap = FALSE; + cap_strip = FALSE; + stock_reload = FALSE; classtips = FALSE; sniperpower = FALSE; - sniperreloadpercent = FALSE; buildstatus = FALSE; - } - + old_hp_armor = TRUE; + superaxe = FALSE; + supermedikit = FALSE; + superspanner = FALSE; + superknife = FALSE; + superknife_multihit = FALSE; + Role_None.gren1_limits[1] = 4; + Role_None.gren1_limits[2] = 4; + Role_None.gren1_limits[3] = 4; + Role_None.gren1_limits[4] = 4; + Role_None.gren1_limits[5] = 4; + Role_None.gren1_limits[6] = 4; + Role_None.gren1_limits[7] = 4; + Role_None.gren1_limits[8] = 4; + Role_None.gren1_limits[9] = 4; + Role_None.gren2_limits[1] = 3; + Role_None.gren2_limits[2] = 4; + Role_None.gren2_limits[3] = 3; + Role_None.gren2_limits[4] = 4; + Role_None.gren2_limits[5] = 3; + Role_None.gren2_limits[6] = 4; + Role_None.gren2_limits[7] = 4; + Role_None.gren2_limits[8] = 4; + Role_None.gren2_limits[9] = 4; + solid_detpack = TRUE; + walls_block_emp = FALSE; + solid_nailgren = TRUE; + scoutscanspike = FALSE; + fo_config.old_ng_rof = TRUE; + } + + st = infokey(world, "huetf"); + if (st == "on") { + server_huetf = TRUE; + impulse_queue = TRUE; + flag_follow = FALSE; + ng_damage = 18; + sng_damage = 26; + superaxe = FALSE; + supermedikit = FALSE; + superspanner = FALSE; + superknife = FALSE; + superknife_multihit = FALSE; + scoutdash = FALSE; + fo_flash = FALSE; + nailgren_type = NGR_TYPE_NAIL; + medic_type = MEDIC_TYPE_DEFAULT; + Role_None.detpipe_limit = 7; + detpipe_limit_world = 7; + medicaura = FALSE; + medicnocuss = FALSE; + distance_based_cuss_duration = FALSE; + drop_grenades = FALSE; + drop_grenpack = FALSE; + drop_gren1 = 0; + drop_gren2 = 0; + cussgrentime = 19; + spawnfull = FALSE; + stockfull = FALSE; + stock_on_cap = FALSE; + cap_strip = FALSE; + stock_reload = FALSE; + classtips = FALSE; + nohitsounds = TRUE; + nohittext = TRUE; //damned luddites + zutmode = FALSE; + detpack_when_reloading = TRUE; + old_hp_armor = TRUE; + //server_sbflaginfo = FALSE; + localcmd("localinfo server_sbflaginfo 0\n"); + solid_detpack = TRUE; + walls_block_emp = FALSE; + Role_None.gren2_limits[1] = 3; + Role_None.gren2_limits[3] = 2; + Role_None.gren2_limits[4] = 4; + Role_None.gren2_limits[5] = 3; + Role_None.gren2_limits[8] = 3; + max_active_gren2_soldier = 1; + round_delay_time = 10; + fo_config.old_ng_rof = TRUE; + } + + // Must be after we've set up conditional options (e.g. hwguy reload). + FO_Weapons_Init(); + InitFppProjectiles(); } if (parm11) self.tfstate = parm11; - if (self.playerclass == 0) - self.playerclass = parm12; + if (!self.team_no && cb_keepteams && clanbattle && parm13 && parm13 > 0 && parm13 <= number_of_teams) { + // 1 = same, 2+ = rotate + if(cb_keepteams == 1) { + //self.team_no = parm13; + TeamFortress_TeamSet(parm13); + } else { + //self.team_no = (parm13 % number_of_teams) + 1; + TeamFortress_TeamSet((parm13 % number_of_teams) + 1); + } + self.lives = TeamFortress_TeamGetLives(parm13); + //Menu_Class(0); + } + if (self.playerclass == 0 && self.team_no) { + TeamFortress_ChangeClass(parm12); + } if (parm15) self.is_admin = parm15; + +/* + local entity p = find(world, classname, "player"); + while (p != world) { + if (p.netname != "") { + if(infokeyf(p, INFOKEY_P_CSQCACTIVE)) { + UpdateClientMOTD(p); + } + } + p = find(p, classname, "player"); + } +*/ + + // Overrides other settings. + new_balance = CF_GetSetting("new_balance", "new_balance", "1"); + // mirror current state into desired state bit + new_balance |= (new_balance << 1); + + if (org_game) // This used to depend on new balance + ActivateOrgGame(); + }; -entity()FindIntermission = +entity() FindIntermission = { local entity spot; local float cyc; @@ -619,6 +1165,7 @@ entity()FindIntermission = } return (spot); } + objerror("FindIntermission: no spot"); return (world); }; @@ -672,7 +1219,7 @@ void () TF_MovePlayer = { }; void () GotoNextMap = { - if (vote_result != string_null) + if (vote_result != string_null && (nextmap == string_null || nextmap == "")) nextmap = strzone(vote_result); if (nextmap != mapname || vote_result != string_null) { @@ -710,7 +1257,8 @@ void () IntermissionThink = { return; if ((!self.button0 && !self.button1) && !self.button2) return; - + if (logfilehandle > 0) + fclose(logfilehandle); dprint("Intermission think\n"); GotoNextMap(); }; @@ -743,8 +1291,6 @@ void () PutClientInIntermission = void () execute_changelevel = { local entity pos; - dprint("execute_changelevel()\n"); - intermission_running = 1; intermission_exittime = time + 5; @@ -761,10 +1307,41 @@ void () execute_changelevel = { WriteAngle(MSG_ALL, pos.mangle_y); WriteAngle(MSG_ALL, pos.mangle_z); + local string nextmapstring = nextmap; + if((vote_result == string_null || vote_result == "") && (nextmap == string_null || nextmap == "")) { + local string vm = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + vote_style = CF_GetSetting("vs", "vote_style", "2"); + if (vote_style || votemode == 2){ + if(votemode == 1 && vote_result && vote_result != vm) { + localcmd ("localinfo votemode off\n"); + //changelevel(vote_result); + } + if((vote_style & 1 && (vote_result == vm || vote_result == "")) || votemode == 2) { + vote_result = vm; + if(!(vote_style & 4)) { + localcmd ("localinfo votemode on\n"); + } + //changelevel(FO_GetUserSettingString(world, "vote_map", "votemap", "se2")); + } + if(vote_style & 2 && (vote_result == vm || vote_result == "")) { + vote_result = mapname; + } + } + //if(!votemode) { + // vote_result = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + // nextmapstring = strcat(vote_result, " \b(voting)\b"); + //} + } + if(vote_result != string_null && vote_result != "" && vote_result != mapname) { + nextmapstring = vote_result; + nextmap = vote_result; + } + + other = find(world, classname, "player"); while (other != world) { - if (vote_result != string_null) - sprint(other, PRINT_HIGH, "Next up: ", vote_result, "\n"); + if (nextmapstring != string_null && nextmapstring != "") + sprint(other, PRINT_HIGH, "Next up: ", nextmapstring, "\n"); other.takedamage = DAMAGE_NO; other.solid = SOLID_NOT; other.movetype = MOVETYPE_NONE; @@ -774,6 +1351,7 @@ void () execute_changelevel = { } if (!clan_scores_dumped) { DumpClanScores(); + MapEndSequence(); clan_scores_dumped = 1; } }; @@ -819,13 +1397,16 @@ void () respawn = { return; if (cease_fire) return; + if (votemode && vote_anarchy_mode) { + return; + } if (coop) { - CopyToBodyQue(self); + AddToBodyQueue(self); setspawnparms(self); PutClientInServer(); } else if (deathmatch) { - CopyToBodyQue(self); + AddToBodyQueue(self); SetNewParms(); PutClientInServer(); } else @@ -836,57 +1417,327 @@ float () CloseToSpawnPoint = { local entity spot = nil; local entity te = nil; local float i; + string searchstr; + searchstr = ""; + + switch (self.team_no) + { + case 1: + searchstr = "ts1"; + break; + case 2: + searchstr = "ts2"; + break; + case 3: + searchstr = "ts3"; + break; + case 4: + searchstr = "ts4"; + break; + default: + return 0; + } + + spot = find(world, team_str_home, searchstr); + for(i = 1; i < 30; i++) { + te = findradius(spot.origin, 185); + while (te != world) { + if (te == self) + return 1; + te = te.chain; + } + spot = find(spot, team_str_home, searchstr); + } - if (self.team_no == 1) { - spot = find(world, team_str_home, "ts1"); - for(i = 1; i < 30; i++) { - te = findradius(spot.origin, 85); - while (te != world) { - if (te == self) - return 1; - te = te.chain; + return 0; +}; + +void () RemovePlayerOwnedEnts = { + local entity te; + + te = find(world, classname, "pipebomb"); + while (te) { + if (te.owner == self) { + dremove(te); + } + te = find(te, classname, "pipebomb"); + } + TeamFortress_RemoveTimers(); + RemoveGasTimers(); + Engineer_QuietlyRemoveBuildings(self); + TeamFortress_RemovePracticeSpawn(self); + te = find(world, classname, "detpack"); + while (te) { + if (te.owner == self) { + if (te.is_disarming == 1) { + TeamFortress_SetSpeed(te.enemy); + dremove(te.oldenemy); + dremove(te.observer_list); } - spot = find(spot, team_str_home, "ts1"); + dremove(te); + te = world; } - } else if (self.team_no == 2) { - spot = find(world, team_str_home, "ts2"); - for(i = 1; i < 30; i++) { - te = findradius(spot.origin, 85); - while (te != world) { - if (te == self) - return 1; - te = te.chain; + te = find(te, classname, "detpack"); + } + te = find(world, classname, "countdown_timer"); + while (te) { + if (te.owner == self) { + if (te.is_disarming == 1) { + TeamFortress_SetSpeed(te.enemy); + dremove(te.oldenemy); + dremove(te.observer_list); } - spot = find(spot, team_str_home, "ts2"); + dremove(te); + te = world; } - } else if (self.team_no == 3) { - spot = find(world, team_str_home, "ts3"); - for(i = 1; i < 30; i++) { - te = findradius(spot.origin, 85); - while (te != world) { - if (te == self) - return 1; - te = te.chain; + te = find(te, classname, "countdown_timer"); + } + RemoveGrenades(); + if (clanbattle && (self.tf_id != 0)) { + te = spawn(); + te.classname = "ghost"; + te.tf_id = self.tf_id; + te.team_no = self.team_no; + te.frags = self.frags; + te.real_frags = self.real_frags; + te.netname = self.netname; + te.playerclass = self.playerclass; + if (self.tfstate & TFSTATE_RANDOMPC) { + te.tfstate = TFSTATE_RANDOMPC; + } + } + RemoveDeadBody(self); +} + +void (string cn) RemoveAllEntsByClassname = { + local entity te = find(world, classname, cn); + while (te) { + dremove(te); + te = find(te, classname, cn); + } +} + +void () spawn_guard_think = { + local entity te, oldself; + local float no_fire = 0; + te = find(world, classname, "player"); + while (te != world) { + if(!te.has_disconnected) { + if(te.duel_guarded != world) { + if(vlen(te.duel_guarded.origin - te.origin) > 200) { + te.duel_guarded = world; + te.invincible_finished = 0; + te.invincible_time = 0; + te.effects = 0; + bprint(PRINT_HIGH, te.netname, " \bhas left the spawn!\b\n"); + FO_Sound(world, CHAN_AUTO, "misc/basekey.wav", 1, ATTN_NONE); + } else { + no_fire++; + } } - spot = find(spot, team_str_home, "ts3"); } - } else if (self.team_no == 4) { - spot = find(world, team_str_home, "ts4"); - for(i = 1; i < 30; i++) { - te = findradius(spot.origin, 85); + te = find (te, classname, "player"); + } + self.nextthink = time + 0.1; + if(!no_fire) { + no_fire_mode = 0; + bprint(PRINT_HIGH, "\bFIGHT!\b\n"); + FO_Sound(world, CHAN_AUTO, "fight.wav", 1, ATTN_NONE); + self.think = SUB_Remove; + if(duel_autoprime) { + te = find(world, classname, "player"); while (te != world) { - if (te == self) - return 1; - te = te.chain; + if(!te.has_disconnected && infokey(te, "dap") == "1") { + oldself = self; + self = te; + TeamFortress_PrimeGrenade(1, FALSE); + self = oldself; + } + te = find (te, classname, "player"); } - spot = find(spot, team_str_home, "ts4"); } } +}; - return 0; +void () StartSpawnGuard = { + no_fire_mode = 1; + entity reset_timer = spawn(); + reset_timer.classname = "spawn_guard_check"; + reset_timer.think = spawn_guard_think; + reset_timer.nextthink = time + 0.1; + bprint(PRINT_HIGH, "\bLeave the spawn to begin!\b\n"); + FO_Sound(world, CHAN_AUTO, "come_get.wav", 1, ATTN_NONE); +}; + +void () DuelFinish = { + FO_Sound(world, CHAN_AUTO, "boss1/sight1.wav", 1, ATTN_NONE); + NextLevel(); + RemovePrimeTimers(); + RemoveGrenades(); }; -void (float force, float free) ClientKill = { +void (entity winner) DuelWin = { + bprint(PRINT_HIGH, "\sDuel over!\s ", winner.netname, " \sis victorious!\s\n"); + DuelFinish(); +}; + + +void () ResetPlayers = { + local entity te, oldself, reset_timer, winner = world; + local float teamsleft; + local float maxscore = 0, nextscore = 0; + no_fire_mode = 0; + + reset_timer = find(world, classname, "duel_reset_timer"); + while (reset_timer != world) { + oldself = reset_timer; + reset_timer = find (reset_timer, classname, "duel_reset_timer"); + dremove(oldself); + } + if(cb_prematch) + return; + + TeamFortress_TeamShowScores(2); + + teamsleft = CountRemainingTeams(); + if(teamsleft == 1) { + if(round_winner) { + bprint(PRINT_HIGH, round_winner.netname, " of team ", TeamFortress_TeamGetColorString(round_winner.team_no) ,", wins the round!\n"); + if(round_winner_print_health && round_winner.health == round_winner.max_health && round_winner.armorvalue == round_winner.maxarmor ) { + bprint(PRINT_HIGH, "\t\t\t \sFLAWLESS VICTORY\s \t\t\t\n"); + } + } else { + bprint(PRINT_HIGH, TeamFortress_TeamGetColorString(round_winner_team), " team wins the round!\n"); + } + TeamFortress_TeamIncreaseScore(round_winner_team, 1); + } else if(teamsleft == 0) { + bprint(PRINT_HIGH, "Everybody died! It's a draw.\n"); + } + + RemoveAllEntsByClassname("proj_bullet"); + RemoveAllEntsByClassname("proj_rocket"); + RemoveAllEntsByClassname("grenade"); + RemoveAllEntsByClassname("pipebomb"); + RemoveAllEntsByClassname("spike"); + RemoveAllEntsByClassname("railslug"); + RemoveAllEntsByClassname("pyro_flame"); + RemoveAllEntsByClassname("fire"); + RemoveAllEntsByClassname("flamerflame"); + RemoveAllEntsByClassname("pyro_rocket"); + RemoveAllEntsByClassname("proj_tranq"); + + te = find(world, classname, "player"); + while (te != world) { + if(!te.has_disconnected) { + if(te.health > 0) { + stuffcmd(te, "f_respawn\n"); + if(round_winner_print_health) { + bprint(PRINT_HIGH, te.netname, ": \sHealth:\s ", ftos(te.health), " \sArmour:\s " , ftos(te.armorvalue), "\n"); + } + } + oldself = self; + self = te; + if(duelmode && duel_allow_draw && fraglimit) { + //if(duel_tie_break > 0) { + if(maxscore <= te.frags) { + nextscore = maxscore; + maxscore = te.frags; + winner = te; + } + //} else if (te.frags >= fraglimit) { + // DuelWin(te); + // return; + //} + } + RemovePlayerOwnedEnts(); + setspawnparms(self); + PutClientInServer(); + self.respawn_time = 0; + self = oldself; + } + te = find (te, classname, "player"); + } + if(duelmode && duel_allow_draw) { + if(fraglimit) { + if(duel_tie_break > 0) { + if(maxscore == fraglimit && maxscore == nextscore) { + bprint(PRINT_HIGH, "\sFraglimit reached, but the fight's not over!\s\n\sYou must reach\s ", ftos(duel_tie_break), " \sfrag difference to win!\s\n"); + FO_Sound(world, CHAN_AUTO, "items/suit.wav", 1, ATTN_NONE); + } + if((maxscore >= fraglimit && nextscore < fraglimit) || (maxscore > fraglimit && maxscore >= (nextscore + duel_tie_break) )) { + DuelWin(winner); + return; + } + } else { + if(maxscore >= fraglimit) { + if(maxscore == nextscore) { + bprint(PRINT_HIGH, "\sDuel over! It is a\s DRAW\s!\s\n"); + DuelFinish(); + } else { + DuelWin(winner); + } + return; + } + } + } + } + + if(duelmode && duel_spawn_guard) { + StartSpawnGuard(); + precache_sound("get_some.wav"); + precache_sound("fight.wav"); + } + +} + +float () CountRemainingTeams = { + local float teamsleft, teamflags, playersleft; + local entity te, lastplayer; + + lastplayer = world; + teamflags = 0; + playersleft = 0; + + te = find(world, classname, "player"); + while (te != world) { + if(!te.has_disconnected && te.health > 0) { + teamflags = teamflags | pow(2, te.team_no); + lastplayer = te; + playersleft++; + } + te = find (te, classname, "player"); + } + teamsleft = 0; + if(teamflags & 2) { + teamsleft++; + } + if(teamflags & 4) { + teamsleft++; + } + if(teamflags & 8) { + teamsleft++; + } + if(teamflags & 16) { + teamsleft++; + } + round_winner_team = 0; + round_winner = world; + if(teamsleft == 1) { + if(playersleft == 1) { + //bprint(PRINT_HIGH, lastplayer.netname, " of team ", TeamFortress_TeamGetColorString(lastplayer.team_no) ,", wins the round!\n"); + round_winner = lastplayer; + round_winner_team = lastplayer.team_no; + } else { + //bprint(PRINT_HIGH, TeamFortress_TeamGetColorString(lastplayer.team_no), " team wins the round!\n"); + round_winner_team = lastplayer.team_no; + } + //} else if(teamsleft == 0) { + //bprint(PRINT_HIGH, "Everybody died! It's a draw.\n"); + } + return teamsleft; +} + +void () ClientKill = { local entity te; local float timeleft; @@ -894,29 +1745,32 @@ void (float force, float free) ClientKill = { return; if (self.deadflag) return; - if (self.playerclass == PC_UNDEFINED) - return; + if(!votemode) { + if (self.playerclass == PC_UNDEFINED) + return; - if (self.suicide_time > time && !force) { - timeleft = self.suicide_time - time; - sprint(self, PRINT_HIGH, "You have to wait ", ftos(ceil(timeleft)), " more seconds to suicide\n"); - return; + if (self.suicide_time > time && self.clientkillforce == 0) { + timeleft = self.suicide_time - time; + sprint(self, PRINT_HIGH, "You have to wait ", ftos(ceil(timeleft)), " more seconds to suicide\n"); + return; + } } - Sniper_ZoomReset(self); - set_suicide_frame(); self.modelindex = modelindex_player; - self.weaponmodel = ""; + self.tfstate |= TFSTATE_NO_WEAPON; self.view_ofs = '0 0 -8'; self.movetype = MOVETYPE_NONE; TeamFortress_RemoveTimers(); // players can't suicide again for 10 seconds - self.suicide_time = time + 5 + random() * 5; + if(!allowpracspawns) + self.suicide_time = time + 5 + random() * 5; + else + self.suicide_time = time; - if (free && !self.has_throwngren && (self.has_changedteam || self.has_changedclass)) { + if (self.clientkillfree == 1 && !self.has_throwngren && (self.has_changedteam || self.has_changedclass)) { TeamFortress_SetupRespawn(FALSE); } else { bprint(PRINT_MEDIUM, self.netname, " suicides\n"); @@ -939,10 +1793,20 @@ void (float force, float free) ClientKill = { self.th_die(); } + self.clientkillforce = 0; + self.clientkillfree = 0; self.health = -1; self.deadflag = DEAD_RESPAWNABLE; self.tfstate = self.tfstate | TFSTATE_RESPAWN_READY; self.takedamage = 0; + if(duelmode && !cb_prematch) { + if(CountRemainingTeams() < 2) { + ResetPlayersWithCountdown(); + } + } + if(votemode && vote_anarchy_mode) { + CheckVoting(); + } }; entity lastspawn_team1; @@ -1055,15 +1919,30 @@ entity(float team_num) FindTeamSpawnPoint = void (entity e) ValidateUser = { }; -entity () SelectSpawnPoint = -{ +entity () SelectSpawnPoint = { + if (allowpracspawns && self != world) + { + local entity spot2; + spot2 = TeamFortress_GetPracticeSpawn(self); + + if(spot2 != world) + return spot2; + } + local entity spot; local float attempts; + if (self.spawn_at_last_spawn_spot == 1) { + self.spawn_at_last_spawn_spot = 0; + return(self.last_spawn_spot); + } + if (self.team_no != 0) { spot = FindTeamSpawnPoint(self.team_no); - if (spot != world) + if (spot != world) { + self.last_spawn_spot = spot; return (spot); + } } if (coop) { lastspawn = find(lastspawn, classname, "info_player_coop"); @@ -1071,6 +1950,7 @@ entity () SelectSpawnPoint = lastspawn = find(world, classname, "info_player_coop"); } if (lastspawn != world) { + self.last_spawn_spot = lastspawn; return (lastspawn); } } else { @@ -1084,6 +1964,7 @@ entity () SelectSpawnPoint = attempts = attempts + 1; if (CheckSpawnPoint(spot.origin) || (attempts >= 10)) { lastspawn = spot; + self.last_spawn_spot = spot; return (spot); } spot = find(spot, classname, "info_player_deathmatch"); @@ -1097,13 +1978,19 @@ entity () SelectSpawnPoint = if (serverflags) { spot = find(world, classname, "info_player_start2"); if (spot) { + self.last_spawn_spot = spot; return (spot); } } spot = find(world, classname, "info_player_start"); + if (!spot) { + spot = find(world, classname, "info_player_teamspawn"); + } + if (!spot) { error("PutClientInServer: no info_player_start on level\n"); } + self.last_spawn_spot = spot; return (spot); }; @@ -1113,29 +2000,133 @@ void () TeamFortress_SetEquipment; void () TeamFortress_StartTimers; void () player_touch; -// moves the client forward if spawn point is occupied -void () CheckClientSpawn = { - local float attempts; - local vector orig = self.origin; - attempts = 0; - while (!CheckSpawnPoint(self.origin) && attempts < 30) { - makevectors(self.angles); - self.origin = self.origin + v_forward * 60; - attempts = attempts + 1; - } +void (entity p) SetVoteParams = { + if (!(toggleflags & TFLAG_FIRSTENTRY)) { + toggleflags = parm10; + toggleflags = (toggleflags | TFLAG_FIRSTENTRY); + + //Proportion required to trigger a map change + vote_threshold = CF_GetSetting("vt", "vote_threshold", "0.5"); + if(vote_threshold <= 0 || vote_threshold > 1) { + dprint("localinfo vote_threshold was outside of valid range, defaulting to 0.5\n"); + vote_threshold = 0.5; + } + } + + sprint(p, PRINT_HIGH, "Voting time! Use \bcmd listmaps\b to see available maps\n"); + sprint(p, PRINT_HIGH, "Use \bcmd votemap \b to state your preference\n"); + sprint(p, PRINT_HIGH, "Use \bcmd showvotes\b to see the current voting status\n"); + p.items = 0; + p.health = 100; + p.armorvalue = 0; + p.ammo_shells = 0; + p.ammo_nails = 0; + p.ammo_rockets = 0; + p.ammo_cells = 0; + p.armortype = 0; + // why? + p.team_no = 0; + p.playerclass = 0; + p.flags |= FL_ONGROUND; + p.skin = rint(random() * 9); //self.playerclass; + stuffcmd(p, strcat("color ", ftos(rint(random() * 12)), " ", ftos(rint(random() * 12)), "\n")); + + p.takedamage = DAMAGE_AIM; //DAMAGE_YES; + p.attack_finished = self.client_time + 0.3; + p.th_pain = player_pain; + p.th_die = PlayerDie; + p.height = 0; + + p.deadflag = 0; + p.pausetime = 0; + + TeamFortress_StartTimers(); + + local entity spot = SelectSpawnPoint(); + + p.observer_list = spot; + p.origin = spot.origin + '0 0 1'; + p.angles = spot.angles; + p.fixangle = 1; + + FO_SetModel(p, "progs/player.mdl"); + p.modelindex = modelindex_player; + setsize(p, VEC_HULL_MIN, VEC_HULL_MAX); + + p.view_ofs = '0 0 22'; + p.velocity = '0 0 0'; + player_stand1(); + + makevectors(p.angles); + spawn_tfog(p.origin + v_forward * 20); +} - if (attempts == 30) { - dprint("Warning: Could not find a safe spawn point for player!\n"); - self.origin = orig; +void (entity player, string field, float fieldval) CheckSetInfoKey = +{ + float val; + val = infokeyf(player, field); + + // TODO - this isn't needed on later versions of fte, we can spam set the value + if (val != fieldval) + forceinfokey(player, field, ftos(fieldval)); +}; + +void (entity player) UpdateScoreboardInfo = { + + CheckSetInfoKey(player, "afflicted", player.afflicted); + CheckSetInfoKey(player, "teamafflicted", player.teamafflicted); + CheckSetInfoKey(player, "damagegiven", player.damagegiven); + CheckSetInfoKey(player, "damagetaken", player.damagetaken); + CheckSetInfoKey(player, "kills", player.kills); + CheckSetInfoKey(player, "teamkills", player.sbteamkills); + CheckSetInfoKey(player, "deaths", player.deaths); + CheckSetInfoKey(player, "caps", player.caps); + CheckSetInfoKey(player, "touches", player.touches); + CheckSetInfoKey(player, "team_no", player.team_no); + CheckSetInfoKey(player, "playerclass", player.playerclass); + CheckSetInfoKey(player, "ready", player.stat_flags & PLAYER_READY); +}; + +void (float all_dimensions) SetDimensions = { + if (all_dimensions) + { + self.dimension_seen = DMN_NOFLASH; + self.tfstate &= ~TFSTATE_FLASHED; + + switch (self.team_no) + { + case TEAM_BLUE: + self.dimension_see = DMN_NOFLASH | DMN_HIDDEN | DMN_TEAMBLUE; + break; + case TEAM_RED: + self.dimension_see = DMN_NOFLASH | DMN_HIDDEN | DMN_TEAMRED; + break; + case TEAM_YELL: + self.dimension_see = DMN_NOFLASH | DMN_HIDDEN | DMN_TEAMYELL; + break; + case TEAM_GREN: + self.dimension_see = DMN_NOFLASH | DMN_HIDDEN | DMN_TEAMGREN; + break; + default: + self.dimension_see = DMN_NOFLASH | DMN_HIDDEN; + break; + } } }; void () PutClientInServer = { + UpdateClientPlayerSpawn(self); + + if (fo_login_required && self.fo_login == string_null) + UpdateClient_Login(self); + local float oldclass; local entity spot; local entity te; + self.spawn_gen += 1; + self.touch = player_touch; self.classname = "player"; self.health = 100; @@ -1161,17 +2152,38 @@ void () PutClientInServer = { self.saveme_time = 0; self.display_tip = 0; self.tip_type = 0; + self.feign_next_damage = 0; + self.conc_state.mag = 0; + + FO_InstantReloadAllWeapons(self); - self.reload_shotgun = 0; - self.reload_super_shotgun = 0; - self.reload_grenade_launcher = 0; - self.reload_rocket_launcher = 0; + // Only need initial init here. + // For hwguy we rotate the seed value, each seed generates many random nums. + for (int i = 0; i < PRNG_NUM_STATES; i++) + self.prng_base[i] = 65535 * random(); + self.primed_gren_exp = 0; self.immune_to_check = time + 10; - self.fire_held_down = 0; + SetDimensions(TRUE); + self.special_next = 0; + + setmodel(self, string_null); + modelindex_null = self.modelindex; + + setmodel(self, "progs/eyes.mdl"); + modelindex_eyes = self.modelindex; + + setmodel(self, "progs/player.mdl"); + modelindex_player = self.modelindex; + + // TODO: There's a bug here where self.team_no is set to 0 immediately after joining team + /* if(votemode) { */ + /* SetVoteParams(self); */ + /* } */ - if (! self.last_playerclass) - self.last_playerclass = self.playerclass; + if(no_fire_mode) { + return; + } // remove prime timers to avoid getting an old grenade in your face te = find(world, classname, "primetimer"); @@ -1183,14 +2195,23 @@ void () PutClientInServer = { DecodeLevelParms(); - if (self.playerclass == 0) { - if (TeamFortress_TeamIsCivilian(self.team_no)) { - TeamFortress_ChangeClass(11); - } + Predict_InitPlayer(self); + + if (self.playerclass != PC_CIVILIAN && TeamFortress_TeamIsCivilian(self.team_no)) { + TeamFortress_ChangeClass(11); } - if ((deathmatch == 3) && (self.nextpc != 0)) { - self.playerclass = self.nextpc; - self.nextpc = 0; + + int prev_playerclass = self.playerclass; + + if (deathmatch == 3) { + if(self.nextpc != self.playerclass) { + if (self.playerclass != 0) { + local float timeplayed = gametime - self.classtime; + LogEventChangeClass(self, self.playerclass, self.nextpc, timeplayed); + self.classtime = gametime; + } + self.playerclass = self.nextpc; + } if (self.playerclass == PC_RANDOM) self.tfstate = self.tfstate | TFSTATE_RANDOMPC; else { @@ -1200,10 +2221,17 @@ void () PutClientInServer = { } } + /* + if ((!IsLegalClass(self.playerclass) && !override_mapclasses) || CF_ClassIsRestricted(self.team_no, self.playerclass)) { + TeamFortress_ChangeClass(0); + Menu_Class(0); + } + */ + if (self.tfstate & TFSTATE_RANDOMPC) { oldclass = self.playerclass; self.playerclass = 1 + floor(random() * (10 - 1)); - while (!IsLegalClass(self.playerclass) || + while ((!IsLegalClass(self.playerclass) && !override_mapclasses) || (self.playerclass == oldclass) || CF_ClassIsRestricted(self.team_no, self.playerclass)) { self.playerclass = 1 + floor(random() * (10 - 1)); @@ -1216,31 +2244,32 @@ void () PutClientInServer = { if (self.playerclass != PC_ENGINEER) Engineer_RemoveBuildings(self); + self.skin = self.playerclass; + self.takedamage = 2; TeamFortress_PrintClassName(self, self.playerclass, self.tfstate & TFSTATE_RANDOMPC); + + self.attack_finished = 0; // Allow below to change weapon freely. TeamFortress_SetEquipment(); TeamFortress_SetHealth(); TeamFortress_SetSpeed(self); TeamFortress_SetSkin(self); TeamFortress_StartTimers(); + if(quadmode && quad_roles) { + sprint(self, PRINT_HIGH, "\sRole:\s ", GetTeamRole(self.team_no).name, "\n"); + } + stuffcmd(self, "v_idlescale 0\n"); stuffcmd(self, "v_cshift; wait; bf\n"); SetTeamName(self); - W_SetCurrentAmmo(self); - if (self.current_weaponslot && self.last_playerclass == self.playerclass) - W_ChangeWeapon(self.current_weaponslot); - else - W_ChangeWeapon(1); - self.last_playerclass = self.playerclass; - self.attack_finished = time + 0.3; + self.attack_finished = self.client_time + 0.3; self.th_pain = player_pain; self.th_die = PlayerDie; self.height = 0; - Sniper_ZoomReset(self); self.deadflag = 0; self.pausetime = 0; @@ -1251,13 +2280,13 @@ void () PutClientInServer = { self.origin = spot.origin + '0 0 1'; self.angles = spot.angles; self.fixangle = 1; - CheckClientSpawn(); if (self.playerclass != 0) spawn_tdeath(self.origin, self); if ((spot.classname == "info_player_teamspawn") && - (cb_prematch_time < time)) { + (!cb_prematch)) { + if (spot.items != 0) { te = Finditem(spot.items); if (te) @@ -1282,6 +2311,7 @@ void () PutClientInServer = { spot.think = SUB_Remove; } } + setmodel(self, string_null); modelindex_null = self.modelindex; @@ -1295,6 +2325,7 @@ void () PutClientInServer = { self.modelindex = modelindex_null; self.menu_input = nil; } + setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); self.view_ofs = '0 0 22'; self.velocity = '0 0 0'; @@ -1314,18 +2345,59 @@ void () PutClientInServer = { if (cease_fire) { sprint(self, PRINT_HIGH, "\n\nCease fire mode\n"); self.immune_to_check = time + 10; - self.tfstate = self.tfstate | TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(self); + self.tfstate |= TFSTATE_CANT_MOVE; } -}; -void () info_player_start = { - if (CheckExistence() == FALSE) { - dremove(self); - return; + if (self.playerclass == 0 && self.team_no > 0) { + Menu_Class(0); + } + + if(duelmode && duel_spawn_guard && self.team_no && self.playerclass) { + self.duel_guarded = spot; + self.invincible_finished = time + 666; + } + + local float autodisguise = FO_GetUserSetting(self, "autodisguise", "ad", "off"); + if (self.playerclass == PC_SPY) { + switch(autodisguise) { + case 1: + FO_Spy_DisguiseLastSpawned(self, FALSE); + break; + case 2: + FO_Spy_DisguiseLast(self, FALSE); + break; + } + } + + if(self.playerclass == PC_SCOUT && self.ScannerOn == 1) { + te = spawn(); + te.nextthink = time + 2; + te.think = TeamFortress_Scan; + te.owner = self; + te.classname = "timer"; + te.netname = "scanner"; + sprint(self, PRINT_HIGH, "Scanner on\n"); + self.ScannerOn = 1; + + if (!(self.tf_items_flags & NIT_SCANNER_ENEMY)) + self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_ENEMY; } + + // Activate medic aura by default when changing to class and off + if (self.playerclass == PC_MEDIC && prev_playerclass != PC_MEDIC && + !self.aura_active) + CF_Medic_AuraToggle(); + + UpdateReadyStatus(); }; +//void () info_player_start = { +// if (CheckExistence() == FALSE) { +// dremove(self); +// return; +// } +//}; + void () info_player_start2 = { if (CheckExistence() == FALSE) { dremove(self); @@ -1413,8 +2485,24 @@ void () NextLevel = { if (already_cycled) return; - + already_cycled = 1; + vote_style = CF_GetSetting("vs", "vote_style", "1"); + local string vm = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + if (vote_style || votemode == 2){ + if(votemode == 1 && nextmap && nextmap != vm) { + localcmd ("localinfo votemode off\n"); + //changelevel(vote_result); + } else if(vote_style & 1 || votemode == 2) { + nextmap = vm; + if(!(vote_style & 4)) { + localcmd ("localinfo votemode on\n"); + } + //changelevel(FO_GetUserSettingString(world, "vote_map", "votemap", "se2")); + } else if (nextmap == "" || nextmap == string_null) { + nextmap = mapname; + } + } o = spawn(); o.map = nextmap; o.think = execute_changelevel; @@ -1422,9 +2510,18 @@ void () NextLevel = { }; void () CheckRules = { - if ((timelimit && time >= timelimit) || (fraglimit && self.frags >= fraglimit)) { + if (!clanbattle) + { + if ((timelimit && (time >= timelimit))) + { + NextLevel(); + RemovePrimeTimers(); + RemoveGrenades(); + } + } + if ((fraglimit && (self.frags >= fraglimit))) + { NextLevel(); - RemoveGrenadeTimers(); RemovePrimeTimers(); RemoveGrenades(); } @@ -1445,8 +2542,7 @@ void () PlayerDeathThink = { if (self.button2 || self.button1 || self.button0) return; self.deadflag = DEAD_RESPAWNABLE; - self.tfstate = - self.tfstate - (self.tfstate & TFSTATE_RESPAWN_READY); + self.tfstate &= ~TFSTATE_RESPAWN_READY; return; } if ((!self.button2 && !self.button1) && !self.button0) { @@ -1471,6 +2567,25 @@ void () PlayerDeathThink = { } }; +void player_asscan_down1(); + +enum ConcMode { + CONC_IDLESCALE, + CONC_CLASSIC, + CONC_AIM, +}; + +ConcMode GetConcMode() { + if (old_grens) + return CONC_IDLESCALE; + else if (fo_concuss) + return CONC_AIM; + else + return CONC_CLASSIC; +} + +entity FindConcTimer(entity player); + void () PlayerJump = { local entity te; local float stumble; @@ -1480,10 +2595,11 @@ void () PlayerJump = { if (self.flags & FL_WATERJUMP) return; + if (self.waterlevel >= 2) { - if (self.watertype == -3) + if (self.watertype == CONTENT_WATER) self.velocity_z = 100; - else if (self.watertype == -4) + else if (self.watertype == CONTENT_SLIME) self.velocity_z = 80; else self.velocity_z = 50; @@ -1491,71 +2607,40 @@ void () PlayerJump = { if (self.swim_flag < time) { self.swim_flag = time + 1; if (random() < 0.5) - sound(self, CHAN_BODY, "misc/water1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_BODY, "misc/water1.wav", 1, ATTN_NORM); else - sound(self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM); - } - if (self.fire_held_down && (self.current_weapon == WEAP_ASSAULT_CANNON)) { - stuffcmd(self, "v_idlescale 0\n"); - self.tfstate = self.tfstate | TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self); - self.weaponframe = 0; - self.heat = 0; - self.count = 1; - player_assaultcannondown1(); + FO_Sound(self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM); } + + if (self.tfstate & TFSTATE_AC_FIRING) + StopAssCan(); + return; } + if (!(self.flags & FL_ONGROUND)) return; + if (!(self.flags & FL_JUMPRELEASED)) return; self.flags = self.flags - (self.flags & FL_JUMPRELEASED); self.button2 = 0; - sound(self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); + NB_ConcCapAction(self, self.playerclass, &self.tfstate, + time, &self.conc_cap_time, kLand); + FO_Sound(self, CHAN_AUTO, "player/plyrjmp8.wav", 1, ATTN_NORM); - if (self.fire_held_down && (self.current_weapon == WEAP_ASSAULT_CANNON)) { - if (!cannon_air) { - if (self.antispam_cannon_air < time) { - sprint(self, PRINT_MEDIUM, "You cannot fire the assault cannon without your feet on the ground...\n"); - self.antispam_cannon_air = time + 3; - } - self.tfstate = self.tfstate | TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self); - self.weaponframe = 0; - self.count = 1; - self.heat = 0; - player_assaultcannondown1(); - stuffcmd(self, "v_idlescale 0\n"); - } else { - self.tfstate = self.tfstate | TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - } - } - if (old_grens != 1) { - te = find(world, classname, "timer"); - while (((te.owner != self) || (te.think != ConcussionGrenadeTimer)) - && (te != world)) - te = find(te, classname, "timer"); - - if ((te != world) && (te != self)) { - crandom(); - stumble = crandom() * (te.health / 100); - if (crandom() < 0) { - self.velocity_x = self.velocity_y + stumble; - self.velocity_y = self.velocity_x + stumble; - } else { - self.velocity_x = (-1 * self.velocity_y) + stumble; - self.velocity_y = (-1 * self.velocity_x) + stumble; - } + if (!cannon_air && self.tfstate & TFSTATE_AC_FIRING) { + if (self.antispam_cannon_air < time) { + sprint(self, PRINT_MEDIUM, "You cannot fire the assault cannon without your feet on the ground...\n"); + self.antispam_cannon_air = time + 3; } + StopAssCan(); } + + if (GetConcMode() == CONC_CLASSIC && (self.tfstate & TFSTATE_CONC)) + Conc_Jump(&self.conc_state, self); }; .float dmgtime; @@ -1568,9 +2653,9 @@ void () WaterMove = { if (self.waterlevel != 3) { if (self.air_finished < time) { - sound(self, 2, "player/gasp2.wav", 1, 1); + FO_Sound(self, CHAN_VOICE, "player/gasp2.wav", 1, 1); } else if (self.air_finished < (time + 9)) { - sound(self, 2, "player/gasp1.wav", 1, 1); + FO_Sound(self, CHAN_VOICE, "player/gasp1.wav", 1, 1); } self.air_finished = time + 12; self.dmg = 2; @@ -1583,38 +2668,49 @@ void () WaterMove = { self.pain_finished = time + 1; } } + if (!self.waterlevel) { - if (self.flags & 16) { - sound(self, 4, "misc/outwater.wav", 1, 1); - self.flags = self.flags - 16; + if (self.flags & FL_INWATER) { + FO_Sound(self, CHAN_BODY, "misc/outwater.wav", 1, 1); + self.flags = self.flags - FL_INWATER; } return; } - if (self.watertype == -5) { + if (self.watertype == CONTENT_LAVA) { if (self.dmgtime < time) { - if (self.radsuit_finished > time) - self.dmgtime = time + 1; - else - self.dmgtime = time + 0.2; - TF_T_Damage(self, world, world, (10 * self.waterlevel), 0, 16); + if(votemode && !vote_anarchy_mode) { + respawn(); + } else { + if (self.radsuit_finished > time) + self.dmgtime = time + 1; + else + self.dmgtime = time + 0.2; + TF_T_Damage(self, world, world, (10 * self.waterlevel), 0, 16); + } } } else if (self.watertype == -4) { if ((self.dmgtime < time) && (self.radsuit_finished < time)) { - self.dmgtime = time + 1; - T_Damage(self, world, world, (4 * self.waterlevel)); + if(votemode && !vote_anarchy_mode) { + respawn(); + } else { + self.dmgtime = time + 1; + T_Damage(self, world, world, (4 * self.waterlevel)); + } } } - if (!(self.flags & 16)) { - if (self.watertype == -5) { - sound(self, 4, "player/inlava.wav", 1, 1); + if (!(self.flags & FL_INWATER)) { + switch(self.watertype) { + case CONTENT_LAVA: + FO_Sound(self, CHAN_BODY, "player/inlava.wav", 1, 1); + break; + case CONTENT_WATER: + FO_Sound(self, CHAN_BODY, "player/inh2o.wav", 1, 1); + break; + case CONTENT_SLIME: + FO_Sound(self, CHAN_BODY, "player/slimbrn2.wav", 1, 1); + break; } - if (self.watertype == -3) { - sound(self, 4, "player/inh2o.wav", 1, 1); - } - if (self.watertype == -4) { - sound(self, 4, "player/slimbrn2.wav", 1, 1); - } - self.flags = self.flags + 16; + self.flags = self.flags + FL_INWATER; self.dmgtime = 0; } }; @@ -1647,7 +2743,54 @@ void () CheckWaterJump = { } }; +void SpawnBubble(vector origin); + +static void ConcUpdate() { + if (self.tfstate & TFSTATE_CONC == 0) + return; + + if (self.invincible_finished > time) { + self.conc_state.mag = 0; + } else { + if (!Conc_Update(&self.conc_state, self, time)) + return; + } + + if (self.conc_state.mag) { + if (self.conc_state.mag % CONC_HZ == 0) + SpawnBubble(self.origin); + } else { + RemoveConc(self, FALSE); + sprint(self.owner, PRINT_HIGH, "Your head feels better now\n"); + } +} + +// If a forceinfokey and a SVC_SETINFO are both set in the same frame, they +// will clobber each other. Thus we do skin setinfos like normal, and then +// per-player custom skin infos are immediately applied for those who need to +// update on the next frame. +void () UpdateAllSkins = { + local entity target = find(world, classname, "player"); + local entity pov; + + while (target != world) { + if (target.needs_skin_update) { + pov = find(world, classname, "player"); + while (pov != world) { + SetSkinInfoFor(pov, target); + pov = find(pov, classname, "player"); + } + target.needs_skin_update = 0; + } + target = find(target, classname, "player"); + } +} + void () PlayerPreThink = { + FO_UpdateClientTime(); + ConcUpdate(); + UpdateAllSkins(); + if (self.impulse) { if (self.impulse == TF_VOTENEXT) { Vote_NextMap(self); @@ -1667,6 +2810,7 @@ void () PlayerPreThink = { if (self.cheat_level > 0) { self.cheat_level = self.cheat_level - 1; } + if (intermission_running) { IntermissionThink(); return; @@ -1677,12 +2821,13 @@ void () PlayerPreThink = { } // Check if timelimit/fraglimit has been met - CheckRules(); + if(!votemode && !(duelmode && duel_allow_draw)) CheckRules(); - if (self.playerclass != 0) { + if (self.playerclass != 0 || votemode) { WaterMove(); } - if (self.deadflag >= 2) { + + if (self.deadflag >= DEAD_DEAD) { PlayerDeathThink(); return; } @@ -1694,10 +2839,11 @@ void () PlayerPreThink = { Spy_RemoveDisguise(self); } } - if (self.deadflag == 1) { + if (self.deadflag == DEAD_DYING) { return; } - if (self.is_feigning) { + + if (IsFeigned(self)) { if (self.flags & FL_ONGROUND) { // check area for entities - if found, bounce player forward if (!self.feign_areachecked) { @@ -1710,8 +2856,7 @@ void () PlayerPreThink = { self.feign_areachecked = 1; if (!(self.tfstate & TFSTATE_CANT_MOVE)) { self.movetype = MOVETYPE_NONE; - self.tfstate = self.tfstate | TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(self); + self.tfstate |= TFSTATE_CANT_MOVE; } } } @@ -1730,13 +2875,18 @@ void () PlayerPreThink = { self.flags = self.flags | FL_JUMPRELEASED; } } + if ((time < self.pausetime) || (cease_fire == 1)) { self.velocity = '0 0 0'; } - if (time > self.attack_finished && !self.currentammo && self.weapon > WEAP_AXE) { - W_ChangeWeapon(W_BestWeaponSlot()); - W_SetCurrentAmmo(self); - W_WeaponState_Save(self); + + // Catch all. + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + if ((self.client_time >= self.attack_finished) && + (ws.weapon > WEAP_AXE) && ((ws->wi)->ammo_type != AMMO_NONE)) { + if (*ws->ammo_remaining == 0) + W_ChangeToBestWeapon(); } }; @@ -1747,7 +2897,7 @@ void () CheckPowerups = { if (self.health <= 0) { return; } - if (self.playerclass == 0) { + if (self.playerclass == 0 && !votemode) { self.modelindex = modelindex_null; } else { if ((self.is_undercover == 1) && (invis_only == 1)) { @@ -1755,13 +2905,13 @@ void () CheckPowerups = { self.modelindex = modelindex_eyes; } else { if (self.invisible_finished) { - if (self.tfstate & 64) { + if (self.pstate & PSTATE_INVISIBLE) { if (self.invisible_finished < (time + 10)) { self.invisible_finished = time + 666; } } if (self.invisible_sound < time) { - sound(self, 0, "items/inv3.wav", 0.5, 2); + FO_Sound(self, CHAN_AUTO, "items/inv3.wav", 0.5, 2); self.invisible_sound = time + ((random() * 3) + 1); } if (self.invisible_finished < (time + 3)) { @@ -1769,7 +2919,7 @@ void () CheckPowerups = { sprint(self, PRINT_HIGH, "Ring of shadows magic is fading\n"); stuffcmd(self, "bf\n"); - sound(self, 0, "items/inv2.wav", 1, 1); + FO_Sound(self, CHAN_AUTO, "items/inv2.wav", 1, 1); self.invisible_time = time + 1; } if (self.invisible_time < time) { @@ -1778,7 +2928,7 @@ void () CheckPowerups = { } } if (self.invisible_finished < time) { - self.items = self.items - 524288; + self.items &= ~IT_INVISIBILITY; self.invisible_finished = 0; self.invisible_time = 0; } @@ -1790,7 +2940,7 @@ void () CheckPowerups = { } } if (self.invincible_finished) { - if (self.tfstate & 32) { + if (self.pstate & PSTATE_INVINCIBLE) { if (self.invincible_finished < (time + 10)) { self.invincible_finished = time + 666; } @@ -1800,7 +2950,7 @@ void () CheckPowerups = { sprint(self, PRINT_HIGH, "Protection is almost burned out\n"); stuffcmd(self, "bf\n"); - sound(self, 0, "items/protect2.wav", 1, 1); + FO_Sound(self, CHAN_AUTO, "items/protect2.wav", 1, 1); self.invincible_time = time + 1; } if (self.invincible_time < time) { @@ -1809,7 +2959,7 @@ void () CheckPowerups = { } } if (self.invincible_finished < time) { - self.items = self.items - 1048576; + self.items &= ~IT_INVULNERABILITY; self.invincible_time = 0; self.invincible_finished = 0; } @@ -1834,7 +2984,7 @@ void () CheckPowerups = { } } if (self.super_damage_finished) { - if (self.tfstate & 128) { + if (self.pstate & PSTATE_QUAD) { if (self.super_damage_finished == (time + 10)) { self.super_damage_finished = time + 666; } @@ -1843,7 +2993,7 @@ void () CheckPowerups = { if (self.super_time == 1) { sprint(self, PRINT_HIGH, "Quad damage is wearing off\n"); stuffcmd(self, "bf\n"); - sound(self, 0, "items/damage2.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_AUTO, "items/damage2.wav", 1, ATTN_NORM); self.super_time = time + 1; } if (self.super_time < time) { @@ -1852,13 +3002,12 @@ void () CheckPowerups = { } } if (self.super_damage_finished < time) { - self.items = self.items - 4194304; + self.items &= ~IT_QUAD; self.super_damage_finished = 0; self.super_time = 0; } if (self.super_damage_finished > time) { - self.effects = self.effects | 8; - self.effects = self.effects | 64; + self.effects |= EF_DIMLIGHT | EF_BLUE; } else { lighton = 0; te = find(world, classname, "item_tfgoal"); @@ -1877,7 +3026,7 @@ void () CheckPowerups = { } if (self.radsuit_finished) { self.air_finished = time + 12; - if (self.tfstate & 256) { + if (self.pstate & PSTATE_RADSUIT) { if (self.radsuit_finished == (time + 10)) { self.radsuit_finished = time + 666; } @@ -1887,7 +3036,7 @@ void () CheckPowerups = { sprint(self, PRINT_HIGH, "Air supply in biosuit expiring\n"); stuffcmd(self, "bf\n"); - sound(self, 0, "items/suit2.wav", 1, 1); + FO_Sound(self, CHAN_AUTO, "items/suit2.wav", 1, 1); self.rad_time = time + 1; } if (self.rad_time < time) { @@ -1896,28 +3045,40 @@ void () CheckPowerups = { } } if (self.radsuit_finished < time) { - self.items = self.items - 2097152; + self.items &= ~IT_SUIT; self.rad_time = 0; self.radsuit_finished = 0; } } }; -void () PlayerPostThink = { - local float fdmg; - - if (self.view_ofs == '0 0 0') { - return; - } - if (self.deadflag) { - DeadImpulses(); - self.impulse = 0; +void FO_HandleTFStateUpdate() { + if (self.last_tfstate == self.tfstate) return; - } - if (((self.jump_flag < -300) && (self.flags & 512)) && + + float state_changed = self.last_tfstate ^ self.tfstate; + if (state_changed & (TFSTATE_NO_WEAPON | TFSTATE_RELOADING)) + W_UpdateWeaponModel(self); + if (state_changed & (TFSTATE_AIMING | TFSTATE_CANT_MOVE | TFSTATE_TRANQUILISED)) + TeamFortress_SetSpeed(self); + + self.last_tfstate = self.tfstate; +} + +void TeamFortress_MOTD(); + +static void PlayerPostThinkAlive() { + float fdmg; + + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + self.currentammo = + (ws->wi)->ammo_type != AMMO_NONE ? *ws->ammo_remaining : 0; + + if (((self.jump_flag < -300) && (self.flags & FL_ONGROUND)) && (self.health > 0)) { - if (self.watertype == -3) { - sound(self, 4, "player/h2ojump.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_WATER) { + FO_Sound(self, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM); } else { if (self.jump_flag < -650) { fdmg = 5; @@ -1931,46 +3092,130 @@ void () PlayerPostThink = { } fdmg = rint(fdmg); TF_T_Damage(self, world, world, fdmg, 1, 0); - sound(self, 2, "player/land2.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM); self.deathtype = "falling"; } else { - sound(self, 2, "player/land.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "player/land.wav", 1, ATTN_NORM); } } } self.jump_flag = self.velocity_z; - CheckPowerups(); - W_WeaponFrame(); - if (self.motd <= 400) { - TeamFortress_MOTD(); + + Forward_OpenDoors(self); + + if(votemode) { + CheckPowerups(); + FO_ReloadFrame(); + W_WeaponFrame(); + //TeamFortress_CheckforCheats(); } else { - if (self.cheat_check == 0) { - self.cheat_check = time + 5; + CheckPowerups(); + FO_ReloadFrame(); + ButtonFrame(); + W_WeaponFrame(); + if (self.motd >= 0) { + TeamFortress_MOTD(); + } else { + if (self.cheat_check == 0) { + self.cheat_check = time + 5; + } + } + if (self.cheat_check <= time) { + TeamFortress_CheckTeamCheats(self); + self.cheat_check = time + 3; } } + + FO_HandleTFStateUpdate(); if (time >= self.StatusRefreshTime) RefreshStatusBar(self); - if (self.cheat_check <= time) { - TeamFortress_CheckTeamCheats(); - self.cheat_check = time + 3; +} + +void () PlayerPostThink = { + FO_CheckClientThink(); + UpdateScoreboardInfo(self); + + if (self.motd >= 0) + TeamFortress_MOTD(); + + if (self.deadflag || self.playerclass == 0) { + DeadImpulses(); + self.impulse = 0; + } else { + PlayerPostThinkAlive(); } + + FOPlayer fop = (FOPlayer)self; + fop.RewindUpdate(); + Predict_Update(TRUE); }; +void () UpdateAllClientsTeamScores = { + local entity e; + + e = find(world, classname, "player"); + while (e) { + UpdateClientTeamScores(e); + e = find(e, classname, "player"); + } + + e = find(world, classname, "observer"); + while (e) { + UpdateClientTeamScores(e); + e = find(e, classname, "observer"); + } +} + +void (entity pov, entity target) SetBottomColorInfoFor = { + local float target_idx = target.colormap - 1; + local string color = TeamFortress_TeamGetColorFor(pov, target.team_no); + + msg_entity = pov; + WriteByte(MSG_ONE, SVC_SETINFO); + WriteByte(MSG_ONE, target_idx); + WriteString(MSG_ONE, "bottomcolor"); + WriteString(MSG_ONE, color); +} + +void (entity pov, entity target) SetTopColorInfoFor = { + local float target_idx = target.colormap - 1; + local string color = TeamFortress_TeamGetColorFor(pov, target.team_no); + + msg_entity = pov; + WriteByte(MSG_ONE, SVC_SETINFO); + WriteByte(MSG_ONE, target_idx); + WriteString(MSG_ONE, "topcolor"); + WriteString(MSG_ONE, color); +} + +void (entity pov, entity target) SetSkinInfoFor = { + SetTopColorInfoFor(pov, target); + SetBottomColorInfoFor(pov, target); +} + void () ClientConnect = { - local entity te; - local string st; - local string st2; - local float got_one; + if (!infokeyf(self,INFOKEY_P_CSQCACTIVE)) { + sprint(self, PRINT_HIGH, "FTE/CSQC is required for this server, please download the latest client package at www.fortressone.org\n"); + dropclient(self); + return; + } bprint(PRINT_HIGH, self.netname, " entered the game\n"); + sprint(self, PRINT_HIGH, "FortressOne: Weapon Prediction ", wp_version, "\n"); + + spawnfunc_FOPlayer(); self.motd = 0; - self.got_aliases = 0; if (self.netname == string_null) KickCheater(self); - TeamFortress_Alias("id", TF_ID, 0); + if (infokey(self,"*login")) + self.login = infokey(self,"*login"); + if (infokey(self,"*admin")) + self.is_admin = stof(infokey(self, "*admin")); + if (loginRequired && self.login == string_null) + sprint(self,2, "Login required, please use \"cmd login \" \nbefore joining the game\n"); TeamFortress_ExecMapScript(self); self.has_disconnected = 0; @@ -1980,128 +3225,135 @@ void () ClientConnect = { GotoNextMap(); } - st2 = infokey(world, "apw"); - if (st2 == string_null) - st2 = infokey(world, "adminpwd"); - - st = infokey(self, "apw"); + string st = infokey(self, "apw"); if (st == string_null) st = infokey(self, "adminpwd"); - - if ((st2 != string_null) && (st != string_null) && (st == st2)) { - self.is_admin = TRUE; - stuffcmd(self, "setinfo apw \""); - stuffcmd(self, "\"\n"); - stuffcmd(self, "setinfo adminpwd \""); - stuffcmd(self, "\"\n"); - TeamFortress_Alias("countplayers", 192, 0); - TeamFortress_Alias("deal", 189, 0); - TeamFortress_Alias("kick", 190, 0); - TeamFortress_Alias("ban", 191, 0); - TeamFortress_Alias("next", 195, 0); - TeamFortress_Alias("ceasefire", 193, 0); - TeamFortress_Alias("listips", 198, 0); - } else - self.is_admin = FALSE; + if (st) { + Admin_Check(st); + if (self.is_admin) + Admin_Aliases(); + else + self.is_admin = FALSE; + } if (clanbattle && (self.has_disconnected != 1)) { - got_one = 0; st = infokey(self, "tf_id"); self.tf_id = stof(st); - if ((st != string_null) && (self.tf_id != 0)) { - sprint(self, PRINT_HIGH, "Welcome back!\n"); - te = find(world, classname, "ghost"); - while (te) { - if (te.tf_id == self.tf_id) { - got_one = 1; - TeamFortress_TeamSet(te.team_no); - self.frags = te.frags; - self.real_frags = te.real_frags; - if (!(toggleflags & TFLAG_TEAMFRAGS) && - !(toggleflags & TFLAG_FULLTEAMSCORE)) { - self.frags = self.real_frags; - } - self.playerclass = te.playerclass; - self.tfstate = te.tfstate; - dremove(te); - te = world; - } else - te = find(te, classname, "ghost"); - } - } - if (!got_one) { - if (game_locked && (cb_prematch_time < time)) { - sprint(self, PRINT_HIGH, - "Closed server. Clan battle in progress.\n"); - KickCheater(self); - return; - } - last_id = last_id + 20 + random() * 10; - self.tf_id = rint(random() * 10 + last_id); - st = ftos(self.tf_id); - stuffcmd(self, "setinfo tf_id "); - stuffcmd(self, st); - stuffcmd(self, "\n"); - sprint(self, PRINT_HIGH, "Your battle ID is ", st, "\n"); + if (!loginRequired) { + if ((st != string_null) && (self.tf_id != 0)) + RejoinWithTfId(); + else + CreateTfIdAndJoin(); } } - if (cb_prematch_time > time) - sprint(self, PRINT_HIGH, "Currently in prematch time\n"); + + InitAllStatuses(self); + UpdateClientMOTD(self); + UpdateClientTeamScores(self); + UpdateClientPrematch(self, !cb_prematch); + UpdateClient_VoteMap_AddAll(self); + TeamFortress_SetSkin(self); + + if (cb_prematch) + sprint(self, PRINT_HIGH, "Currently in \sprematch\s time\n"); }; -void () ClientDisconnect = { +float () RejoinWithTfId = { local entity te; - local string st; - local float fr; - - fr = rint(self.frags); - st = ftos(fr); - bprint4(PRINT_HIGH, self.netname, " left the game with ", st, - " frags\n"); - sound(self, 4, "player/tornoff2.wav", 1, ATTN_NONE); - self.has_disconnected = 1; - TeamFortress_RemoveTimers(); - RemoveGasTimers(); - Engineer_RemoveBuildings(self); - te = find(world, classname, "detpack"); + sprint(self, PRINT_HIGH, "Welcome back!\n"); + te = find(world, classname, "ghost"); while (te) { - if (te.owner == self) { - if (te.weaponmode == 1) { - TeamFortress_SetSpeed(te.enemy); - dremove(te.oldenemy); - dremove(te.observer_list); + if (te.tf_id == self.tf_id) { + TeamFortress_TeamSet_Options(self, te.team_no, TRUE); + UpdateAllClientsTeamScores(); + + self.frags = te.frags; + self.real_frags = te.real_frags; + if (!(toggleflags & TFLAG_TEAMFRAGS) && + !(toggleflags & TFLAG_FULLTEAMSCORE)) { + self.frags = self.real_frags; } + self.playerclass = te.playerclass; + self.nextpc = te.playerclass; + self.tfstate = te.tfstate; dremove(te); te = world; - } - te = find(te, classname, "detpack"); + return 1; + } else + te = find(te, classname, "ghost"); } - te = find(world, classname, "countdown_timer"); + return 0; +} + +void () CreateTfIdAndJoin = { + local string st; + if (game_locked && (!cb_prematch)) { + sprint(self, PRINT_HIGH, + "Closed server. Clan battle in progress.\n"); + KickCheater(self); + return; + } + last_id = last_id + 20 + random() * 10; + self.tf_id = rint(random() * 10 + last_id); + st = ftos(self.tf_id); + stuffcmd(self, "setinfo tf_id "); + stuffcmd(self, st); + stuffcmd(self, "\n"); + sprint(self, PRINT_HIGH, "Your battle ID is ", st, "\n"); +} + +void (entity pl) ResetAndRespawnPlayer = { + if(pl.team_no && pl.playerclass) { + pl.classname = "player"; + pl.takedamage = DAMAGE_AIM; + pl.solid = SOLID_SLIDEBOX; + pl.movetype = MOVETYPE_WALK; + TeamFortress_RemoveTimers(); + setspawnparms(pl); + PutClientInServer(); + } + if(infokeyf(pl, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(pl, FALSE); + } +} + +void () CountMatchPlayersAndReset = { + local float playersleft = 0; + local entity te = find (world, classname, "player"); while (te) { - if (te.owner == self) { - if (te.weaponmode == 1) { - TeamFortress_SetSpeed(te.enemy); - dremove(te.oldenemy); - dremove(te.observer_list); - } - dremove(te); - te = world; + if (!te.has_disconnected) { + playersleft = playersleft + 1; } - te = find(te, classname, "countdown_timer"); + te = find (te, classname, "player"); } - RemoveGrenades(); - if (clanbattle && (self.tf_id != 0)) { - te = spawn(); - te.classname = "ghost"; - te.tf_id = self.tf_id; - te.team_no = self.team_no; - te.frags = self.frags; - te.real_frags = self.real_frags; - te.netname = self.netname; - te.playerclass = self.playerclass; - if (self.tfstate & 8) { - te.tfstate = 8; - } + if (!playersleft) { + bprint(PRINT_HIGH, "Match \sabandoned\s, resetting to prematch\n"); + InitPrematch(); + } else if(CountRemainingTeams() < 2) { + ResetPlayersWithCountdown(); + } +} + +void () ClientDisconnect = { + local string st; + local float fr; + + fr = rint(self.frags); + st = ftos(fr); + + local float timeplayed = gametime - self.classtime; + LogEventChangeClass(self, self.playerclass, 0, timeplayed); + self.classtime = gametime; + + bprint(PRINT_HIGH, self.netname, " left the game with ", st, + " frags\n"); + FO_Sound(self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE); + self.has_disconnected = 1; + RemovePlayerOwnedEnts(); + + if (duelmode && clanbattle && !cb_prematch) { + //In duelmode, reset back to prematch if everyone leaves half-way through a round + CountMatchPlayersAndReset(); } set_suicide_frame(); self.netname = string_null; @@ -2111,6 +3363,23 @@ void () ClientDisconnect = { if (self.StatusString) strunzone(self.StatusString); self.StatusString = string_null; + + + + if(cb_prematch) { + //Check in case the player leaving is the last not-readied player + if(self.stat_flags & PLAYER_READY) { + v_ready = (v_ready - 1); + } else if (!CheckAllPlayersReady()) { + UpdateReadyStatus(); + } + } + + if(votemode) { + UnvoteForMap(self); + } + + Predict_Destroy(self); }; string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage = { @@ -2129,7 +3398,7 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage strcat(" was telefragged by ", strcat(pe_attacker.owner.netname, "\n"))); } - if (pf_deathmsg == 37) { + if (pf_deathmsg == DMSG_TEAMKILL) { return strcat(pe_attacker.netname, " shoots his teammate one too many times\n"); } if (pe_attacker.classname == "player" || pe_attacker.classname == "bot") { @@ -2155,37 +3424,31 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage s_deathstring = " stared at his grenade too long\n"; else s_deathstring = " grenades himself\n"; - return strcat(pe_target.netname, s_deathstring); - } else if (pf_deathmsg == DMSG_GREN_NAIL) { - return strcat(pe_target.netname, " hammers himself\n"); - + } else if (pf_deathmsg == DMSG_GREN_SHOCK) { + return strcat(pe_target.netname, " electrocutes himself\n"); + } else if (pf_deathmsg == DMSG_GREN_BURST) { + return strcat(pe_target.netname, " peppers himself\n"); } else if (pf_deathmsg == DMSG_GREN_MIRV) { - if (pe_target.playerclass == PC_DEMOMAN) s_deathstring = " practiced his own Mirv dance\n"; else if (pe_target.playerclass == PC_HVYWEAP) s_deathstring = " allowed his Mirv to turn against him\n"; else s_deathstring = " goes to pieces\n"; - return strcat(pe_target.netname, s_deathstring); - } else if (pf_deathmsg == DMSG_GREN_PIPE) { - return strcat(pe_target.netname, " ambushes himself with his own pipebombs\n"); - } - - else if (pf_deathmsg == 40) + else if (pf_deathmsg == DMSG_GREN_PIPE_AIR) s_deathstring = " tried to juggle his own pipebombs\n"; else if (pf_deathmsg == DMSG_GREN_GAS) s_deathstring = " chokes on his own gas\n"; else if (pf_deathmsg == DMSG_GREN_EMP) s_deathstring = " explodes his ammo and body\n"; - else if (pf_deathmsg == 41) + else if (pf_deathmsg == DMSG_GREN_CALTROP) s_deathstring = " stepped on too many of his own caltrops\n"; else if (pf_deathmsg == DMSG_GREN_FLASH) s_deathstring = " is charred by his own flash grenade\n"; @@ -2208,10 +3471,12 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage s_deathstring = " torches himself\n"; else if (pf_deathmsg == DMSG_LIGHTNING && pe_target.waterlevel > 1) s_deathstring = " discharges into the water\n"; - else if (pf_deathmsg == 38) + else if (pf_deathmsg == DMSG_SENTRYGUN_EXPLODE) s_deathstring = " gets too friendly with his sentry gun\n"; - else if (pf_deathmsg == 39) + else if (pf_deathmsg == DMSG_DISPENSER_EXPLODE) s_deathstring = " dispenses with himself\n"; + else if (pf_deathmsg == DMSG_IMPELLER) + s_deathstring = " couldn't resist their inner impelsions\n"; return strcat(pe_target.netname, s_deathstring); @@ -2269,8 +3534,14 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage } } else if (pf_deathmsg == DMSG_GREN_NAIL) { - s_deathstring = " gets flayed by "; - s_deathstring2 = "'s nail grenade\n"; + s_deathstring = " gets flayed by "; + s_deathstring2 = "'s nail grenade\n"; + } else if (pf_deathmsg == DMSG_GREN_SHOCK) { + s_deathstring = " gets melted by "; + s_deathstring2 = "'s shock grenade\n"; + } else if (pf_deathmsg == DMSG_GREN_BURST) { + s_deathstring = " gets peppered by "; + s_deathstring2 = "'s burst grenade\n"; } else if (pf_deathmsg == DMSG_GREN_MIRV) { if (pe_attacker.playerclass == PC_DEMOMAN) { s_deathstring = " does a dance on "; @@ -2291,7 +3562,7 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage } else if (pf_deathmsg == DMSG_GREN_EMP) { s_deathstring = "'s ammo detonates him as "; s_deathstring2 = "'s EMP fries it\n"; - } else if (pf_deathmsg == 41) { + } else if (pf_deathmsg == DMSG_GREN_CALTROP) { s_deathstring = " stepped on too many of "; s_deathstring2 = "'s caltrops\n"; } else if (pf_deathmsg == DMSG_GREN_FLASH) { @@ -2345,9 +3616,7 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage } } else if (pf_deathmsg == DMSG_AXE) { s_deathstring2 = "\n"; - if (pe_attacker.playerclass == PC_SPY) - s_deathstring = " was knife-murdered by "; - else if (pe_attacker.playerclass == PC_SCOUT) + if (pe_attacker.playerclass == PC_SCOUT) s_deathstring = "'s mellon was split by "; else if (pe_attacker.playerclass == PC_SNIPER) s_deathstring = " was put on the chop block by "; @@ -2364,6 +3633,12 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage s_deathstring2 = "'s axe\n"; } else s_deathstring = " was ax-murdered by "; + } else if (pf_deathmsg == DMSG_KNIFE) { + s_deathstring = " was knife-murdered by "; + s_deathstring2 = "\n"; + } else if (pf_deathmsg == DMSG_BACKSTAB) { + s_deathstring = " gets knifed from behind by "; + s_deathstring2 = "\n"; } else if (pf_deathmsg == DMSG_SPANNER) { s_deathstring = " was spanner-murdered by "; s_deathstring2 = "\n"; @@ -2463,22 +3738,22 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage } else if (pf_deathmsg == DMSG_ASSAULTCANNON) { s_deathstring = " gets sawn in half by "; s_deathstring2 = "\n"; - } else if (pf_deathmsg == DMSG_BACKSTAB) { - s_deathstring = " gets knifed from behind by "; - s_deathstring2 = "\n"; } else if (pf_deathmsg == DMSG_TRANQ) { s_deathstring = " is put to sleep by "; s_deathstring2 = "\n"; } else if (pf_deathmsg == DMSG_LASERBOLT) { s_deathstring = " gets a hole in his heart from "; s_deathstring2 = "'s railgun\n"; + } else if (pf_deathmsg == DMSG_IMPELLER) { + s_deathstring = " gets pushed around by "; + s_deathstring2 = "\n"; } else if (pf_deathmsg == DMSG_INCENDIARY) { s_deathstring = " gets well done by "; s_deathstring2 = "'s incendiary rocket\n"; - } else if (pf_deathmsg == 38) { + } else if (pf_deathmsg == DMSG_SENTRYGUN_EXPLODE) { s_deathstring = " gets destroyed by "; s_deathstring2 = "'s exploding sentrygun\n"; - } else if (pf_deathmsg == 39) { + } else if (pf_deathmsg == DMSG_DISPENSER_EXPLODE) { s_deathstring = " didn't insert the correct change into "; s_deathstring2 = "'s dispenser.\n"; } @@ -2524,9 +3799,12 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage strcat(pe_attacker.real_owner.netname, s_deathstring2))); } - - } else { - + //only do world-based deaths if not caused by a tf goal with a defined custom death (or broadcast) message + } else if (!(( + pe_attacker.classname == "info_tfgoal" || + pe_attacker.classname == "info_tfgoal_timer" || + pe_attacker.classname == "item_tfgoal" + ) && (pe_attacker.deathtype || pe_attacker.n_b || pe_attacker.netname_broadcast))) { rnum = pe_target.watertype; if (rnum == -3) { @@ -2539,7 +3817,7 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage s_deathstring = " gulped a load of slime\n"; else s_deathstring = " can't exist on slime alone\n"; - } else if (rnum == -5) { + } else if (rnum == CONTENT_LAVA) { if (pe_target.health < -15) s_deathstring = " burst into flames\n"; else if (random() < 0.5) @@ -2590,9 +3868,7 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage void (entity targ, entity attacker) ClientObituary = { local string deathstring; - Sniper_ZoomReset(targ); - - if ((cb_prematch_time + 3) > time) + if (cb_prematch) return; deathstring = GetDeathMessage(targ, attacker, deathmsg); @@ -2686,3 +3962,95 @@ void (entity targ, entity attacker) ClientObituary = { } }; + +float () IsUsingOldImpulses = { + return ((infokey(self, "old_weapon_impulses") == "1") || (infokey(self, "owi") == "1")); +} + +float () IsUsingCFImpulses = { + return FO_GetUserSetting(self, "cf_pyro_impulses", "cfpi", "off"); +} + +float GetMaxWeaponInput() { + return IsUsingOldImpulses() ? 7 : 4; +} + +void () InitReverseCap = { + local entity te = world, tg, tfdet; + local vector originbkp; + + te = world; + tfdet = find(world, classname, "info_tfdetect"); + for (float t = 1; t <= number_of_teams; t++) { + switch (t) + { + case 1: + te = Finditem(tfdet.display_item_status1); + break; + case 2: + te = Finditem(tfdet.display_item_status2); + break; + case 3: + te = Finditem(tfdet.display_item_status3); + break; + case 4: + te = Finditem(tfdet.display_item_status4); + break; + } + + if (te) { + tg = find(world, classname, "info_tfgoal"); + while (tg) { + if (tg.items_allowed == te.goal_no) { + originbkp = te.origin; + + te.oldorigin = tg.origin; + te.origin = tg.origin; + setsize(te, te.goal_min, te.goal_max); + + tg.oldorigin = originbkp; + tg.origin = originbkp; + setsize(tg, tg.mins, tg.maxs); + } + tg = find(tg, classname, "info_tfgoal"); + } + } + } + +} + +// this is to fix hack used to avoid fall damage, it currently breaks pm_airstep +/* +void() SV_RunClientCommand = { + + local float fdmg; + + runstandardplayerphysics(self); + + if (((self.jump_flag < -300) && (self.flags & 512)) && + (self.health > 0)) { + if (self.watertype == -3) { + FO_Sound(self, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM); + } else { + if (self.jump_flag < -650) { + fdmg = 5; + fdmg = (fdmg * (self.jump_flag / 300)) * -1; + if (self.playerclass == 1) { + fdmg = fdmg / 2; + } else { + if (self.playerclass == 6) { + fdmg = fdmg * 1.5; + } + } + fdmg = rint(fdmg); + TF_T_Damage(self, world, world, fdmg, 1, 0); + FO_Sound(self, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM); + self.deathtype = "falling"; + } else { + FO_Sound(self, CHAN_VOICE, "player/land.wav", 1, ATTN_NORM); + } + } + } + self.jump_flag = self.velocity_z; +} +*/ diff --git a/ssqc/combat.qc b/ssqc/combat.qc new file mode 100644 index 000000000..e2e6fd3cd --- /dev/null +++ b/ssqc/combat.qc @@ -0,0 +1,799 @@ +void () T_MissileTouch; +void () info_player_start; +void (entity targ, entity attacker) ClientObituary; +void () ResetPlayers; +void (entity targ, entity attacker) KillSound; +void (entity Goal, entity AP, float addb) DoResults; +float (entity Goal, entity AP) Activated; +float (entity targ, entity attacker, float damage) TeamEqualiseDamage; +float () CountRemainingTeams; +void () CheckRules; + +void () monster_death_use = { + if (self.flags & FL_FLY) + self.flags = self.flags - FL_FLY; + + if (self.flags & FL_SWIM) + self.flags = self.flags - FL_SWIM; + + if (!self.target) + return; + + activator = self.enemy; + SUB_UseTargets(); +}; + +float (entity targ, entity inflictor) CanDamage = { + if (targ.movetype == MOVETYPE_PUSH) { + + traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), + TRUE, self); + if (trace_fraction == 1) + return (TRUE); + if (trace_ent == targ) + return (TRUE); + return (FALSE); + } + + traceline(inflictor.origin, targ.origin, TRUE, self); + if (trace_fraction == 1) + return (TRUE); + traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); + if (trace_fraction == 1) + return (TRUE); + traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); + if (trace_fraction == 1) + return (TRUE); + traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); + if (trace_fraction == 1) + return (TRUE); + traceline(inflictor.origin, targ.origin + '15 -15 0', 1, self); + if (trace_fraction == 1) + return (TRUE); + return (FALSE); +}; + +void () ResetPlayersDelayed = { + if(duel_reset_timer < 1 && duel_reset_timer > 0) { + self.nextthink = time + duel_reset_timer; + duel_reset_timer = 0; + } else if(duel_reset_timer <= 0) { + ResetPlayers(); + dremove(self); + } else { + bprint(PRINT_HIGH, Q"\sReset in \x10", ftos(duel_reset_timer), "\x11\s\n"); + FO_Sound(self, CHAN_AUTO, "buttons/switch04.wav", 1, ATTN_NONE); + //stuffcmd (p, "play buttons/switch04.wav\n"); + duel_reset_timer--; + self.nextthink = time + 1; + } +}; + +void () ResetPlayersWithCountdown = { + local entity reset_timer; + local float remainder; + no_fire_mode = 1; + if(duel_reset_delay) { + reset_timer = find(world, classname, "duel_reset_timer"); + if(!reset_timer) { + remainder = duel_reset_delay % 1; + duel_reset_timer = duel_reset_delay - remainder; + reset_timer = spawn(); + reset_timer.classname = "duel_reset_timer"; + reset_timer.nextthink = time + remainder; + reset_timer.think = ResetPlayersDelayed; + } + } else { + ResetPlayers(); + } + +} + +void (entity targ, entity attacker) Killed = { + local entity oself; + KillSound(targ, attacker); + if (attacker == world && (targ.classname == "building_dispenser" || targ.classname == "building_sentrygun")) + attacker = targ; + + oself = self; + self = targ; + if (self.health < -99) + self.health = -99; + + if ((self.movetype == MOVETYPE_PUSH) || + //make sure unmovable players like those feigning/building are still given an obituary + //(self.movetype == MOVETYPE_NONE && !self.is_building && !self.is_detpacking && !self.is_feigning)) { + (self.movetype == MOVETYPE_NONE && self.classname != "player")) { + + self.th_die(); + self = oself; + return; + } + + self.enemy = attacker; + + if (self.flags & FL_MONSTER) { + + killed_monsters = killed_monsters + 1; + WriteByte(MSG_ALL, SVC_KILLEDMONSTER); + } + + ClientObituary(self, attacker); + self.takedamage = DAMAGE_NO; + self.touch = SUB_Null; + monster_death_use(); + + self.th_die(); + + self = oself; + if(duelmode && targ.classname == "player" && !cb_prematch) { + //Already in no fire mode - implies you're not the first to die + if(no_fire_mode) { + if(CountRemainingTeams() == 0) { + if(!duel_draw_countdown) { + ResetPlayers(); + } + } + } else { + if(CountRemainingTeams() < 2) { + ResetPlayersWithCountdown(); + } + } + } + if(vote_anarchy_mode) { + UnvoteForMap(targ); + } +}; + +void (entity targ, entity inflictor, entity attacker, float damage) HitSound = { + if(nohitsounds) + return; + + if ((attacker.classname == "player") || (attacker.classname == "building_sentrygun") || (attacker.classname == "building_dispenser")) + { + if ((targ.classname == "player") || (targ.classname == "building_sentrygun") || (targ.classname == "building_dispenser")) + { + local entity trueattacker; + + if (attacker.classname == "building_sentrygun") + { // work out correct attacker + trueattacker = attacker.real_owner; + } + else + { + trueattacker = attacker; + } + + local float csqcactive = infokeyf(trueattacker, INFOKEY_P_CSQCACTIVE); + + if(zutmode && csqcactive) + return; + + if (trueattacker == targ) + return; + + local string hitsound; + hitsound = infokey(trueattacker, "hitsound"); + + if (hitsound == "1" || hitsound == "2") + { + local float crit = 0; + + if((inflictor.weapon == DMSG_ROCKETL) && (damage >= 80)) { + crit = 1; + } + if ((inflictor.weapon == DMSG_SSHOTGUN) && (damage >= 40)) { + crit = 1; + } + if ((inflictor.weapon == DMSG_GRENADEL) && (damage >= 85)) { + crit = 1; + } + if ((inflictor.weapon == DMSG_INCENDIARY) && (damage >= 40)) { + crit = 1; + } + + if (targ.playerclass == PC_SPY && IsFeigned(targ)) + return; + + float targteam; + targteam = (targ.undercover_team == 0) ? targ.team_no : targ.undercover_team; + + if (targteam != attacker.team_no) { + if (crit == 1){ + stuffcmd(trueattacker, "play misc/hitsoundcrit.wav\n"); //sounds precached in weapons.qc + } + else { + stuffcmd(trueattacker, "play misc/hitsound.wav\n"); //sounds precached in weapons.qc + } + } + else { + if (hitsound == "2"){ + stuffcmd(trueattacker, "play misc/hitsoundteam.wav\n"); //sounds precached in weapons.qc + } + } + } + } + } +}; + +void (entity targ, entity attacker) KillSound = { + + + if ((attacker.classname == "player") || (attacker.classname == "building_sentrygun") || (attacker.classname == "building_dispenser")) { + if ((targ.classname == "player") || (targ.classname == "building_sentrygun") || (targ.classname == "building_dispenser")) { + + local entity trueattacker; + if (attacker.classname == "building_sentrygun") { + trueattacker = attacker.real_owner; + } + else { + trueattacker = attacker; + } + + local float csqcactive = infokeyf(trueattacker, INFOKEY_P_CSQCACTIVE); + if(csqcactive && zutmode) + return; + + local string killsound = infokey(trueattacker, "killsound"); + + if (killsound == "1" || killsound == "2" || killsound == "3"){ + + if (targ.team_no != trueattacker.team_no) { + stuffcmd(trueattacker, "play misc/killsound.wav\n"); + return; + } + else { + + if (trueattacker == targ) { + if(killsound == "3") { + stuffcmd(trueattacker, "play misc/killsoundteam.wav\n"); + } + return; + } + + if (killsound == "2" || killsound == "3") { + stuffcmd(trueattacker, "play misc/killsoundteam.wav\n"); + } + + } + } + } + } + else { + if (targ.classname == "player") { + local string targkillsound = infokey(targ, "killsound"); + if (targkillsound == "3") { + stuffcmd(targ, "play misc/killsoundteam.wav\n"); + } + } + } +}; + +static inline float valid_hitflag_target(string name) { + return (name == "player" || name == "building_sentrygun" || + name == "building_dispenser"); +} + +void BroadcastHitFlag(entity targ, entity inflictor, entity attacker, + float original_damage, float actual_damage) { + float hitflag = 0; + + if (!valid_hitflag_target(attacker.classname) || + !valid_hitflag_target(targ.classname)) + return; + + if (attacker.classname != "player") + attacker = attacker.real_owner; + + if (!infokeyf(attacker, INFOKEY_P_CSQCACTIVE) || (attacker.tfstate & TFSTATE_FLASHED)) + return; + + // Feigned spies act as corpses unless they're a teammate + if (targ.playerclass == PC_SPY && IsFeigned(targ) && targ.health > 0 && + targ.team_no != attacker.team_no) + return; + + // server allowances -- just baking in hitflag coz im a lazy as + if(nohitsounds) + hitflag |= HITFLAG_NOAUDIO; + if(nohittext) + hitflag |= HITFLAG_NOTEXT; + + // no armour left on target -- other games call it "screaming" "cracked" "one shot" etc + if(targ.armorvalue <= 0 && targ.classname == "player") + hitflag |= HITFLAG_NOARMOUR; + + // friendly + if ((targ.team_no == attacker.team_no) || + (targ.playerclass == PC_SPY && targ.undercover_team == attacker.team_no)) + hitflag |= HITFLAG_FRIENDLY; + + // kill + if(targ.health <= 0) { + hitflag |= HITFLAG_KILLINGBLOW; + + if (targ.playerclass == PC_SPY) { + if (targ.undercover_team != 0 && targ.team_no != attacker.team_no) { + hitflag |= HITFLAG_KILLEDUNDERCOVERSPY; + hitflag &= ~HITFLAG_FRIENDLY; + } + if (IsFeigned(targ)) + hitflag |= HITFLAG_FEIGNEDENEMY; + } + } + + // meatshot -- this is very lazy, should do t_missile etc + if (((inflictor.weapon == DMSG_ROCKETL) && (original_damage >= 80)) || + ((inflictor.weapon == DMSG_SSHOTGUN) && (original_damage >= 40)) || + ((inflictor.weapon == DMSG_GRENADEL) && (original_damage >= 85)) || + ((inflictor.weapon == DMSG_INCENDIARY) && (original_damage >= 40))) + hitflag |= HITFLAG_MEATSHOT; + + // headshot + if (deathmsg == DMSG_SNIPERHEADSHOT) + hitflag |= HITFLAG_HEADSHOT; + + // self + if (attacker == targ) + hitflag |= HITFLAG_SELF; + + // TODO: cuss - done elsewhere + + msg_entity = attacker; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_HITFLAG); + WriteCoord(MSG_MULTICAST, targ.origin.x); + WriteCoord(MSG_MULTICAST, targ.origin.y); + WriteCoord(MSG_MULTICAST, targ.origin.z); + WriteShort(MSG_MULTICAST, actual_damage); + WriteShort(MSG_MULTICAST, original_damage); + WriteShort(MSG_MULTICAST, hitflag); + + // TODO: Supporting spectators (requires no ezquake) + multicast('0 0 0', MULTICAST_ONE_R_NOSPECS); +}; + +void (entity targ, entity inflictor, entity attacker, float damage) T_Damage = { + local vector dir; + local entity oldself, te; + local float save; + local float take; + + if (!targ.takedamage) + return; + + if (attacker.classname == "player") + damage = damage * 0.9; + + if (attacker.classname == "player") { + + if (attacker.super_damage_finished > time) + damage = damage * 4; + + if ((targ.classname != "player") && (targ.classname != "bot")) { + + if (!Activated(targ, attacker)) { + + if (targ.else_goal != 0) { + + te = Findgoal(targ.else_goal); + if (te) + AttemptToActivate(te, attacker, targ); + } + return; + } + } + } + damage_attacker = attacker; + if (teamplay & (TEAMPLAY_LESSSCOREHELP | TEAMPLAY_LESSPLAYERSHELP)) + damage = TeamEqualiseDamage(targ, attacker, damage); + + save = ceil(targ.armortype * damage); + if (save >= targ.armorvalue) { + + save = targ.armorvalue; + targ.armortype = 0; + targ.armorclass = 0; + targ.items = + targ.items - + (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); + } + targ.armorvalue = targ.armorvalue - save; + take = ceil(damage - save); + + if (targ.flags & FL_CLIENT) { + targ.dmg_take = targ.dmg_take + take; + targ.dmg_save = targ.dmg_save + save; + targ.dmg_inflictor = inflictor; + } + if ((inflictor != world) && (targ.movetype == MOVETYPE_WALK)) { + + targ.immune_to_check = time + (damage / 20); + dir = targ.origin - ((inflictor.absmin + inflictor.absmax) * 0.5); + dir = normalize(dir); + if (((damage < 60) && + ((attacker.classname == "player") && + (targ.classname == "player"))) + && (attacker.netname != targ.netname)) + targ.velocity = targ.velocity + dir * damage * 11; + else + targ.velocity = targ.velocity + dir * damage * 8; + + if (((rj > 1) && + ((attacker.classname == "player") && + (targ.classname == "player"))) + && (attacker.netname == targ.netname)) + targ.velocity = (targ.velocity + ((dir * damage) * rj)); + } + if (targ.flags & FL_GODMODE) + return; + + if (targ.invincible_finished >= time) { + if (self.invincible_sound < time) { + + FO_Sound(targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + self.invincible_sound = time + 2; + } + return; + } + if ((attacker.classname == "player") + && ((targ.classname == "player") || + (targ.classname == "building_sentrygun"))) { + + if (((targ.team_no > 0) && (targ.team_no == attacker.team_no)) && + (targ != attacker)) { + + if (teamplay & TEAMPLAY_NOEXPLOSIVE) + return; + else if (teamplay & TEAMPLAY_HALFEXPLOSIVE) + take = take / 2; + } + } + if ((take < 1) && (take != 0)) + take = 1; + + float damh = (take > targ.health) ? targ.health : take; + targ.health = targ.health - take; + + if (targ.armorvalue < 1) { + targ.armorclass = 0; + targ.armorvalue = 0; + } + if ((attacker.classname == "player" || targ.classname == "player") && (take + save > 0)) { + LogEventDamage(attacker, targ, inflictor, take + save, damh + save); + } + if (targ.health <= 0) { + LogEventKill(attacker, targ, inflictor); + Killed(targ, attacker); + return; + } + + oldself = self; + self = targ; + + if (self.th_pain) { + self.th_pain(attacker, take); + if (skill >= 3) + self.pain_finished = time + 5; + } + self = oldself; +}; + +void TF_T_Damage(entity targ, entity inflictor, entity attacker, + float damage, float T_flags, float T_AttackType) { + local vector dir; + local entity oldself; + local entity te; + local float save; + local float take; + local float olddmsg; + local float no_damage; + local float original_damage = damage; + + + if (targ.takedamage == 0) + return; + + if (T_AttackType & TF_TD_NOSOUND) { + targ.health = damage; + return; + } + if (cease_fire) + return; + + float fl = 0; + if (attacker.classname == "player") + fl |= KF_SRC_PLAYER; + if (targ.classname == "player") + fl |= KF_TARG_PLAYER; + if (targ == attacker) + fl |= KF_SELF; + + no_damage = 0; + + if (targ.tfstate & TFSTATE_BURNING) + damage *= PC_PYRO_BURN_DAMAGE_AMP; + + if (fl & KF_SRC_PLAYER) { + damage = damage * 0.9; + + if (attacker.super_damage_finished > time) + damage = damage * 4; + + if ((fl & KF_TARG_PLAYER == 0) + && (targ.classname != "bot") + && (targ.classname != "building_sentrygun") + && (targ.classname != "building_dispenser") + && (targ.classname != "building_teleporter_entrance") + && (targ.classname != "building_teleporter_exit")) { + + if (!Activated(targ, attacker)) { + if (targ.else_goal != 0) { + te = Findgoal(targ.else_goal); + if (te) + AttemptToActivate(te, attacker, targ); + } + return; + } + } + } + + HitSound(targ, inflictor, attacker, damage); + + damage_attacker = attacker; + + if (teamplay & (TEAMPLAY_LESSSCOREHELP | TEAMPLAY_LESSPLAYERSHELP)) + damage = TeamEqualiseDamage(targ, attacker, damage); + + if ((targ.armorclass != 0) && (T_AttackType != 0)) { + if ((targ.armorclass & AT_SAVESHOT) && (T_AttackType & TF_TD_SHOT)) + damage = floor(damage * 0.5); + if ((targ.armorclass & AT_SAVENAIL) && (T_AttackType & TF_TD_NAIL)) + damage = floor(damage * 0.5); + if ((targ.armorclass & AT_SAVEEXPLOSION) && (T_AttackType & TF_TD_EXPLOSION)) + damage = floor(damage * 0.5); + if ((targ.armorclass & AT_SAVEELECTRICITY) && (T_AttackType & TF_TD_ELECTRICITY)) + damage = floor(damage * 0.5); + if ((targ.armorclass & AT_SAVEFIRE) && (T_AttackType & TF_TD_FIRE)) + damage = floor(damage * 0.5); + } + + if (T_flags & TF_TD_IGNOREARMOR) { + take = damage; + save = 0; + } else { + save = ceil(targ.armortype * damage); + if ((fl & KF_SRC_PLAYER) + && (targ.team_no > 0) + && (targ.team_no == attacker.team_no) + && (targ != attacker) + && (T_flags & 2)) { + + if (T_AttackType & TF_TD_EXPLOSION) { + + if (teamplay & 1024) { + save = 0; + } else if (teamplay & 512) { + save = save / 2; + } + + } else if (teamplay & 256) + save = 0; + else if (teamplay & 128) + save = save / 2; + + } + if (save >= targ.armorvalue) { + + save = targ.armorvalue; + targ.armortype = 0; + targ.armorclass = 0; + targ.items = + targ.items - + (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); + + } + targ.armorvalue = targ.armorvalue - save; + take = ceil(damage - save); + + } + if (targ.flags & FL_CLIENT) { + + targ.dmg_take = targ.dmg_take + take; + targ.dmg_save = targ.dmg_save + save; + targ.dmg_inflictor = inflictor; + + } + + if (!(T_AttackType & TF_TD_NOMOMENTUM) && damage) { + if ((inflictor != world) && (targ.movetype == MOVETYPE_WALK) && !(targ.tfstate & TFSTATE_CANT_MOVE)) { + if (deathmsg != DMSG_GREN_NAIL && deathmsg != DMSG_GREN_SHOCK && deathmsg != DMSG_GREN_BURST) { + targ.immune_to_check = time + damage / 20; + + float moment = Class_ScaleMoment(targ.playerclass, damage); + targ.velocity += CalcKnock(inflictor.origin, targ, fl, moment); + Predict_AddFilterEnt(targ, inflictor.filter_ent ?: inflictor); + } + } + } + + if (targ.flags & FL_GODMODE) + return; + + if (targ.invincible_finished >= time) { + if (self.invincible_sound < time) { + FO_Sound(targ, CHAN_ITEM, "items/protect3.wav", 1, 1); + self.invincible_sound = time + 2; + } + return; + } + if ((attacker.classname == "player") + && ((targ.classname == "player") + || (targ.classname == "building_sentrygun") + || (targ.classname == "building_dispenser") + || (targ.classname == "building_teleporter_entrance") + || (targ.classname == "building_teleporter_exit"))) { + + if ((targ.team_no > 0) && (targ.team_no == attacker.team_no) + && (targ != attacker) && (T_flags & TF_TD_NOTTEAM)) { + + if (T_AttackType & TF_TD_EXPLOSION) { + + if (teamplay & TEAMPLAY_NOEXPLOSIVE) + no_damage = TRUE; + else if (teamplay & TEAMPLAY_HALFEXPLOSIVE) + take = take / 2; + + } else if (teamplay & TEAMPLAY_NODIRECT) + no_damage = TRUE; + else if (teamplay & TEAMPLAY_HALFDIRECT) + take = take / 2; + } + } + + if (targ.playerclass == PC_SPY) { + targ.attacked_by = attacker; + targ.feignmsg = deathmsg; + } + + if (T_flags & TF_TD_NOTSELF) + if (targ == attacker) + return; + + if (take < 1) + take = 1; + + take = rint(take); + + float damh = (take > targ.health) ? targ.health : take; + + if (no_damage == 0) + targ.health = (targ.health - take); + + if ((attacker.classname == "player") + && ((targ.classname == "player") + || (targ.classname == "building_sentrygun") + || (targ.classname == "building_dispenser") + || (targ.classname == "building_teleporter_entrance") + || (targ.classname == "building_teleporter_exit"))) { + + if ((targ.team_no > 0) && (targ.team_no == attacker.team_no) + && (targ != attacker) && (T_flags & 2)) { + + olddmsg = deathmsg; + + if (T_AttackType & TF_TD_EXPLOSION) { + + deathmsg = DMSG_TEAMKILL; + if (teamplay & TP_FULLMIRRORAOE) + TF_T_Damage(attacker, world, world, take, 1, 0); + else if (teamplay & TP_HALFMIRRORAOE) + TF_T_Damage(attacker, world, world, take / 2, 1, 0); + + } else { + + deathmsg = DMSG_TEAMKILL; + if (teamplay & TP_FULLMIRRORDIRECT) + TF_T_Damage(attacker, world, world, take, 1, 0); + else if (teamplay & TP_HALFMIRRORDIRECT) + TF_T_Damage(attacker, world, world, take / 2, 1, 0); + } + deathmsg = olddmsg; + } + } + + if (no_damage == TRUE) + return; + + if ((attacker.classname == "player" || targ.classname == "player") && (take + save > 0)) { + LogEventDamage(attacker, targ, inflictor, take + save, damh + save); + } + if (targ.armorvalue < 1) { + targ.armorclass = 0; + targ.armorvalue = 0; + } + + if (zutmode) + BroadcastHitFlag(targ, inflictor, attacker, original_damage, damh); + + if (targ.health <= 0) { + if ((inflictor.classname == "detpack") + && (inflictor.is_disarming) && (inflictor.enemy == targ)) + deathmsg = DMSG_DETPACK_DIS; + LogEventKill(attacker, targ, inflictor); + Killed(targ, attacker); + return; + } + + oldself = self; + self = targ; + + if (self.th_pain) { + self.th_pain(attacker, take); + if (skill >= 3) { + self.pain_finished = time + 5; + } + } + + if (self.feign_next_damage && !IsFeigned(self)) + FO_Spy_Feign(0, /* ignore rate-limit */ 1); + + self = oldself; +}; + +void (entity inflictor, entity attacker, float damage, + entity ignore1, entity ignore2 = __NULL__, entity only = __NULL__) + T_RadiusDamage = { + local float points; + local entity head; + local vector org; + + int count, i; + entity* rlist = findradius_list(inflictor.origin, damage + 40, count); + + for (i = 0; i < count; i ++) { + entity e = rlist[i]; + if ((only && e != only) || e == ignore1 || e == ignore2) + continue; + + if (!e.takedamage) + continue; + + float fl = e == attacker ? KF_SELF : 0; + points = CalcRadiusDamage(inflictor.origin, e, fl, damage); + if (points > 0) { + if (CanDamage(e, inflictor)) { + // shambler takes half damage from all explosions + if (e.classname == "monster_shambler") + T_Damage(e, inflictor, attacker, + points * 0.5); + else + TF_T_Damage(e, inflictor, attacker, points, + TF_TD_NOTTEAM, TF_TD_EXPLOSION); + } + } + } +}; + + +void (entity attacker, float damage) T_BeamDamage = { + local float points; + local entity head; + + head = findradius(attacker.origin, damage + 40); + while (head) { + if (head.takedamage) { + + points = 0.5 * vlen(attacker.origin - head.origin); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + if (CanDamage(head, attacker)) + T_Damage(head, attacker, attacker, points); + } + head = head.chain; + } +}; diff --git a/ssqc/commands.qc b/ssqc/commands.qc new file mode 100644 index 000000000..abf3e6998 --- /dev/null +++ b/ssqc/commands.qc @@ -0,0 +1,1422 @@ +void (entity pe_player) FO_SpecTrackPoint; + +void () UpdateAllAdmins = { + local entity ent = find(world, classname, "player"); + while(ent) { + if(ent.is_admin) { + Update_ServerAdminInfo(ent); + } + ent = find(ent, classname, "player"); + } + ent = find(world, classname, "observer"); + while(ent) { + if(ent.is_admin) { + Update_ServerAdminInfo(ent); + } + ent = find(ent, classname, "observer"); + } +} + +void () RestartMap = { + bprint(PRINT_HIGH, self.netname); + bprint(PRINT_HIGH, " Has restarted the map.\n"); + + localcmd ("changelevel "); + localcmd (mapname); + localcmd ("\n"); +} + +void () ForceStartMatch = { + if (fo_login_required) { + bprint(PRINT_HIGH, "Can't forcestart while logins enabled.\n"); + return; + } + + if (clanbattle == 1 && cb_prematch == 1) { + bprint(PRINT_HIGH, self.netname); + bprint(PRINT_HIGH, " has forced the match start.\n"); + StartTimer(); + } +} + +void (float inp) SetQuadRounds = { + //No idea - for some reason quad rounds have a +1 count to the actual number + rounds = inp + 1; + local string cmd; + cmd = strcat("localinfo rounds ", ftos(inp), "\n"); + localcmd(cmd); + bprint(2, "Quad Round Number changed to ", ftos(inp), "\n"); + UpdateAllAdmins(); +} + + +void () QuadMode = +{ + localcmd ("localinfo clan on\n"); + localcmd ("localinfo quadmode on\n"); + localcmd ("localinfo duelmode off\n"); + localcmd ("localinfo rounds 2\n"); + localcmd ("timelimit 0\n"); + localcmd ("localinfo round_time 10\n"); + localcmd ("localinfo teamfrags on\n"); + localcmd ("localinfo fullteamscore off\n"); + localcmd ("fraglimit 0\n"); + localcmd ("localinfo rd 0\n"); + localcmd ("localinfo votemode 0\n"); + localcmd ("localinfo vote_style 2\n"); + localcmd ("exec fo_quadmode.cfg\n"); + bprint (PRINT_HIGH, "Quad Mode set to on\n"); + bprint (PRINT_HIGH, "Map Restart needed to take effect!\n"); +}; + +void () ClanMode = +{ + localcmd("localinfo clan on\n"); + localcmd ("localinfo rd 0\n"); + localcmd ("localinfo votemode 0\n"); + localcmd ("localinfo vote_style 2\n"); + localcmd ("localinfo rounds 0\n"); + localcmd ("exec fo_clanmode.cfg\n"); + bprint(PRINT_HIGH, "Clan Mode set to on\n"); + bprint(PRINT_HIGH, "Map Restart needed to take effect!\n"); +}; + +void () PubMode = +{ + localcmd ("localinfo clan off\n"); + localcmd ("localinfo quadmode off\n"); + localcmd ("localinfo duelmode off\n"); + localcmd ("localinfo teamfrags off\n"); + localcmd ("localinfo fullteamscore off\n"); + localcmd ("password none\n"); + localcmd ("localinfo rounds 0\n"); + localcmd ("timelimit 20\n"); + localcmd ("fraglimit 0\n"); + localcmd ("localinfo round_time 0\n"); + localcmd ("localinfo rd 0\n"); + localcmd ("localinfo votemode 0\n"); + localcmd ("localinfo vote_style 1\n"); + localcmd ("exec fo_pubmode.cfg\n"); + bprint(PRINT_HIGH, "Pub Mode set to on\n"); + bprint(PRINT_HIGH, "Map Restart needed to take effect!\n"); +} + +void () DuelMode = +{ + localcmd ("localinfo teamfrags off\n"); + localcmd ("localinfo fullteamscore off\n"); + localcmd ("localinfo clan on\n"); + localcmd ("localinfo quadmode off\n"); + localcmd ("localinfo duelmode on\n"); + localcmd ("localinfo sf on\n"); // spawnfull, ie spawn fully stocked + localcmd ("localinfo drd 3.9\n"); // wait before resetting the winner long enough for grens to go off + localcmd ("localinfo rd 9999\n"); // wait before respawning the loser + localcmd ("localinfo dph 1\n"); // print health of duel survivors + localcmd ("localinfo stockfull on\n"); // all packs fully restock + localcmd ("localinfo stock_reload on\n"); // all packs insta-reload + localcmd ("timelimit 0\n"); + localcmd ("fraglimit 30\n"); + localcmd ("localinfo votemode 0\n"); + localcmd ("localinfo vote_style 2\n"); + localcmd ("exec fo_duelmode.cfg\n"); + bprint(PRINT_HIGH, "Duel Mode set to on\n"); + bprint(PRINT_HIGH, "Map Restart needed to take effect!\n"); +} + +void new_balance_mode() { + new_balance ^= 2; + if (new_balance & 2) + localcmd("localinfo new_balance 1\n"); + else + localcmd("localinfo new_balance 0\n"); +} + +void (entity pe) SetEquipmentForPlayer = { + entity oldself = self; + self = pe; + TeamFortress_SetEquipment(); + //self.immune_to_check = time + 10; + //self.undercover_team = 0; + stuffcmd(self, "color "); + string st = TeamFortress_TeamGetColor(self.team_no); + stuffcmd(self, st); + stuffcmd(self, "\n"); + + self = oldself; +} + +void (float cap1, float cap2) CaptainMode = { + captainmode = 1; + bprint(PRINT_HIGH, "\x10\sCaptain Mode\s\x11\s:\s All available players are now Observers\n"); + local string st = 0; + local float userid = 0, csqcactive = 0; + local entity oldself;// = self; + local entity te = find (world, classname, "player"); + while (te != world) { + userid = infokeyf(te, INFOKEY_P_USERID); + csqcactive = infokeyf(te, INFOKEY_P_CSQCACTIVE); + //te.immune_to_check = time + 10; + if(userid == cap1 || userid == cap2) { + te.captain = 9; + te.playerclass = 0; + if(userid == cap1) { + //playerSetTeam(1); + te.team_no = 1; + SetTeamName(te); + TeamFortress_TeamSet_Options(te, 1, TRUE); + + UpdateAllClientsTeamScores(); + + //stuffcmd(self, "color "); + //st = ftos(TeamFortress_TeamGetColor (1) - 1); + //stuffcmd(self, st); + //stuffcmd(self, "\n"); + //SetTeamName(self); + } + if(userid == cap2) { + //playerSetTeam(2); + te.team_no = 2; + SetTeamName(te); + TeamFortress_TeamSet_Options(te, 2, TRUE); + UpdateAllClientsTeamScores(); + + //stuffcmd(self, "color "); + //st = ftos(TeamFortress_TeamGetColor (2) - 1); + //stuffcmd(self, st); + //stuffcmd(self, "\n"); + //SetTeamName(self); + } + bprint(PRINT_HIGH, "\x10\sCaptain Mode\s\x11\s:\s "); + bprint(PRINT_HIGH, te.netname); + bprint(PRINT_HIGH, " \bis the captain for team\b "); + bprint(PRINT_HIGH, TeamToString(te.team_no)); + bprint(PRINT_HIGH, "\b.\b\n"); + if(csqcactive) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(te); + UpdateClientMenu_Captain(te); + } else { + //self = te; + Menu_Close(te); + te.current_menu_type = ADMIN_MENU_TYPE_CAPTAINSELECT; + te.current_menu_page = 1; + //Menu_Admin(); + } + } else { + te.captain = 0; + //self = te; + //playerSetTeam(-1); + te.team_no = 0; + te.playerclass = 0; + oldself = self; + self = te; + TeamFortress_ChangeClass(0); + self = oldself; + SetTeamName(te); + //TeamFortress_TeamSet_Options(te, 0, TRUE); + //stuffcmd(te, "color 0 0\n"); + //SetTeamName(te); + + te.current_menu = 0; + te.impulse = 0; + } + if(csqcactive) { + UpdateClient_Captains(te, cap1, cap2); + } + SetEquipmentForPlayer(te); + te = find (te, classname, "player"); + } + //self = oldself; +} + +void (entity cap1, entity cap2) StopCaptainMode = { + if(captainmode){ + local entity oldself = self; + self = cap1; + TeamFortress_TeamSet(cap1.team_no); + self = cap2; + TeamFortress_TeamSet(cap2.team_no); + captainmode = 0; + self = oldself; + } +} + +void (entity cap, float pick) CaptainPick = { + if(!cap.captain) { + sprint(self, PRINT_HIGH, "You are not a captain, sorry\n"); + return; + } + local float countleft = 0, userid = 0; + local entity picked = world, othercap = world, oldself; + local entity te = find (world, classname, "player"); + while (te != world) { + userid = infokeyf(te, INFOKEY_P_USERID); + if(userid == pick) { + picked = te; + } + if(!te.team_no) { + countleft++; + } + if(te != cap && te.captain) { + othercap = te; + } + te = find (te, classname, "player"); + } + if(picked != world) { + if(picked == cap) { + sprint(self, PRINT_HIGH, "You don't have to pick yourself, it's implied...\n"); + return; + } + if(picked.captain) { + sprint(self, PRINT_HIGH, picked.netname ," is a captain, thus unpickable!\n"); + return; + } + if(picked.team_no) { + sprint(self, PRINT_HIGH, picked.netname ," has already been picked!\n"); + return; + } + bprint(PRINT_HIGH, "\x10\bCaptain Mode\b\x11\b:\b "); + bprint(PRINT_HIGH, picked.netname); + bprint(PRINT_HIGH, " \bcalled\b "); + bprint(PRINT_HIGH, cap.netname); + bprint(PRINT_HIGH, " \bfor team\b "); + bprint(PRINT_HIGH, TeamToString(cap.team_no)); + bprint(PRINT_HIGH, "\b.\b\n"); + oldself = self; + self = picked; + TeamFortress_TeamSet(cap.team_no); + self = oldself; + } else { + sprint(self, PRINT_HIGH, "Sorry, couldn't pick ", picked.netname ,"\n"); + } + if(!countleft) { + bprint(PRINT_HIGH, "\x10\sCaptain Mode\s\x11\s:\s \sTeams are set, let's start the game!\s\n"); + StopCaptainMode(cap, othercap); + } +} + +void (entity pl) PrintWho = { + local string msg; + msg = strzone(string_null); + msg = strcat(msg, infokey(pl,"name")); + msg = strcat(msg, " - "); + if (pl.login != string_null) + msg = strcat(msg, pl.login); + else + msg = strcat(msg, "NOT LOGGED IN"); + if (pl.is_admin) { + msg = strcat(msg, " (admin)"); + } + msg = strcat(msg, "\n"); + sprint(self, PRINT_HIGH, msg); + strunzone(msg); +} + +void (entity pov, string command, string color) UpdateTeamColor = { + forceinfokey(pov, command, color); + + local entity target = find(world, classname, "player"); + while (target != world) { + SetSkinInfoFor(pov, target); + target = find(target, classname, "player"); + } +} + +float (string arg1, string arg2, string arg3, string arg4) ParseCmds = { + local float arg_num = 0, processedCmd, inp; + local string tmp; + local float farg2, farg3, farg4; + local entity ent, pl; + processedCmd = FALSE; + + if (arg1) + arg_num = 1; + if (arg2) + arg_num = 2; + + switch (arg1) + { + case "impulse": + if (arg_num == 2) { + self.impulse = stof(arg2); + } + break; + case "progsversion": + // yy.mm.dd.incremented vers + sprint(self, PRINT_HIGH, "sv progs version: 22.02.13.1\n"); + break; + case "adminpwd": + processedCmd = TRUE; + if (arg_num == 2) { + Admin_Check(arg2); + if (self.is_admin) { + Admin_Aliases(); + } + } + if (arg_num == 1) { + sprint(self, PRINT_HIGH, "usage: cmd adminpwd password, where password is the admin password\n"); + sprint(self, PRINT_HIGH, "\n"); + } + break; + case "setinfo": + if (arg2) { + switch(arg2) { + case "team1color": + case "team2color": + case "team3color": + case "team4color": + UpdateTeamColor(self, arg2, arg3); + processedCmd = TRUE; + break; + } + } + break; + case "changeteam": + processedCmd = TRUE; + if (self.classname == "observer") { +#if 0 + sprint(self, PRINT_HIGH, "You can't join a team while spectating! Join the game first.\n"); + break; +#else + clientcommand(self, "join"); +#endif + } + if(teamplay == 0) { + sprint(self, PRINT_HIGH, "You can't join a team when teamplay is disabled!\n"); + break; + } + if(number_of_teams == 1) { + if(self.team_no == 0) { + TeamFortress_TeamSet(1); + } else { + sprint(self, PRINT_HIGH, "Only one team is available on this map.\n"); + } + break; + } + + if (arg2) { + local float final_round = (rounds == 1); + + switch(arg2) { + case "attack": + self.all_time = ALL_TIME_ATTACK; + arg2 = final_round ? "2" : "1"; + sprint(self, PRINT_HIGH, "Always on the attacking team\n"); + break; + case "defence": + self.all_time = ALL_TIME_DEFENCE; + arg2 = final_round ? "1" : "2"; + sprint(self, PRINT_HIGH, "Always on the defending team\n"); + break; + default: + self.all_time = ALL_TIME_COLOUR; + } + + if(arg2 == "auto") { + if(self.team_no == 0) { + TeamFortress_TeamPutPlayerInTeam(); + //UpdateClientMenu_Class(self); + Menu_Class(0); + break; + } else { + sprint(self, PRINT_HIGH, "You can't auto team when you're already playing!\n"); + break; + } + } + + if(arg2 == "1" || arg2 == "2" || arg2 == "3" || arg2 == "4") { + float newteam = stof(arg2), oldteam = self.team_no; + if(number_of_teams == 0 || newteam <= number_of_teams) { + TeamFortress_TeamSet(newteam); + if(!oldteam) { + //UpdateClientMenu_Class(self); + Menu_Class(0); + } + break; + } + } + sprint(self, PRINT_HIGH, "Invalid team choice. Please use values 1-",number_of_teams?ftos(number_of_teams):"4",self.team_no?"":" or 'auto'",".\n"); + } else { + //UpdateClientMenu_Team(self); + Menu_Team(0); + } + + break; + case "changeclass": + processedCmd = TRUE; + if(self.classname == "observer") { + sprint(self, PRINT_HIGH, "You can't pick a class while spectating! Join the game first.\n"); + break; + } + if (arg2) { + float newclass = stof(arg2); + + if (!newclass) { + sprint(self, PRINT_HIGH, "Invalid class choice. Please use values 1-10.\n"); + break; + } + + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + + // close menu if selected class is current class + if (newclass == self.nextpc) { + sprint(self, PRINT_HIGH, "You're already going to play that class!\n"); + break; + } + + // keep showing menu if class is invalid + if (newclass < 1 || newclass > 10 || (!IsLegalClass(newclass) && !override_mapclasses) || (self.playerclass != newclass && CF_ClassIsRestricted(self.team_no, newclass))) { + sprint(self, PRINT_HIGH, "Invalid class for this team!\n"); + Menu_Class(0); + break; + } + + // don't try to change class if class is forbidden + if ((!IsLegalClass(newclass) && !override_mapclasses) || (self.playerclass != newclass && CF_GetClassRestriction(self.team_no, newclass) == -1)) { + sprint(self, PRINT_HIGH, "Forbidden class for this team!\n"); + break; + } + + TeamFortress_ChangeClass(newclass); + } else { + Menu_Class(0); + } + break; + + //These only work when alive + case "dropammo": + processedCmd = TRUE; + if(self.health <= 0) { + sprint(self, PRINT_HIGH, "You've already dropped all your ammo when you died!\n"); + break; + } + if (arg2) { + if(arg2 == "1" || arg2 == "2" || arg2 == "3" || arg2 == "4") { + inp = stof(arg2); + farg3 = stof(arg3); + if(farg3 < 0) { + farg3 = 0; + } + TeamFortress_DropAmmo(inp, farg3); + //Menu_Drop(); + break; + } + sprint(self, PRINT_HIGH, "Invalid choice. Please use values 1-4.\n"); + } + Menu_Drop(); + break; + + case "disguise": + processedCmd = TRUE; + if(self.playerclass != PC_SPY) { + sprint(self, PRINT_HIGH, "Only spies can disguise!\n"); + break; + } + if(self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't spy while dead!\n"); + break; + } + if (arg2) { + if(arg2 == "next") { + FO_Spy_DisguiseCycle(self, 1); + break; + } + if(arg2 == "prev") { + FO_Spy_DisguiseCycle(self, -1); + break; + } + if(arg2 == "last") { + Menu_Spy_Input(2); + break; + } + if(arg2 == "none") { + Menu_Spy_Input(4); + break; + } + if(arg2 == "skin") { + if(arg3) { + Menu_Spy_Skin_Input(stof(arg3)); + } else { + Menu_Spy_Skin(); + } + break; + } + if(arg2 == "team") { + if(arg3) { + //Menu_Spy_Color_Input(stof(arg3)); + farg3 = stof(arg3); + if (farg3 > 0 && farg3 <= number_of_teams) + CF_Spy_ChangeColor(self, farg3, TRUE); + else + Menu_Spy_Input(1); + break; + } else { + Menu_Spy_Color(); + break; + } + } + sprint(self, PRINT_HIGH, "Invalid choice. Please use values 'none', 'last', 'skin #' or 'team #'.\n"); + } else { + Menu_Spy_Input(1); + } + break; + case "menu": + processedCmd = TRUE; + if(self.health <= 0 && self.playerclass != PC_SCOUT) { + sprint(self, PRINT_HIGH, "Menus are for the living!\n"); + break; + } + switch(self.playerclass) { + case PC_SCOUT: + Menu_Scout(); + break; + case PC_DEMOMAN: + if(self.is_detpacking) { + Menu_Demoman_Cancel(); + break; + } + Menu_Demoman(); + break; + case PC_SPY: + Menu_Spy(self); + break; + case PC_ENGINEER: + Menu_Engineer(self); + break; + default: + sprint(self, PRINT_HIGH, "This class doesn't have a menu.\n"); + break; + } + break; + case "autoscan": + processedCmd = TRUE; + if(self.playerclass == PC_SCOUT) { + ScannerSwitch(); + } + break; + case "scansound": + processedCmd = TRUE; + if(self.playerclass == PC_SCOUT) { + //self.impulse = TF_SCAN_SOUND; + sprint(self, PRINT_HIGH, "Scanner sound: "); + if (self.tf_items_flags & 4) { + self.tf_items_flags = self.tf_items_flags - 4; + sprint(self, PRINT_HIGH, "off\n"); + } else { + self.tf_items_flags = self.tf_items_flags | 4; + sprint(self, PRINT_HIGH, "on\n"); + } + } + break; + case "scanf": + processedCmd = TRUE; + if(self.playerclass == PC_SCOUT) { + //self.impulse = TF_SCAN_FRIENDLY; + sprint(self, PRINT_HIGH, "Scanning for: "); + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { + self.tf_items_flags = self.tf_items_flags - NIT_SCANNER_FRIENDLY; + if (self.tf_items_flags & NIT_SCANNER_ENEMY) { + sprint(self, PRINT_HIGH, "Enemies only\n"); + } else { + sprint(self, PRINT_HIGH, "Nothing\n"); + } + } else { + self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_FRIENDLY; + if (self.tf_items_flags & NIT_SCANNER_ENEMY) { + sprint(self, PRINT_HIGH, "Friendlies and enemies\n"); + } else { + sprint(self, PRINT_HIGH, "Friendlies only\n"); + } + } + Status_Refresh(self); + } + break; + case "scane": + processedCmd = TRUE; + if(self.playerclass == PC_SCOUT) { + //self.impulse = TF_SCAN_ENEMY; + sprint(self, PRINT_HIGH, "Scanning for: "); + if (self.tf_items_flags & NIT_SCANNER_ENEMY) { + self.tf_items_flags = self.tf_items_flags - NIT_SCANNER_ENEMY; + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { + sprint(self, PRINT_HIGH, "Friendlies only\n"); + } else { + sprint(self, PRINT_HIGH, "Nothing\n"); + } + } else { + self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_ENEMY; + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { + sprint(self, PRINT_HIGH, "Friendlies and enemies\n"); + } else { + sprint(self, PRINT_HIGH, "Enemies only\n"); + } + Status_Refresh(self); + } + } + break; + case "detpack": + processedCmd = TRUE; + if(self.playerclass != PC_DEMOMAN) { + sprint(self, PRINT_HIGH, "Only demomen are entrusted with such power!\n"); + break; + } + if(self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't set detpacks while dead.\n"); + break; + } + if (arg2) { + if(arg2 == "cancel") { + TeamFortress_DetpackStop(); + break; + } + if(self.is_detpacking) { + TeamFortress_DetpackStop(); +// Menu_Demoman_Cancel(); + break; + } + local float farg2 = stof(arg2); + if(farg2) { + TeamFortress_SetDetpack(farg2); + break; + } + sprint(self, PRINT_HIGH, "Invalid choice. Please use integer values 5+.\n"); + } else { + if(self.is_detpacking) { + Menu_Demoman_Cancel(); + break; + } + Menu_Demoman(); + } + break; + case "build": + processedCmd = TRUE; + if(self.playerclass != PC_ENGINEER) { + sprint(self, PRINT_HIGH, "You are not qualified to build anything!\n"); + break; + } + if(self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't build while dead.\n"); + break; + } + if (arg2) { + if(arg2 == "cancel") { + TeamFortress_EngineerBuildStop(); + break; + } + if(arg2 == "sentry") { + if(self.is_building && !engineer_move) { + TeamFortress_EngineerBuildStop(); + break; + } + + TeamFortress_BuildSentry(stof(arg3)); + break; + } + if(arg2 == "dispenser") { + if(self.is_building && !engineer_move) { + TeamFortress_EngineerBuildStop(); + break; + } + Menu_Engineer_Input(2); + break; + } + if(arg2 == "destroy" && arg3) { + if(arg3 == "sentry") { + Menu_Engineer_Input(3); + break; + } + if(arg3 == "dispenser") { + Menu_Engineer_Input(4); + break; + } + } + sprint(self, PRINT_HIGH, "Invalid choice.\n"); + } else { + Menu_Engineer(self); + } + break; + case "sentry": + processedCmd = TRUE; + if(self.playerclass != PC_ENGINEER) { + sprint(self, PRINT_HIGH, "Only engineers can do that!\n"); + break; + } + + if(self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't maintain while dead.\n"); + break; + } + + //find sentry first + ent = findradius(self.origin, ENG_BUILDING_MAINT_DISTANCE); + while (ent) { + if (ent.classname == "building_sentrygun") { + if (ent.real_owner == self) + break; + } + ent = ent.chain; + } + + if (!ent) { + sprint(self, PRINT_HIGH, "No sentry in range\n"); + break; + } + + if (arg2 && arg3) { + if(arg2 == "rotate") { + farg3 = stof(arg3); + if (!ent.real_owner.has_sentry || ent.real_owner != self + || self.classname != "player" || ent == world) { + sprint(self, PRINT_HIGH, "Sentry detection issue!\n"); + break; + } + ent.angles_y -= farg3; + ent.waitmin = rint(ent.angles_y - 50); + ent.waitmin = anglemod(ent.waitmin); + ent.waitmax = rint(ent.angles_y + 50); + ent.waitmax = anglemod(ent.waitmax); + if (ent.waitmin > ent.waitmax) { + ent.waitmin = ent.waitmax; + ent.waitmax = anglemod(ent.angles_y - 50); + } + ent.nextthink = time + 0.5; + break; + } else if (arg2 == "angle") { + farg3 = stof(arg3); + if (!ent.real_owner.has_sentry || ent.real_owner != self + || self.classname != "player" || ent == world) { + sprint(self, PRINT_HIGH, "Sentry detection issue!\n"); + break; + } + ent.angles_y = farg3; + ent.waitmin = rint(ent.angles_y - 50); + ent.waitmin = anglemod(ent.waitmin); + ent.waitmax = rint(ent.angles_y + 50); + ent.waitmax = anglemod(ent.waitmax); + if (ent.waitmin > ent.waitmax) { + ent.waitmin = ent.waitmax; + ent.waitmax = anglemod(ent.angles_y - 50); + } + ent.nextthink = time + 0.5; + break; + } + sprint(self, PRINT_HIGH, "Invalid choice.\n"); + } else { + Menu_EngineerFix_SentryGun_Rotate(); + } + break; + case "dispenser": + processedCmd = TRUE; + if(self.playerclass != PC_ENGINEER) { + sprint(self, PRINT_HIGH, "Only engineers can do that!\n"); + break; + } + if(self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't dispense while dead.\n"); + break; + } + if (arg2) { + if(arg2 == "ammo") { + Menu_EngineerFix_Dispenser_Input(1); + break; + } + if(arg2 == "armor") { + Menu_EngineerFix_Dispenser_Input(2); + break; + } + if(arg2 == "repair") { + Menu_EngineerFix_Dispenser_Input(3); + break; + } + if(arg2 == "withdraw" && arg3) { + if(arg3 == "ammo") { + Menu_Dispenser_Input(1); + break; + } + if(arg3 == "armor") { + Menu_Dispenser_Input(2); + break; + } + } + } + sprint(self, PRINT_HIGH, "Invalid choice. Choices are [ammo|armo[u]r|repair|withdraw ammo|withdraw armo[u]r].\n"); + break; + case "votemap": + processedCmd = TRUE; + if(vote_style) { + if(arg2) { + VoteForMap(self, arg2); + } + } else { + switch(arg2) { + case vote1_map: + Vote_Input(1); + break; + case vote2_map: + Vote_Input(2); + break; + case vote3_map: + Vote_Input(3); + break; + case vote4_map: + Vote_Input(4); + break; + case vote5_map: + Vote_Input(5); + break; + default: + Vote_Menu(self); + break; + } + } + break; + case "showvotes": + processedCmd = TRUE; + PrintVoting(self); + break; + case "listmaps": + case "maplist": + processedCmd = TRUE; + ListVoteMaps(self); + break; + case "break": + processedCmd = TRUE; + + if (disable_voting) { + // Do nothing + } else if (self.vote_map) { + UnvoteForMap(self); + } else { + if(votemode) { + sprint(self, PRINT_HIGH, "You haven't voted yet!\n"); + } else { + VoteToEndMap(self); + } + } + break; + case "voteyes": + processedCmd = TRUE; + VoteYes(self); + break; + case "captainpick": + processedCmd = TRUE; + if(arg2) { + farg2 = stof(arg2); + CaptainPick(self, farg2); + } + break; + case "mapmenu": + processedCmd = TRUE; + self.current_menu_page = 1; + Vote_Menu_Map(0); + break; + case "reportmodel": + processedCmd = TRUE; + sprint(self, PRINT_HIGH, "Model: ", self.model, " index: ", ftos(self.modelindex), "\n"); + break; + case "reportloc": + processedCmd = TRUE; + sprint(self, PRINT_HIGH, "Origin: ", vtos(self.origin), " OldOrigin: ", vtos(self.oldorigin)); + sprint(self, PRINT_HIGH, " view_ofs: ", vtos(self.view_ofs), "\n"); + break; + case "tracktarget": + processedCmd = TRUE; + FO_SpecTrackPoint(self); + break; + case "id": + processedCmd = TRUE; + CF_Identify(self, 0); + break; + case "showroles": + processedCmd = TRUE; + PrintRoleStatus(self); + PrintRole(self, &Role_None); + PrintRole(self, &Role_Attack); + PrintRole(self, &Role_Defence); + PrintTeamRoles(self); + break; + case "help": + case "list": + processedCmd = TRUE; + sprint(self, PRINT_HIGH, "\bUser Commands list:\b\n"); + sprint(self, PRINT_HIGH, "cmd adminpwd \n"); + sprint(self, PRINT_HIGH, "cmd mapmenu\b: menu for changing maps\b\n"); + if(votemode) { + sprint(self, PRINT_HIGH, "cmd listmaps\b: list votable maps\b\n"); + sprint(self, PRINT_HIGH, "cmd votemap \b: vote for the specified map\b\n"); + sprint(self, PRINT_HIGH, "cmd showvotes\b: List current voting summary\b\n"); + sprint(self, PRINT_HIGH, "cmd break\b: Cancel current vote\b\n"); + } else { + sprint(self, PRINT_HIGH, "cmd changeteam [#|auto|attack|defence]\n"); + sprint(self, PRINT_HIGH, "cmd changeclass [#]\n"); + sprint(self, PRINT_HIGH, "cmd dropammo [1-4] [amount]\n"); + sprint(self, PRINT_HIGH, "cmd disguise [last|none|skin #|team #]\n"); + sprint(self, PRINT_HIGH, "cmd menu\n"); + sprint(self, PRINT_HIGH, "cmd autoscan\n"); + sprint(self, PRINT_HIGH, "cmd scansound\n"); + sprint(self, PRINT_HIGH, "cmd scanf\n"); + sprint(self, PRINT_HIGH, "cmd scane\n"); + sprint(self, PRINT_HIGH, "cmd detpack [#|cancel]\n"); + sprint(self, PRINT_HIGH, "cmd build [cancel|sentry|dispenser|destroy {sentry|dispenser}]\n"); + sprint(self, PRINT_HIGH, "cmd sentry [rotate #]\n"); + sprint(self, PRINT_HIGH, "cmd dispenser [ammo|armor|repair|withdraw {ammo|armor}]\n"); + sprint(self, PRINT_HIGH, "cmd captainpick #\n"); + sprint(self, PRINT_HIGH, "cmd break\b: end current map\b\n"); + } + sprint(self, PRINT_HIGH, "cmd help || list (this command)\n"); + sprint(self, PRINT_HIGH, "\n"); + break; + case "login": + processedCmd = TRUE; + if (arg_num == 2) { + if (self.login != string_null) { + sprint (self, PRINT_HIGH, "You are already logged in\n"); + } + else { + performLogin(self,arg2,arg3); + } + } + else { + sprint (self, PRINT_HIGH, "Missing username/password\n"); + } + break; + case "login-silent" : + processedCmd = TRUE; + if (arg3 != string_null) { + performLogin(self,arg2,arg3); + } + break; + case "fo-login": + processedCmd = TRUE; + if (self.fo_login != string_null) { + sprint (self, PRINT_HIGH, "You are logged in\n"); + } else { + performFoLogin(self, arg2); + } + break; + case "fo-login-silent": + processedCmd = TRUE; + if (self.fo_login == string_null) + performFoLogin(self, arg2); + break; + case "who": + processedCmd = TRUE; + pl = find (world, classname, "player"); + while (pl) { + PrintWho(pl); + pl = find (pl, classname, "player"); + } + pl = find (world, classname, "observer"); + while (pl) { + PrintWho(pl); + pl = find (pl, classname, "observer"); + } + break; + } + + if (self.is_admin) + { + switch (arg1) + { + case "adminmenu": + processedCmd = TRUE; + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + Menu_Admin(); + break; + case "timelimit": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("timelimit "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + tmp = infokey(world, arg1); + sprint(self, PRINT_HIGH, "timelimit is "); + sprint(self, PRINT_HIGH, "\""); + sprint(self, PRINT_HIGH, tmp); + sprint(self, PRINT_HIGH, "\"\n"); + } + break; + case "prematch": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("prematch "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + tmp = infokey(world, arg1); + sprint(self, PRINT_HIGH, "prematch is "); + sprint(self, PRINT_HIGH, "\""); + sprint(self, PRINT_HIGH, tmp); + sprint(self, PRINT_HIGH, "\"\n"); + } + break; + case "fraglimit": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("fraglimit "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + tmp = infokey(world, arg1); + sprint(self, PRINT_HIGH, "fraglimit is "); + sprint(self, PRINT_HIGH, "\""); + sprint(self, PRINT_HIGH, tmp); + sprint(self, PRINT_HIGH, "\"\n"); + } + break; + case "teamplay": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("teamplay "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + tmp = infokey(world, arg1); + sprint(self, PRINT_HIGH, "teamplay is "); + sprint(self, PRINT_HIGH, "\""); + sprint(self, PRINT_HIGH, tmp); + sprint(self, PRINT_HIGH, "\"\n"); + } + break; + case "password": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("password "); + if (arg2 == "none") { + bprint(PRINT_HIGH, Q"\n\sServer Password removed!\s\n\n"); + localcmd("\"\""); + } + else { + bprint(PRINT_HIGH, Q"\n\sServer Password changed to \"", arg2, "\"\s\n\n"); + localcmd(arg2); + } + localcmd("\n"); + } + if (arg_num == 1) { + sprint(self, PRINT_HIGH, "usage: cmd password pwd\n"); + sprint(self, PRINT_HIGH, "\n"); + } + break; + case "record": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("record "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + sprint(self, PRINT_HIGH, "usage: cmd record demo, where demo is the demo name\n"); + sprint(self, PRINT_HIGH, "\n"); + } + break; + case "easyrecord": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("easyrecord "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + localcmd("easyrecord\n"); + } + break; + case "autorecord": + processedCmd = TRUE; + if (arg_num == 2) { + localcmd("localinfo demo_auto_left "); + localcmd(arg2); + localcmd("\n"); + } + if (arg_num == 1) { + tmp = infokey(world, "demo_auto_left"); + if (stof(tmp) > 0) { + sprint (self, PRINT_HIGH, "Auto-Recording off\n"); + localcmd ("localinfo demo_auto_left 0\n"); + } + else { + sprint(self, PRINT_HIGH, "Auto-Recording the next match\n"); + localcmd("localinfo demo_auto_left 1\n"); + } + } + break; + case "cancel": + localcmd ("cancel\n"); + processedCmd = TRUE; + break; + case "stop": + localcmd ("stop\n"); + processedCmd = TRUE; + break; + case "kick": + processedCmd = TRUE; + if(arg2) { + //make sure it's a number so there's no funny business + farg2 = stof(arg2); + if(farg2) { + localcmd("kick ", ftos(farg2), "\n"); + } + } else { + Admin_CycleDeal(); + } + break; + case "ban": + processedCmd = TRUE; + if(arg2) { + //make sure it's a number so there's no funny business + farg2 = stof(arg2); + if(farg2) { + ent = find(world, classname, "player"); + while(ent) { + if(infokeyf(ent, INFOKEY_P_USERID) == farg2) { + bprint(PRINT_HIGH, ent.netname, " was banned by ", self.netname, ".\n"); + localcmd("addip "); + localcmd(infokey(ent, INFOKEY_P_IP)); + localcmd("\n"); + break; + } + ent = find(ent, classname, "player"); + } + localcmd("kick ", ftos(farg2), "\n"); + } + } else { + sprint(self, PRINT_HIGH, "ban requires a userid parameter\n"); + } + break; + case "forcespec": + processedCmd = TRUE; + if(arg2) { + //make sure it's a number so there's no funny business + farg2 = stof(arg2); + if(farg2) { + ent = find(world, classname, "player"); + while(ent) { + if(infokeyf(ent, INFOKEY_P_USERID) == farg2) { + bprint(PRINT_HIGH, ent.netname, " was made spectator by ", self.netname, ". There's probably a good reason for this.\n"); + clientcommand(ent, "observe"); + break; + } + ent = find(ent, classname, "player"); + } + } + } else { + sprint(self, PRINT_HIGH, "forcespec requires a userid parameter\n"); + } + break; + case "ceasefire": + if (ceasefire_type) + Admin_Pause(); + else + Admin_CeaseFire(); + processedCmd = TRUE; + break; + case "updateserver": + Admin_UpdateServer(); + processedCmd = TRUE; + break; + case "map": + processedCmd = TRUE; + if (arg_num == 2) { + bprint(PRINT_HIGH, self.netname); + bprint(PRINT_HIGH, " has changed the map to "); + bprint(PRINT_HIGH, arg2); + bprint(PRINT_HIGH, "\n"); + nextmap = arg2; + if (!clan_scores_dumped) { + if (quadmode) { + rounds = 1; + QuadRoundOver(); + } + DumpClanScores(); + MapEndSequence(); + clan_scores_dumped = 1; + } + localcmd("changelevel "); + localcmd(arg2); + localcmd("\n"); + } + else if (arg_num == 1) { + sprint (self, PRINT_HIGH, "usage: cmd map mapname, where mapname is the map name you wish to change to\n"); + sprint (self, PRINT_HIGH, "\n"); + } + break; + case "restart": + processedCmd = TRUE; + RestartMap(); + break; + case "randomise": + processedCmd = TRUE; + randomizeTeams(); + break; + case "adminrefresh": + processedCmd = TRUE; + Update_ServerAdminInfo(self); + break; + case "pubmode": + processedCmd = TRUE; + PubMode(); + break; + case "clanmode": + processedCmd = TRUE; + ClanMode(); + break; + case "quadmode": + processedCmd = TRUE; + QuadMode(); + break; + case "duelmode": + processedCmd = TRUE; + DuelMode(); + break; + case "new_balance": + processedCmd = TRUE; + new_balance_mode(); + break; + case "forcestart": + processedCmd = TRUE; + ForceStartMatch(); + break; + case "rounds": + processedCmd = TRUE; + if(arg2) { + //make sure it's a number so there's no funny business + farg2 = stof(arg2); + if(farg2) { + SetQuadRounds(farg2); + } + } + break; + case "roundtime": + processedCmd = TRUE; + if(arg2) { + //make sure it's a number so there's no funny business + farg2 = stof(arg2); + if(farg2) { + localcmd ("localinfo round_time ",arg2,"\n"); + UpdateAllAdmins(); + } + } + break; + case "fologinrequired": + processedCmd = TRUE; + if(arg2) { + localcmd ("localinfo fologinrequired ",arg2,"\n"); + if (stof(arg2)) { + bprint(PRINT_HIGH, "Logins will be required. Matches can be reported\n"); + } else { + bprint(PRINT_HIGH, "Allow unregistered players. Matches won't be reported\n"); + } + bprint(PRINT_HIGH, "Map Restart needed to take effect!\n"); + UpdateAllAdmins(); + } + break; + case "fo_matchrated": + processedCmd = TRUE; + if(arg2) { + localcmd ("localinfo fo_matchrated ",arg2,"\n"); + + switch(stof(arg2)) { + case 0: + bprint(PRINT_HIGH, "Next match will not be rated\n"); + break; + case 1: + bprint(PRINT_HIGH, "Next match will be rated\n"); + break; + case 2: + bprint(PRINT_HIGH, "Next match will only be rated if more there are than 2 players on each team\n"); + } + UpdateAllAdmins(); + } + break; + case "play_to_completion": + if(arg2 != "0" && arg2 != "1") + break; + + processedCmd = TRUE; + localcmd ("localinfo play_to_completion ",arg2,"\n"); + UpdateAllAdmins(); + break; + case "captainmode": + processedCmd = TRUE; + if(arg2 && arg3) { + farg2 = stof(arg2); + farg3 = stof(arg3); + if(farg2 && farg3) { + CaptainMode(farg2, farg3); + } else { + sprint(self, PRINT_HIGH, "Captain mode requires 2 userids\n"); + } + break; + } + if(arg2 == "stop") { + local entity cap1 = findfloat(world, captain, 1); + local entity cap2 = findfloat(cap1, captain, 1); + StopCaptainMode(cap1, cap2); + break; + } + Menu_Admin(); + break; + case "forcebreak": + processedCmd = TRUE; + bprint(PRINT_HIGH, "\bMap ended by admin\b ",self.netname,".\n"); + vote_result = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + votemode = 2; + execute_changelevel(); + break; + case "help": + case "list": + processedCmd = TRUE; + sprint(self, PRINT_HIGH, "\bAdmin Commands list:\b\n"); + sprint(self, PRINT_HIGH, "cmd timelimit\n"); + sprint(self, PRINT_HIGH, "cmd prematch [#]\n"); + sprint(self, PRINT_HIGH, "cmd fraglimit [#]\n"); + sprint(self, PRINT_HIGH, "cmd teamplay [#]\n"); + sprint(self, PRINT_HIGH, "cmd password {|none}\n"); + sprint(self, PRINT_HIGH, "cmd map \n"); + sprint(self, PRINT_HIGH, "cmd record \n"); + sprint(self, PRINT_HIGH, "cmd easyrecord []\n"); + sprint(self, PRINT_HIGH, "cmd autorecord [0|1]\n"); + sprint(self, PRINT_HIGH, "cmd cancel\n"); + sprint(self, PRINT_HIGH, "cmd stop\n"); + sprint(self, PRINT_HIGH, "cmd kick [userid]\n"); + sprint(self, PRINT_HIGH, "cmd ban \n"); + sprint(self, PRINT_HIGH, "cmd forcespec \n"); + sprint(self, PRINT_HIGH, "cmd ceasefire\n"); + sprint(self, PRINT_HIGH, "cmd restart\n"); + sprint(self, PRINT_HIGH, "cmd randomise\n"); + sprint(self, PRINT_HIGH, "cmd pubmode\n"); + sprint(self, PRINT_HIGH, "cmd clanmode\n"); + sprint(self, PRINT_HIGH, "cmd quaddmode\n"); + sprint(self, PRINT_HIGH, "cmd duelmode\n"); + sprint(self, PRINT_HIGH, "cmd forcestart\n"); + sprint(self, PRINT_HIGH, "cmd rounds #\b: Set number of Quad rounds\b\n"); + sprint(self, PRINT_HIGH, "cmd roundtime #\b: Set Quad round time\b\n"); + sprint(self, PRINT_HIGH, "cmd captainmode { | stop} \n"); + sprint(self, PRINT_HIGH, "cmd forcebreak\b: End current map\b\n"); + sprint(self, PRINT_HIGH, "\n"); + break; + } + } + + return processedCmd; +} + +void (string cmd) SV_ParseClientCommand = { + float isProcessed; + tokenize(cmd); + + isProcessed = ParseCmds(argv(0), argv(1), argv(2), argv(3)); + + if (!isProcessed) + { + clientcommand(self, cmd); + } +} + diff --git a/coop.qc b/ssqc/coop.qc similarity index 91% rename from coop.qc rename to ssqc/coop.qc index d9819db86..64f1a8ad5 100644 --- a/coop.qc +++ b/ssqc/coop.qc @@ -15,15 +15,15 @@ void () DropKey = { self.items = self.items - (self.items & IT_KEY1); newmis.items = IT_KEY1; if (world.worldtype == 0) { - setmodel(newmis, "progs/w_s_key.mdl"); + FO_SetModel(newmis, "progs/w_s_key.mdl"); newmis.netname = "silver key"; newmis.noise = "misc/medkey.wav"; } else if (world.worldtype == 1) { - setmodel(newmis, "progs/m_s_key.mdl"); + FO_SetModel(newmis, "progs/m_s_key.mdl"); newmis.netname = "silver runekey"; newmis.noise = "misc/runekey.wav"; } else if (world.worldtype == 2) { - setmodel(newmis, "progs/b_s_key.mdl"); + FO_SetModel(newmis, "progs/b_s_key.mdl"); newmis.netname = "silver keycard"; newmis.noise = "misc/basekey.wav"; } @@ -31,15 +31,15 @@ void () DropKey = { self.items = self.items - (self.items & IT_KEY2); newmis.items = IT_KEY2; if (world.worldtype == 0) { - setmodel(newmis, "progs/w_g_key.mdl"); + FO_SetModel(newmis, "progs/w_g_key.mdl"); newmis.netname = "gold key"; newmis.noise = "misc/medkey.wav"; } else if (world.worldtype == 1) { - setmodel(newmis, "progs/m_g_key.mdl"); + FO_SetModel(newmis, "progs/m_g_key.mdl"); newmis.netname = "gold runekey"; newmis.noise = "misc/runekey.wav"; } else if (world.worldtype == 2) { - setmodel(newmis, "progs/b_g_key.mdl"); + FO_SetModel(newmis, "progs/b_g_key.mdl"); newmis.netname = "gold keycard"; newmis.noise = "misc/basekey.wav"; } diff --git a/ssqc/csmenu.qc b/ssqc/csmenu.qc new file mode 100644 index 000000000..9d1816a77 --- /dev/null +++ b/ssqc/csmenu.qc @@ -0,0 +1,432 @@ +void UpdateClientMenu_Team(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_TEAM); + WriteFloat(MSG_MULTICAST, number_of_teams); + for(float i = 0; i < number_of_teams; i++) { + WriteByte(MSG_MULTICAST, TeamFortress_TeamGetNoPlayers(i+1)); + } + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientClasses(entity pl, float team) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + local float civilian_team = TeamFortress_TeamIsCivilian(pl.team_no); + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLASSES_UPDATE); + WriteByte(MSG_MULTICAST, civilian_team); + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + if(!civilian_team) { + for(float i = 0; i < 10; i++) { + local float f_max = CF_GetClassRestriction(team, i+1); + local float f_players = CF_GetClassPlayers(team, i+1); + + if ((IsLegalClass(i+1) || override_mapclasses) && f_max >= 0) { + WriteFloat(MSG_MULTICAST, f_max); + WriteByte(MSG_MULTICAST, f_players); + } else { + WriteFloat(MSG_MULTICAST, -1); + } + } + } + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Class(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + if(!pl.playerclass) { + return; + } + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_CLASS); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_DropAmmo(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_DROPAMMO); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Scout(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_SCOUT); + WriteByte(MSG_MULTICAST, pl.ScannerOn); + WriteFloat(MSG_MULTICAST, pl.tf_items_flags); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Spy(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_SPY); + WriteFloat(MSG_MULTICAST, invis_only); + WriteFloat(MSG_MULTICAST, pl.last_selected_skin); + WriteFloat(MSG_MULTICAST, pl.last_team); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Spy_Skin(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_SPY_SKIN); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Spy_Team(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_SPY_TEAM); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void UpdateClientMenu_Detpack(entity pl, float cancel_detpack) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_DETPACK); + WriteByte(MSG_MULTICAST, cancel_detpack); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Build(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_BUILD); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void UpdateClientMenu_FixSentry(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_ROTATE_SENTRY); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void UpdateClientMenu_FixDispenser(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_FIX_DISPENSER); + WriteByte(MSG_MULTICAST, old_spanner); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void UpdateClientMenu_UseDispenser(entity pl, entity disp) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_USE_DISPENSER); + WriteFloat(MSG_MULTICAST, disp.origin.x); + WriteFloat(MSG_MULTICAST, disp.origin.y); + WriteFloat(MSG_MULTICAST, disp.origin.z); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void UpdateClientMenu_Admin(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_ADMIN); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void Update_ServerAdminInfo(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + local float cm = CF_GetSetting("c", "clan", "off"), + qm = CF_GetSetting("quadmode", "quadmode", "off"), + dm = CF_GetSetting("duelmode", "duelmode", "off"), + flr = CF_GetSetting("flr", "fologinrequired", "off"), + ptc = CF_GetSetting("ptc", "play_to_completion", "off"); + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_SERVER_ADMIN_INFO); + WriteByte(MSG_MULTICAST, is_paused || cease_fire); + WriteFloat(MSG_MULTICAST, infokeyf(world, "rounds")); + WriteFloat(MSG_MULTICAST, infokeyf(world, "round_time")); + WriteFloat(MSG_MULTICAST, (fo_login_required?1:0) + (flr?2:0)); + WriteFloat(MSG_MULTICAST, CF_GetSetting("mra", "fo_matchrated", "2")); + WriteFloat(MSG_MULTICAST, (play_to_completion?1:0) + (ptc?2:0)); + WriteFloat(MSG_MULTICAST, infokeyf(world, "timelimit")); + WriteFloat(MSG_MULTICAST, infokeyf(world, "fraglimit")); + WriteFloat(MSG_MULTICAST, (clanbattle?1:0) + (cm?2:0)); + WriteFloat(MSG_MULTICAST, (quadmode?1:0) + (qm?2:0)); + WriteFloat(MSG_MULTICAST, (duelmode?1:0) + (dm?2:0)); + WriteFloat(MSG_MULTICAST, new_balance); + WriteFloat(MSG_MULTICAST, captainmode); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Admin_Kick(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_ADMIN_KICK); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} +void UpdateClientMenu_Vote(entity pl, float timeleft) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_VOTE); + WriteFloat(MSG_MULTICAST, timeleft); + WriteString(MSG_MULTICAST, vote1_map); + WriteFloat(MSG_MULTICAST, vote1_cnt); + WriteString(MSG_MULTICAST, vote2_map); + WriteFloat(MSG_MULTICAST, vote2_cnt); + WriteString(MSG_MULTICAST, vote3_map); + WriteFloat(MSG_MULTICAST, vote3_cnt); + WriteString(MSG_MULTICAST, vote4_map); + WriteFloat(MSG_MULTICAST, vote4_cnt); + WriteString(MSG_MULTICAST, vote5_map); + WriteFloat(MSG_MULTICAST, vote5_cnt); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMenu_Captain(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_CAPTAIN_PICK); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClient_Captains(entity pl, float cap1, float cap2) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CAPTAINS); + WriteFloat(MSG_MULTICAST, cap1); + WriteFloat(MSG_MULTICAST, cap2); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMOTD(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_MOTD); + WriteString(MSG_MULTICAST, infokey(world, "motd1")); + WriteString(MSG_MULTICAST, infokey(world, "motd2")); + WriteFloat(MSG_MULTICAST, ( + (clanbattle?GAMEMODE_CLAN:0) + + (quadmode?GAMEMODE_QUAD:0) + + (duelmode?GAMEMODE_DUEL:0) + + (votemode?GAMEMODE_VOTE:0) + )); + WriteFloat(MSG_MULTICAST, infokeyf(world, "rounds")); + WriteFloat(MSG_MULTICAST, number_of_teams); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientTeamScores(entity e) = { + if(!infokeyf(e, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = e; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_TEAM_SCORES); + WriteFloat(MSG_MULTICAST, team1score); + WriteFloat(MSG_MULTICAST, team2score); + WriteFloat(MSG_MULTICAST, team3score); + WriteFloat(MSG_MULTICAST, team4score); + multicast('0 0 0', MULTICAST_ONE_R_NOSPECS); +} + +void UpdateClientQuadRoundBegin(entity pl, float round_time) = { + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_QUAD_ROUND_BEGIN); + multicast('0 0 0', MULTICAST_ALL_R); +} + +void UpdateClientPrematch(entity pl, float countdownstarted) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_PREMATCH); + WriteByte(MSG_MULTICAST, cb_prematch); + WriteByte(MSG_MULTICAST, round_active); + WriteByte(MSG_MULTICAST, round_over); + WriteByte(MSG_MULTICAST, countdownstarted); + WriteFloat(MSG_MULTICAST, rounds); + float timeleft = 0; +// if(!cb_prematch && round_active && !round_over) { + local entity t = find(world, classname, "match"); + if (t != world) { + timeleft = t.cnt2; + } + t = find(world, classname, "prematch"); + if (t != world) { + timeleft = t.cnt2; + } + t = find(world, classname, "round"); + if (t != world) { + timeleft = t.cnt * 60 + t.cnt2; + } +// } + WriteFloat(MSG_MULTICAST, timeleft); + //WriteByte(MSG_MULTICAST, countdownstarted); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientMapVote(entity pl, entity candidate) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE) || !candidate) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_VOTE_UPDATE); + WriteString(MSG_MULTICAST, candidate.netname); + WriteFloat(MSG_MULTICAST, candidate.cnt); + WriteByte(MSG_MULTICAST, (candidate.cnt > 0 && pl.vote_map == candidate)); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateAllClientMapVotes(entity candidate) { + if(!candidate) { + return; + } + entity pl = find(world, classname, "player"); + while(pl) { + if(!pl.has_disconnected) { + UpdateClientMapVote(pl, candidate); + } + pl = find(pl, classname, "player"); + } +} + +void UpdateClientMenu_Maps(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_CLIENT_MENU); + WriteFloat(MSG_MULTICAST, CLIENT_MENU_MAPS); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClient_VoteMap_Add(entity pl, entity map_candidate) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_VOTE_MAP_ADD); + WriteString(MSG_MULTICAST, substring(map_candidate.netname,0,20)); + WriteString(MSG_MULTICAST, map_candidate.broadcast); + WriteString(MSG_MULTICAST, substring(map_candidate.team_broadcast,0,10)); + WriteFloat(MSG_MULTICAST, map_candidate.team_no); + WriteFloat(MSG_MULTICAST, map_candidate.ex_skill_min); + WriteFloat(MSG_MULTICAST, map_candidate.ex_skill_max); + WriteFloat(MSG_MULTICAST, map_candidate.cnt); + WriteByte(MSG_MULTICAST, (pl.vote_map == map_candidate)); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClient_Login(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + if(!fo_login_required) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_LOGIN); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void() votemap_sender_think = { + if (self.owner.vote_map_send) { + UpdateClient_VoteMap_Add(self.owner, self.owner.vote_map_send); + self.owner.vote_map_send = find(self.owner.vote_map_send, classname, "map_candidate"); + self.nextthink = time + 0.1; + } else { + dremove(self); + } +} + +void UpdateClient_VoteMap_AddAll(entity pl) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + //PlayerPreThink was too fast. Use a timer to slow down the send rate + //Soas to not overflow the message buffers + pl.vote_map_send = find(world, classname, "map_candidate"); + local entity sender_timer = spawn(); + sender_timer.owner = pl; + sender_timer.classname = "votemap_sender_timer"; + sender_timer.nextthink = time + 0.1; + sender_timer.think = votemap_sender_think; +} + +void UpdateClient_VoteMap_Add_Broadcast(entity map_candidate) = { + local entity pl = find(world, classname, "player"); + while(pl) { + if(infokeyf(pl, INFOKEY_P_CSQCACTIVE) && !pl.has_disconnected) { + UpdateClient_VoteMap_Add(pl, map_candidate); + } + pl = find(pl, classname, "player"); + } +} + +void UpdateClient_VoteMap_Delete(entity pl, string map) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_VOTE_MAP_DELETE); + WriteString(MSG_MULTICAST, map); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClient_VoteMap_Delete_Broadcast(entity map_candidate) = { + local entity pl = find(world, classname, "player"); + while(pl) { + if(infokeyf(pl, INFOKEY_P_CSQCACTIVE) && !pl.has_disconnected) { + UpdateClient_VoteMap_Delete(pl, map_candidate.netname); + } + pl = find(pl, classname, "player"); + } +} diff --git a/ctf.qc b/ssqc/ctf.qc similarity index 100% rename from ctf.qc rename to ssqc/ctf.qc diff --git a/debug.qc b/ssqc/debug.qc similarity index 54% rename from debug.qc rename to ssqc/debug.qc index 4f08d0526..4d9ccf4ca 100644 --- a/debug.qc +++ b/ssqc/debug.qc @@ -16,12 +16,26 @@ void (entity te) dremove = remove(te); }; +.float created_at; // Forward dec +void dremove_sent(entity te) { + static const float epsilon = 2*SERVER_FRAME_DT; + + float expires = te.created_at + epsilon; // In the past for 0 created at + + if (time > expires) { + dremove(te); + } else { + te.nextthink = expires; + te.think = SUB_Remove; + } +} + void () display_location = { local string st; st = vtos(self.origin); - sprint3(self, PRINT_HIGH, "Location : ", st, "\n"); + sprint(self, PRINT_HIGH, "Location : ", st, "\n"); st = vtos(self.angles); - sprint3(self, PRINT_HIGH, "Angles : ", st, "\n"); + sprint(self, PRINT_HIGH, "Angles : ", st, "\n"); }; diff --git a/defs.qc b/ssqc/defs.qc similarity index 63% rename from defs.qc rename to ssqc/defs.qc index 373b72a5a..2ee18387e 100644 --- a/defs.qc +++ b/ssqc/defs.qc @@ -60,8 +60,8 @@ void () StartFrame; void () PlayerPreThink; // Called every frame before physics are run void () PlayerPostThink; // Called every frame after physics are run -void (float force, float free) ClientKill; // Player entered the suicide command -void () ClientConnect; // called when a player connects to a server +void () ClientKill; // Player entered the suicide command +//void () ClientConnect; // called when a player connects to a server void () PutClientInServer; // call after setting the parm1... parms void () ClientDisconnect; // called when a player disconnects from a server @@ -119,7 +119,6 @@ void end_sys_globals; .float weapon; .string weaponmodel; .float weaponframe; -.float currentammo; .float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; .float items; @@ -164,6 +163,8 @@ void end_sys_globals; .string target; .string targetname; +.string targetsequence; +.float lasttarget; // damage is accumulated through a frame. and sent as one single // message, so the super shotgun doesn't generate huge messages @@ -189,7 +190,6 @@ string string_null; // null string, nothing should be held here entity activator; // the entity that activated a trigger or brush entity damage_attacker; // set by T_Damage -float framecount; // cvars checked each frame float skill; @@ -286,6 +286,7 @@ float rj; // misc flag .float cnt; +.float cnt2; // subs .void () think1; @@ -308,123 +309,21 @@ float rj; //=========================================================================== // builtin functions // -void (vector ang) makevectors = #1; // sets v_forward, etc globals -void (entity e, vector o) setorigin = #2; -void (entity e, string m) setmodel = #3; // set movetype and solid first -void (entity e, vector min, vector max) setsize = #4; -// #5 was removed -// #6 was removed (was: break) -float () random = #7; // returns 0 - 1 -void (entity e, float chan, string samp, float vol, float atten) sound = #8; -vector(vector v) normalize = #9; -void (string e) error = #10; -void (string e) objerror = #11; -float (vector v) vlen = #12; -float (vector v) vectoyaw = #13; -entity()spawn = #14; -void (entity e) remove = #15; - -// sets trace_* globals -// nomonsters can be: -// An entity will also be ignored for testing if forent == test, -// forent->owner == test, or test->owner == forent -// a forent of world is ignored -void (vector v1, vector v2, float nomonsters, entity forent) traceline = #16; - -entity()checkclient = #17; // returns a client to look for -entity(entity start,.string fld, string match) find = #18; -string(string s) precache_sound = #19; -string(string s) precache_model = #20; -void (entity client, string s) stuffcmd = #21; -entity(vector org, float rad) findradius = #22; -void (...) bprint = #23; -void (...) bprint2 = #23; -void (...) bprint3 = #23; -void (...) bprint4 = #23; -void (...) sprint = #24; -void (...) sprint2 = #24; -void (...) sprint3 = #24; -void (...) dprint = #25; -string(float f) ftos = #26; -string(vector v) vtos = #27; -void () coredump = #28; // prints all edicts -void () traceon = #29; // turns statment trace on -void () traceoff = #30; -void (entity e) eprint = #31; // prints an entire edict -float (float yaw, float dist) walkmove = #32; // returns TRUE or FALSE -// #33 was removed -float () droptofloor = #34; // TRUE if landed on floor -void (float style, string value) lightstyle = #35; -float (float v) rint = #36; // round to nearest int -float (float v) floor = #37; // largest integer <= v -float (float v) ceil = #38; // smallest integer >= v -// #39 was removed -float (entity e) checkbottom = #40; // true if self is on ground -float (vector v) pointcontents = #41; // returns a CONTENT_* -// #42 was removed -float (float f) fabs = #43; -vector(entity e, float speed) aim = #44; // returns the shooting vector -float (string s) cvar = #45; // return cvar.value -void (string s) localcmd = #46; // put string into local que -entity(entity e) nextent = #47; // for looping through all ents -void (vector o, vector d, float color, float count) particle = #48; -void () ChangeYaw = #49; // turn towards self.ideal_yaw - // at self.yaw_speed -// #50 was removed -vector(vector v) vectoangles = #51; -// -// direct client message generation -// -void (float to, float f) WriteByte = #52; -void (float to, float f) WriteChar = #53; -void (float to, float f) WriteShort = #54; -void (float to, float f) WriteLong = #55; -void (float to, float f) WriteCoord = #56; -void (float to, float f) WriteAngle = #57; -void (float to, string s) WriteString = #58; -void (float to, entity s) WriteEntity = #59; - -// several removed - -void (float step) movetogoal = #67; - -string(string s) precache_file = #68; // no effect except for -copy -void (entity e) makestatic = #69; -void (string s) changelevel = #70; - -//#71 was removed +float() argc = #85; // returns number of tokens +string(float num) argv_mvdsv = #86; // returns token for the given number -void (string var, string val) cvar_set = #72; // sets cvar.value +//In order to make it more useful, this header includes a timeofday function which just grabs the values for you to use in your qc code straight after calling calltimeofday. +string tod_sec, tod_min, tod_hour, tod_day, tod_mon, tod_year; -void (...) centerprint = #73; // sprint, but in middle +//This function is to facilitate use, and need not be modified. +string zeropad(float val) +{ + return (val < 10 ? strcat("0", ftos(val)) : ftos(val)); +} -void (vector pos, string samp, float vol, float atten) ambientsound = #74; - -string(string s) precache_model2 = #75; // registered version only -string(string s) precache_sound2 = #76; // registered version only -string(string s) precache_file2 = #77; // registered version only - -void (entity e) setspawnparms = #78; // set parm1... to the - // values at level start - // for coop respawn -void (entity killer, entity killee) logfrag = #79; // add to stats - -string(entity e, string key) infokey = #80; // get a key value (world = serverinfo) -float (string s) stof = #81; // convert string to float -void (vector where, float set) multicast = #82; // sends the temp message to a set - // of clients, possibly in PVS or PHS - -float (float a, float b, ...) min = #94; -float (float a, float b, ...) max = #95; -float (float min, float value, float max) bound = #96; -float (float x, float y) pow = #97; - -float (string s) strlen = #114; -string (string s1, string s2) strcat = #115; -string (string s, float start, float count) substr = #116; -string (string s) strzone = #118; -void (string s) strunzone = #119; +void(float s, float mi, float h, float d, float mo, float y, string tmp_timeofday) timeofday = +{ tod_sec = zeropad(s); tod_min = zeropad(mi); tod_hour = zeropad(h); tod_day = zeropad(d); tod_mon = zeropad((mo + 1)); tod_year = zeropad(y); }; //============================================================================ @@ -447,3 +346,6 @@ void () SUB_Remove; void (entity targ, entity inflictor, entity attacker, float damage) T_Damage; float (entity e, float healamount, float ignore) T_Heal; // health function float (entity targ, entity inflictor) CanDamage; + +// enable qw in fteextensions +#define QUAKEWORLD diff --git a/demoman.qc b/ssqc/demoman.qc similarity index 65% rename from demoman.qc rename to ssqc/demoman.qc index 78a1e83c9..3185760e7 100644 --- a/demoman.qc +++ b/ssqc/demoman.qc @@ -3,11 +3,9 @@ //======================================================== void () NormalGrenadeTouch; -void () NormalGrenadeExplode; void () MirvGrenadeTouch; void () MirvGrenadeExplode; -void (vector org, entity shooter) MirvGrenadeLaunch; void () TeamFortress_DetpackMenu; void () TeamFortress_DetpackStop; @@ -16,87 +14,100 @@ void () TeamFortress_DetpackExplode; void () TeamFortress_DetpackTouch; void () TeamFortress_DetpackDisarm; void () TeamFortress_DetpackCountDown; +void CheckStateQ3Goal(entity trig); -void (float force) TeamFortress_DetonatePipebombs = { - local entity e; +float (float force) TeamFortress_DetonatePipebombs = { + if (!force && allpipes_cooldown && time < self.pipecooldown) + return impulse_queue ? FALSE : TRUE; - if (time < self.pipecooldown && !force) - return; + if (self.detpipe_nesting > 0) + return TRUE; + // It's possible that we'll kill ourselves when we detonate pipebombs and + // initiate a nested det; exclude this to avoid iterating removed ents. + self.detpipe_nesting++; + + int count; + entity* pipes = find_list(classname, "pipebomb", EV_STRING, count); + + float rewound = FALSE; + if (!force && count > 0) + rewound = RewindPlayersExceptSelf(self.pipecooldown); + + for (float i = 0; i < count; i++) { + if (pipes[i].owner != self) + continue; + + if (!force && pipes[i].created_at > time - pipecooldown_time) + continue; + + deathmsg = pipes[i].flags & FL_ONGROUND ? pipes[i].weapon : + DMSG_GREN_PIPE_AIR; + + T_RadiusDamage(pipes[i], pipes[i].owner, 120, world); - e = find(world, classname, "pipebomb"); - while (e != world) { - if (e.owner == self) - e.nextthink = time; - e = find(e, classname, "pipebomb"); + // This maintains some sort of awful hack that we'll remove. + pipes[i].voided = 1; + RenderExplosion(pipes[i].origin); + dremove(pipes[i]); } + + // NOTE: nested death-dets would not trigger double unwind due to `force==1` + if (rewound) + FOPlayer::RestoreAll(); + + self.detpipe_nesting--; + return TRUE; }; void () PipebombTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); if (self.velocity == '0 0 0') self.avelocity = '0 0 0'; }; void () MirvGrenadeTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); if (self.velocity == '0 0 0') self.avelocity = '0 0 0'; }; -void () MirvGrenadeExplode = { - local float i; - - deathmsg = DMSG_GREN_MIRV; - T_RadiusDamage(self, self.owner, 100, world); - - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); +static void (vector org, entity shooter) MirvGrenadeLaunch = { + float xdir = 75 * crandom(); + float ydir = 75 * crandom(); + float zdir = 40 * random(); - self.solid = SOLID_NOT; - i = 0; - while (i < 4) { - MirvGrenadeLaunch(self.origin + '0 0 -1', self.owner); - i = i + 1; - } - BecomeExplosion(); + entity proj = FOProj_Create(FPP_HANDGRENADE); + proj.owner = shooter; + proj.movetype = MOVETYPE_BOUNCE; + proj.solid = SOLID_BBOX; -}; + proj.classname = "grenade"; + proj.touch = NormalGrenadeTouch; + proj.think = FO_T_GrenExplode; + proj.nextthink = time + 2 + random(); -void (vector org, entity shooter) MirvGrenadeLaunch = { - local float xdir; - local float ydir; - local float zdir; + proj.velocity = [xdir * 2, ydir * 2, zdir * 15]; - xdir = 150 * random() - 75; - ydir = 150 * random() - 75; - zdir = 40 * random(); + FO_GrenInfo* gdesc = FO_GrenDesc(GREN_MIRVLET); + proj.fpp.gren_type = GREN_MIRVLET; + proj.skin = gdesc->skin; - newmis = spawn(); - newmis.owner = shooter; - newmis.movetype = MOVETYPE_BOUNCE; - newmis.solid = SOLID_BBOX; + setorigin(proj, org); + FOProj_Finalize(proj); +}; - newmis.classname = "grenade"; - newmis.weapon = DMSG_GREN_MIRV; - newmis.touch = NormalGrenadeTouch; - newmis.think = NormalGrenadeExplode; - newmis.nextthink = time + 2 + random(); +void () MirvGrenadeExplode = { + FO_GrenExplode(self); - newmis.velocity_x = xdir * 2; - newmis.velocity_y = ydir * 2; - newmis.velocity_z = zdir * 15; - newmis.avelocity = '250 300 400'; + self.solid = SOLID_NOT; + for (int i = 0; i < 4; i++) + MirvGrenadeLaunch(self.origin, self.owner); - setmodel(newmis, "progs/grenade2.mdl"); + dremove(self); - setsize(newmis, VEC_ORIGIN, VEC_ORIGIN); - setorigin(newmis, org); }; + void () TeamFortress_DetpackMenu = { local entity detpack; @@ -123,6 +134,14 @@ void () TeamFortress_DetpackMenu = { Menu_Demoman(); }; +void (float timer) TeamFortress_ToggleDetpack = { + if (self.is_detpacking) { + TeamFortress_DetpackStop(); + } else { + TeamFortress_SetDetpack(timer); + } +}; + void (float timer) TeamFortress_SetDetpack = { local string stimer; local entity te; @@ -130,12 +149,17 @@ void (float timer) TeamFortress_SetDetpack = { self.impulse = 0; self.last_impulse = 0; - if (!(self.weapons_carried & WEAP_DETPACK)) - return; - if (self.ammo_detpack <= 0) + if (self.ammo_detpack <= 0) { + sprint(self, PRINT_HIGH, "You don't have any detpacks left\n"); return; + } + if (no_fire_mode) { + sprint(self, PRINT_HIGH, "You can't set a detpack right now\n"); + return; + } + at_spot = findradius(self.origin, 65); while (at_spot != world) { @@ -158,9 +182,12 @@ void (float timer) TeamFortress_SetDetpack = { } at_spot = at_spot.chain; } - if (!(self.flags & FL_ONGROUND)) { - sprint(self, PRINT_HIGH, "You cannot set detpacks in the air\n"); - return; + + if (!self.waterlevel) { + if (!(self.flags & FL_ONGROUND)) { + sprint(self, PRINT_HIGH, "You cannot set detpacks in the air\n"); + return; + } } te = find(world, classname, "detpack"); @@ -175,22 +202,28 @@ void (float timer) TeamFortress_SetDetpack = { if (timer < 5) { sprint(self, PRINT_HIGH, - "You cannot set detpacks for less that 5 seconds\n"); + "You cannot set detpacks for less than 5 seconds\n"); return; } + float quad_limit = quadmode && round_active && + (time < round_end_time) && (rounds % 2) != (self.team_no - 1); + if (NewBalanceActive() && quad_limit) { + float last = max(self.detpack_last, round_start_time); + float next = last + 30; // TODO: tunable if this sticks + if (next > time) { + sprint(self, PRINT_HIGH, sprintf("%0.1f seconds until next detpack\n", next - time)); + return; + } + } + self.is_detpacking = 3; self.detpack_left = timer; self.ammo_detpack = self.ammo_detpack - 1; self.immune_to_check = time + 10; - self.tfstate = self.tfstate | TFSTATE_CANT_MOVE; - self.weapon = self.current_weapon; - self.current_weapon = 0; - self.weaponmodel = ""; - self.weaponframe = 0; + self.tfstate |= TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON; Status_Refresh(self); - TeamFortress_SetSpeed(self); self.pausetime = time + 3; stimer = ftos(timer); @@ -199,6 +232,7 @@ void (float timer) TeamFortress_SetDetpack = { " seconds...\n"); Menu_Demoman_Cancel(); + self.detpack_last = time; newmis = spawn(); newmis.owner = self; newmis.classname = "timer"; @@ -223,14 +257,11 @@ void () TeamFortress_DetpackStop = { self.ammo_detpack = self.ammo_detpack + 1; dremove(detpack_timer); - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); self.is_detpacking = 0; + self.tfstate &= ~(TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); self.detpack_left = 0; - self.current_weapon = self.weapon; Status_Refresh(self); - W_SetCurrentAmmo(self); - TeamFortress_SetSpeed(self); self.pausetime = time; }; @@ -247,32 +278,33 @@ void () TeamFortress_DetpackSet = { self.owner.is_detpacking = 0; Menu_Close(self.owner); - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self.owner); - sound(self.owner, CHAN_VOICE, "doors/medtry.wav", 1, ATTN_NORM); + self.owner.tfstate &= ~(TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); + FO_Sound(self.owner, CHAN_VOICE, "doors/medtry.wav", 1, ATTN_NORM); oldself = self; self = self.owner; - self.is_detpacking = 0; - self.current_weapon = self.weapon; + self.is_detpacking = 0; // Weapon is queued. Status_Refresh(self); - W_SetCurrentAmmo(self); self = oldself; newmis = spawn(); newmis.owner = self.owner; newmis.origin = (self.owner.origin - '0 0 23'); newmis.movetype = MOVETYPE_BOUNCE; - newmis.solid = SOLID_BBOX; + if(solid_detpack) { + newmis.solid = SOLID_BBOX; + } else { + newmis.solid = SOLID_TRIGGER; //so that scout can disarm + } newmis.classname = "detpack"; newmis.flags = FL_ITEM; newmis.angles = '90 0 0'; newmis.angles_y = self.owner.angles_y; newmis.velocity = '0 0 0'; newmis.avelocity = '0 0 0'; - newmis.weaponmode = 0; + newmis.is_disarming = FALSE; newmis.touch = TeamFortress_DetpackTouch; - setmodel(newmis, "progs/detpack.mdl"); + FO_SetModel(newmis, "progs/detpack.mdl"); setsize(newmis, '-16 -16 0', '16 16 8'); setorigin(newmis, self.owner.origin); @@ -307,12 +339,10 @@ void () TeamFortress_DetpackExplode = { self.owner.detpack_left = 0; bprint(PRINT_MEDIUM, "FIRE IN THE HOLE!\n"); - sound(self, CHAN_WEAPON, "weapons/detpack.wav", 1, ATTN_NONE); - + FO_Sound(self, CHAN_WEAPON, "weapons/detpack.wav", 1, ATTN_NONE); pos = pointcontents(self.origin); if ((pos != CONTENT_SOLID) && (pos != CONTENT_SKY) && (self.owner.has_disconnected != 1)) { - deathmsg = DMSG_DETPACK; head = findradius(self.origin, 1500); while (head) { @@ -330,8 +360,22 @@ void () TeamFortress_DetpackExplode = { } } } - } else if (head.takedamage && - (vlen((head.origin - self.origin)) <= 700)) { + } + else if (head.classname == "info_notnull") + { + // q3 detpackable goals + if (strstrofs(head.flagsq3, "chargeable") >= 0) + { + traceline(self.origin, head.origin, 1, self); + if (trace_fraction == 1) + { + CheckStateQ3Goal(head); + } + } + } + else if (head.takedamage && + (vlen((head.origin - self.origin)) <= 700)) + { org = head.origin + (head.mins + head.maxs) * 0.5; points = 0.5 * vlen(self.origin - org); if (points < 0) @@ -355,8 +399,7 @@ void () TeamFortress_DetpackExplode = { } else sprint(self.owner, PRINT_HIGH, "Your detpack fizzled out\n"); - if (self.weaponmode == 1) { - TeamFortress_SetSpeed(self.enemy); + if (self.is_disarming) { dremove(self.oldenemy); dremove(self.observer_list); } @@ -375,26 +418,29 @@ void () TeamFortress_DetpackTouch = { return; if (other.deadflag) return; - if (self.weaponmode == 1) + if (self.is_disarming) return; if ((other.team_no == self.owner.team_no) && (self.owner.team_no != 0)) return; makevectors(other.v_angle); - source = other.origin + '0 0 16'; - - traceline(source, source + v_forward * 64, 0, other); - if ((trace_fraction == 1) || (trace_ent != self)) + //source = other.origin + '0 0 16'; + source = other.origin + other.view_ofs; + + self.solid = SOLID_BBOX; + traceline(source, source + v_forward * 64, MOVE_HITMODEL | MOVE_EVERYTHING | MOVE_TRIGGERS, other); + if(!solid_detpack) + self.solid = SOLID_TRIGGER; + if ((trace_fraction == 1) || (trace_ent != self)) { return; + } other.immune_to_check = time + 5; - other.tfstate = other.tfstate | TFSTATE_CANT_MOVE; + other.tfstate |= (TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); sprint(other, PRINT_HIGH, "Disarming detpack...\n"); - TeamFortress_SetSpeed(other); - disarm = spawn(); disarm.owner = other; disarm.enemy = self; @@ -402,7 +448,7 @@ void () TeamFortress_DetpackTouch = { disarm.nextthink = time + 3; disarm.think = TeamFortress_DetpackDisarm; - self.weaponmode = 1; + self.is_disarming = TRUE; self.enemy = other; self.observer_list = disarm; }; @@ -414,9 +460,8 @@ void () TeamFortress_DetpackDisarm = { } bprint(PRINT_MEDIUM, self.enemy.owner.netname, "'s detpack was defused by ", self.owner.netname, "\n"); - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & 65536); + self.owner.tfstate &= ~(TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); TF_AddFrags(self.owner, 1); - TeamFortress_SetSpeed(self.owner); self.enemy.owner.detpack_left = 0; Status_Refresh(self.enemy.owner); @@ -440,7 +485,7 @@ void () TeamFortress_DetpackCountDown = { sprint(self.owner, PRINT_HIGH, cd, "...\n"); if ((self.health < 5) && (self.has_disconnected == 0)) { - sound(self.enemy, CHAN_VOICE, "doors/baseuse.wav", 1, + FO_Sound(self.enemy, CHAN_VOICE, "doors/baseuse.wav", 1, ATTN_NORM); self.has_disconnected = 1; } diff --git a/doors.qc b/ssqc/doors.qc similarity index 88% rename from doors.qc rename to ssqc/doors.qc index cbac22809..9ed830a9d 100644 --- a/doors.qc +++ b/ssqc/doors.qc @@ -1,12 +1,13 @@ void () door_go_down; void () door_go_up; +void (entity pe_player, float dontstopdead) Spy_CheckForFuncTouch; void () door_blocked = { if (other.classname == "detpack") { sprint(other.owner, PRINT_HIGH, "Your detpack was squashed\n"); - if (other.weaponmode == 1) { + if (other.is_disarming) { TeamFortress_SetSpeed(other.enemy); dremove(other.observer_list); @@ -17,7 +18,7 @@ void () door_blocked = { } T_Damage(other, self, self, self.dmg); - if (self.wait >= 0) { + if (self.wait >= 0 && self.forcecloseonblock == 0) { if (self.state == STATE_DOWN) door_go_up(); else @@ -26,8 +27,8 @@ void () door_blocked = { }; void () door_hit_top = { - sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); - self.state = 0; + FO_Sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; if (self.spawnflags & 32) return; @@ -37,18 +38,22 @@ void () door_hit_top = { void () door_hit_bottom = { self.goal_state = 2; - sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); - self.state = 1; + FO_Sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; }; -void () door_go_down = { - sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); +void () door_go_down_silent = { if (self.max_health) { self.takedamage = 1; self.health = self.max_health; } self.state = STATE_DOWN; SUB_CalcMove(self.pos1, self.speed, door_hit_bottom); +} + +void () door_go_down = { + FO_Sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + door_go_down_silent(); }; void () door_go_up = { @@ -59,7 +64,7 @@ void () door_go_up = { self.nextthink = self.ltime + self.wait; return; } - sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); self.state = STATE_UP; SUB_CalcMove(self.pos2, self.speed, door_hit_top); SUB_UseTargets(); @@ -73,7 +78,7 @@ void () door_fire = { objerror("door_fire: self.owner != self"); } if (self.items) { - sound(self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); } self.message = string_null; oself = self; @@ -151,6 +156,9 @@ void () door_touch = { if (other.classname != "player") return; + + Spy_CheckForFuncTouch(other, 0); + if (self.owner.attack_finished > time) return; @@ -165,7 +173,7 @@ void () door_touch = { self.owner.attack_finished = time + 2; if (self.owner.message != "") { CenterPrint(other, self.owner.message); - sound(other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + FO_Sound(other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); } if (!self.items) return; @@ -174,23 +182,23 @@ void () door_touch = { if (self.owner.items == IT_KEY1) { if (world.worldtype == 2) { CenterPrint(other, "You need the silver keycard"); - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); } else if (world.worldtype == 1) { CenterPrint(other, "You need the silver runekey"); - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); } else if (world.worldtype == 0) { CenterPrint(other, "You need the silver key"); - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); } } else if (world.worldtype == 2) { CenterPrint(other, "You need the gold keycard\n"); - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); } else if (world.worldtype == 1) { CenterPrint(other, "You need the gold runekey"); - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); } else if (world.worldtype == 0) { CenterPrint(other, "You need the gold key"); - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); } return; } @@ -234,6 +242,7 @@ entity(vector fmins, vector fmaxs) spawn_field = trigger.all_active = self.all_active; trigger.last_impulse = self.last_impulse; trigger.else_goal = self.else_goal; + trigger.group_no = self.group_no; t1 = fmins; t2 = fmaxs; setsize(trigger, t1 - '60 60 8', t2 + '60 60 8'); @@ -375,6 +384,13 @@ void () func_door = { self.noise1 = "doors/ddoor2.wav"; self.noise2 = "doors/ddoor1.wav"; } + // q3 fields + if (self.allowteams == "blue") { + self.team_no = 1; + } + if (self.allowteams == "red") { + self.team_no = 2; + } SetMovedir(); self.max_health = self.health; self.solid = SOLID_BSP; @@ -411,7 +427,7 @@ void () func_door = { self.pos2 = self.pos1; self.pos1 = self.origin; } - self.state = 1; + self.state = STATE_BOTTOM; if (self.health) { self.takedamage = 1; self.th_die = door_killed; @@ -449,7 +465,7 @@ void () fd_secret_use = { self.takedamage = 0; } self.velocity = '0 0 0'; - sound(self, 2, self.noise1, 1, 1); + FO_Sound(self, CHAN_VOICE, self.noise1, 1, 1); self.nextthink = self.ltime + 0.1; temp = 1 - (self.spawnflags & 2); makevectors(self.mangle); @@ -470,7 +486,7 @@ void () fd_secret_use = { } self.dest2 = self.dest1 + (v_forward * self.t_length); SUB_CalcMove(self.dest1, self.speed, fd_secret_move1); - sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); }; void (entity e, float f) fd_secret_pain = { @@ -480,16 +496,16 @@ void (entity e, float f) fd_secret_pain = { void () fd_secret_move1 = { self.nextthink = self.ltime + 1; self.think = fd_secret_move2; - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); }; void () fd_secret_move2 = { - sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); SUB_CalcMove(self.dest2, self.speed, fd_secret_move3); }; void () fd_secret_move3 = { - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); if (!(self.spawnflags & 1)) { self.nextthink = self.ltime + self.wait; self.think = fd_secret_move4; @@ -497,18 +513,18 @@ void () fd_secret_move3 = { }; void () fd_secret_move4 = { - sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); SUB_CalcMove(self.dest1, self.speed, fd_secret_move5); }; void () fd_secret_move5 = { self.nextthink = self.ltime + 1; self.think = fd_secret_move6; - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); }; void () fd_secret_move6 = { - sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done); }; @@ -519,7 +535,7 @@ void () fd_secret_done = { self.th_pain = fd_secret_pain; self.th_die = fd_secret_use; } - sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); }; void () secret_blocked = { @@ -534,6 +550,8 @@ void () secret_touch = { if (other.classname != "player") return; + Spy_CheckForFuncTouch(other, 0); + if (self.attack_finished > time) return; @@ -543,7 +561,7 @@ void () secret_touch = { self.attack_finished = time + 2; if (self.message) { CenterPrint(other, self.message); - sound(other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + FO_Sound(other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); } }; diff --git a/engineer.qc b/ssqc/engineer.qc similarity index 56% rename from engineer.qc rename to ssqc/engineer.qc index 537bdd30d..7cea347bb 100644 --- a/engineer.qc +++ b/ssqc/engineer.qc @@ -18,14 +18,13 @@ void (entity disp) Engineer_SentryGun_Upgrade; void (entity disp) Engineer_SentryGun_Repair; void () Menu_Engineer_Cancel; void () CF_CheckBuilding; -float (entity obj, entity builder) CheckArea; void () LaserBolt_Think = { self.solid = SOLID_TRIGGER; self.movetype = MOVETYPE_FLYMISSILE; self.velocity = self.oldorigin; self.touch = LaserBolt_Touch; - setmodel(self, "progs/e_spike2.mdl"); + FO_SetModel(self, "progs/e_spike2.mdl"); self.nextthink = time + 1; self.think = SUB_Remove; }; @@ -51,16 +50,13 @@ void () LaserBolt_Touch = { deathmsg = DMSG_LASERBOLT; TF_T_Damage(other, self, self.enemy, 25, 2, TF_TD_ELECTRICITY); - // create a new projectile on other side of target if old_railgun is enabled - if (old_railgun) { - self.velocity = self.oldorigin; - self.owner = other; - setmodel(self, string_null); - self.touch = SUB_Null; - self.nextthink = time + 0.1; - self.think = LaserBolt_Think; - return; - } + // create a new projectile on other side of target + self.velocity = self.oldorigin; + self.owner = other; + setmodel(self, string_null); + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = LaserBolt_Think; } else { WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); WriteByte(MSG_MULTICAST, TE_SPIKE); @@ -68,39 +64,179 @@ void () LaserBolt_Touch = { WriteCoord(MSG_MULTICAST, self.origin_y); WriteCoord(MSG_MULTICAST, self.origin_z); multicast(self.origin, MULTICAST_PHS); + + if(other.classname == "worldspawn" && spurs_enabled > 0) + { + if(self.owner.playerclass == PC_ENGINEER && spurs_engineer == 1) + { + ConvertToSpurs(self); + return; + } + } } dremove(self); }; -void () W_FireLaser = { +static const float kImpellerTargetRange = 1500; + +static float ValidImpellerTarget(entity t) { + if (t.classname != "player") + return FALSE; + + if (vlen(t.origin - self.origin) > kImpellerTargetRange) + return FALSE; + + if (cb_prematch) // Shoot anyone in prematch + return TRUE; + + return (t.team_no != self.team_no && t.has_flag); +} + + +static entity FindImpellerTarget(vector org, float min_a, float* direct) { + + traceline(org, org + 1500 * v_forward, MOVE_NORMAL, world); + if (trace_fraction != 1 && ValidImpellerTarget(trace_ent)) + return trace_ent; + + int count; + entity* players; + float best_a = min_a; + entity best_p = world; + + players = find_list(classname, "player", EV_STRING, count); + for (int i = 0; i < count; i++) { + entity p = players[i]; + + if (!ValidImpellerTarget(p)) + continue; + + vector dir = normalize(p.origin - org); + float a = v_forward * dir; + + traceline(org, p.origin, MOVE_NORMAL, world); + if (trace_fraction < 1) { + if (trace_ent != p) { + continue; // Something in the way + } else { + *direct = TRUE; + return p; + } + } + + if (a < best_a) + continue; // Not in AA cone + + best_a = a; + best_p = p; + } + + return best_p; +} + +static vector find_closest(vector a, vector b, vector p) { + vector v = b - a, u = a - p; + float t = -(v * u) / (v * v); + + if (t >= 0 && t <= 1) + return (1 - t) * a + t * b; + return b; +} + +static void draw_beam(vector a, vector b, int effect) { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, effect); + WriteEntity(MSG_MULTICAST, world); + WriteCoord(MSG_MULTICAST, a_x); + WriteCoord(MSG_MULTICAST, a_y); + WriteCoord(MSG_MULTICAST, a_z); + WriteCoord(MSG_MULTICAST, b_x); + WriteCoord(MSG_MULTICAST, b_y); + WriteCoord(MSG_MULTICAST, b_z); + multicast(a, MULTICAST_PHS); +} + +void W_FireImpeller() { + FO_Sound(self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + + float rewound = RewindPlayersExceptSelf(0); + + if (!cb_prematch) + self.special_next = time + 3; + + vector org = self.origin + '0 0 16'; + float direct = FALSE; + entity hit = FindImpellerTarget(org, 0.985, &direct); + + if (rewound) + FOPlayer::RestoreAll(); + + float mag = 250; + vector end; + + if (hit == world) { + traceline(org, org + 350 * v_forward, MOVE_WORLDONLY, world); + if (trace_fraction == 1) + return; + + end = trace_endpos; + hit = self; + draw_beam(org, end, TE_LIGHTNING2); + } else { + if (direct) { + end = hit.origin; + draw_beam(org, end, TE_LIGHTNING2); + } else { + vector a = org, b = org + kImpellerTargetRange * v_forward; + traceline(a, b, MOVE_WORLDONLY, world); + end = trace_endpos; + vector x = find_closest(a, end, hit.origin); + + draw_beam(a, x, TE_LIGHTNING2); + draw_beam(x, hit.origin, TE_LIGHTNING2); + + // Discount knockback by help given + mag -= max(10, vlen(hit.origin - x)); + } + + end = hit.origin; + } + + deathmsg = DMSG_IMPELLER; + TF_T_Damage(hit, self, self, 5, TF_TD_NOTTEAM, TF_TD_ELECTRICITY | TF_TD_NOMOMENTUM); + hit.flags &= ~FL_ONGROUND; + if (hit.has_flag) + mag *= 2; + hit.velocity -= mag * normalize(end - org); +} + +void () W_FireRailgun = { local vector vec, org; self.ammo_nails = self.ammo_nails - 1; - self.currentammo = self.ammo_nails; makevectors(self.v_angle); org = self.origin + v_forward * 8; vec = aim(self, 10000); vec = normalize(vec); - newmis = spawn(); - newmis.owner = self; - newmis.enemy = self; - newmis.movetype = MOVETYPE_FLYMISSILE; - newmis.solid = SOLID_TRIGGER; + entity proj = FOProj_Create(FPP_RAILGUN); + proj.owner = self; + proj.enemy = self; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.solid = SOLID_TRIGGER; - setmodel(newmis, "progs/e_spike1.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, org + '0 0 16'); + proj.velocity = vec * FPP_Get(FPP_RAILGUN)->speed; + proj.angles = vectoangles(proj.velocity); - newmis.velocity = vec * 1500; - newmis.angles = vectoangles(newmis.velocity); - newmis.oldorigin = newmis.velocity; + proj.nextthink = time + 5; + proj.think = SUB_Remove; + proj.touch = LaserBolt_Touch; + proj.classname = "railslug"; + setorigin(proj, org + '0 0 16'); - newmis.nextthink = time + 5; - newmis.think = SUB_Remove; - newmis.touch = LaserBolt_Touch; + FOProj_Finalize(proj); }; void () EMPExplode = { @@ -139,7 +275,7 @@ void () EMPExplode = { }; void () EMPGrenadeTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); if (self.velocity == '0 0 0') { self.avelocity = '0 0 0'; } @@ -147,8 +283,10 @@ void () EMPGrenadeTouch = { void () EMPGrenadeExplode = { local float expsize; - local entity te; + local entity te, te2; local entity oldself; + local float candamage, empblockinrange; + empblockinrange = FALSE; WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); WriteByte(MSG_MULTICAST, TE_TAREXPLOSION); @@ -157,75 +295,68 @@ void () EMPGrenadeExplode = { WriteCoord(MSG_MULTICAST, self.origin_z); multicast(self.origin, MULTICAST_PHS); - te = findradius(self.origin, 240); + te2 = find(world, classname, "info_empblock"); + while(te2) { + //bprint(PRINT_HIGH, "Found EMP Block at ", vtos(te2.origin), " with radius ", ftos(te2.t_length), "\n"); + //bprint(PRINT_HIGH, "Block is ", ftos(vlen(te2.origin - self.origin)), " units away\n"); + if(vlen(te2.origin - self.origin) <= te2.t_length) { + if(te2.goal_effects & 16) { + traceline(self.origin, te2.origin, 0, self); + //bprint(PRINT_HIGH, "Block wall check is ", ftos(trace_fraction), "\n"); + if (trace_fraction == 1) { + empblockinrange = TRUE; + break; + } + } else { + empblockinrange = TRUE; + break; + } + } + te2 = find(te2, classname, "info_empblock"); + } + + te = findradius(self.origin, PC_ENGINEER_GRENADE_TYPE_2_RANGE); while (te) { - if ((te.touch == ammo_touch) || (te.touch == weapon_touch)) { - if (te.classname != "item_spikes") { + te.chain2 = te.chain; + candamage = TRUE; + if(walls_block_emp || empblockinrange) + candamage = CanDamage(te, self); + traceline(self.origin, te.origin, MOVE_NOMONSTERS, self); + if(trace_ent && trace_ent.spawnflags & SPAWNFLAG_BLOCK_EMP) { + candamage = FALSE; + } + if(candamage) { + if ((te.touch == ammo_touch) || (te.touch == weapon_touch)) { + if (te.classname != "item_spikes") { + te.solid = SOLID_NOT; + te.enemy = self.owner; + te.nextthink = time + 1 + random() * 2; + te.think = EMPExplode; + } + } else if (te.think == TeamFortress_DetpackExplode) { te.solid = SOLID_NOT; - te.enemy = self.owner; te.nextthink = time + 1 + random() * 2; - te.think = EMPExplode; - } - } else if (te.think == TeamFortress_DetpackExplode) { - te.solid = SOLID_NOT; - te.nextthink = time + 1 + random() * 2; - dremove(te.oldenemy); - } else if (te.classname == "pipebomb") { - te.nextthink = time + 0.1; - } else if ((te.classname == "building_dispenser") || - (te.classname == "building_sentrygun")) { - if (! - (((teamplay & 16) && (te.team_no > 0)) && - (te.team_no == self.owner.team_no))) { - TF_T_Damage(te, self, self.owner, 200, 0, TF_TD_EXPLOSION); - } - } else if (te.classname == "ammobox") { - expsize = 0; - expsize = expsize + te.ammo_shells * 0.75; - expsize = expsize + te.ammo_rockets * 0.75 * 2; - expsize = expsize + te.ammo_cells * 0.75 * 2; - if (expsize > 0) { - te.solid = 0; - deathmsg = 30; - T_RadiusDamage(te, self.owner, expsize, te); - te.think = SUB_Remove; + dremove(te.oldenemy); + } else if (te.classname == "pipebomb") { te.nextthink = time + 0.1; - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, te.origin_x); - WriteCoord(MSG_MULTICAST, te.origin_y); - WriteCoord(MSG_MULTICAST, te.origin_z); - multicast(te.origin, MULTICAST_PHS); - } - } else if ((te.classname == "player") || - (te.touch == BackpackTouch)) { - if (! - ((teamplay & 16) && (te.team_no > 0) && - (te.team_no == self.owner.team_no))) { + } else if ((te.classname == "building_dispenser") || + (te.classname == "building_sentrygun")) { + if (! + (((teamplay & 16) && (te.team_no > 0)) && + (te.team_no == self.owner.team_no))) { + TF_T_Damage(te, self, self.owner, 200, 0, TF_TD_EXPLOSION); + } + } else if (te.classname == "ammobox") { expsize = 0; expsize = expsize + te.ammo_shells * 0.75; expsize = expsize + te.ammo_rockets * 0.75 * 2; - if (te.playerclass != PC_ENGINEER) { - expsize = expsize + te.ammo_cells * 0.75; - } + expsize = expsize + te.ammo_cells * 0.75 * 2; if (expsize > 0) { + te.solid = 0; deathmsg = 30; T_RadiusDamage(te, self.owner, expsize, te); - if (te.touch != BackpackTouch) { - TF_T_Damage(te, self, self.owner, expsize, 2, 4); - te.ammo_shells = ceil(te.ammo_shells * 0.25); - te.ammo_rockets = ceil(te.ammo_rockets * 0.25); - if (te.playerclass != PC_ENGINEER) { - te.ammo_cells = ceil(te.ammo_cells * 0.25); - } - oldself = self; - self = te; - W_SetCurrentAmmo(self); - self = oldself; - } else { - te.think = SUB_Remove; - te.nextthink = time + 0.1; - } + te.think = SUB_Remove; + te.nextthink = time + 0.1; WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); WriteByte(MSG_MULTICAST, TE_EXPLOSION); WriteCoord(MSG_MULTICAST, te.origin_x); @@ -233,22 +364,133 @@ void () EMPGrenadeExplode = { WriteCoord(MSG_MULTICAST, te.origin_z); multicast(te.origin, MULTICAST_PHS); } + } else if ((te.classname == "player") || + (te.touch == BackpackTouch)) { + if (! + ((teamplay & 16) && (te.team_no > 0) && + (te.team_no == self.owner.team_no))) { + expsize = 0; + + if (new_emp) + { + // we assume player has discarded non-needed ammo for damage calcs + expsize = expsize + te.maxammo_shells * .75; + expsize = expsize + te.maxammo_rockets * .75 * 2; + expsize = expsize + te.maxammo_cells * 0.75; + + // remove discard ammo amount + switch (te.playerclass) + { + case PC_SCOUT: + expsize = expsize - te.maxammo_rockets * .75 * 2; + break; + case PC_MEDIC: + expsize = expsize - te.maxammo_rockets * .75 * 2; + break; + case PC_SNIPER: + case PC_SPY: + case PC_ENGINEER: + expsize = expsize - te.maxammo_rockets * .75 * 2; + expsize = expsize - te.maxammo_cells * .75; + break; + case PC_SOLDIER: + case PC_DEMOMAN: + expsize = expsize - te.maxammo_cells * .75; + break; + case PC_HVYWEAP: + expsize = expsize - te.maxammo_rockets * .75 * 2; + break; + default: + } + } + else + { + expsize = expsize + te.ammo_shells * 0.75; + expsize = expsize + te.ammo_rockets * 0.75 * 2; + if (te.playerclass != PC_ENGINEER) { + expsize = expsize + te.ammo_cells * 0.75; + } + } + + + if (expsize > 0) { + deathmsg = DMSG_GREN_EMP; + T_RadiusDamage(te, self.owner, expsize, te); + if (te.touch != BackpackTouch) { + TF_T_Damage(te, self, self.owner, expsize, 2, 4); + te.ammo_shells = ceil(te.ammo_shells * 0.25); + te.ammo_rockets = ceil(te.ammo_rockets * 0.25); + if (te.playerclass != PC_ENGINEER) { + te.ammo_cells = ceil(te.ammo_cells * 0.25); + } + oldself = self; + self = te; + self = oldself; + } else { + te.think = SUB_Remove; + te.nextthink = time + 0.1; + } + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_EXPLOSION); + WriteCoord(MSG_MULTICAST, te.origin_x); + WriteCoord(MSG_MULTICAST, te.origin_y); + WriteCoord(MSG_MULTICAST, te.origin_z); + multicast(te.origin, MULTICAST_PHS); + } + } } } - te = te.chain; + te = te.chain2; } dremove(self); }; +entity () FO_FindCurrentBuildingObject = { + local entity te; + te = find(world, netname, "build_timer"); + while (te) { + if (te.owner == self || (engineer_move && te.real_owner == self)) { + return te; + } else { + te = find(te, netname, "build_timer"); + } + } + return world; +} + +void () FO_Engineer_ToggleDispenser = { + if (self.has_dispenser) { + DestroyBuilding(self, "building_dispenser"); + } else { + if (self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't build while dead.\n"); + } else { + Menu_Engineer_Input(2); + } + } +} + +void () FO_Engineer_ToggleSentry = { + if (self.has_sentry) { + Menu_Engineer_Input(3); + } else { + if (self.health <= 0) { + sprint(self, PRINT_HIGH, "Can't build while dead.\n"); + } else { + Menu_Engineer_Input(1); + } + } +} + void () TeamFortress_EngineerBuild = { - if (self.is_building == 0) { + if (!self.is_building) { if (((self.ammo_cells < 100) && !self.has_dispenser) && !self.has_sentry) { Status_Print(self, "\n\n\n\n\n\n\n", "Not enough metal to build anything"); return; } Menu_Engineer(self); - } else if (self.is_building == 1) { + } else if (self.is_building) { TeamFortress_EngineerBuildStop(); } }; @@ -257,95 +499,51 @@ void () TeamFortress_EngineerBuildStop = { local entity te; local vector dist; - self.tfstate = self.tfstate - TFSTATE_CANT_MOVE; - self.movetype = MOVETYPE_WALK; - TeamFortress_SetSpeed(self); - te = find(world, netname, "build_timer"); - while (te) { - if (te.owner == self) { - dist = self.origin - te.origin; - if (vlen(dist) > 128) { - sprint(self, PRINT_HIGH, "Your building disappeared\n"); - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, te.origin_x); - WriteCoord(MSG_MULTICAST, te.origin_y); - WriteCoord(MSG_MULTICAST, te.origin_z); - multicast(te.origin, MULTICAST_PHS); - } else { - sprint(self, PRINT_HIGH, "You stop building\n"); - } - dremove(te); - te = world; + te = FO_FindCurrentBuildingObject(); + if (te != world) { + dist = self.origin - te.origin; + if (vlen(dist) > 128) { + sprint(self, PRINT_HIGH, "Your building disappeared\n"); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_EXPLOSION); + WriteCoord(MSG_MULTICAST, te.origin_x); + WriteCoord(MSG_MULTICAST, te.origin_y); + WriteCoord(MSG_MULTICAST, te.origin_z); + multicast(te.origin, MULTICAST_PHS); } else { - te = find(te, netname, "build_timer"); + sprint(self, PRINT_HIGH, "You stop building\n"); } + if (engineer_move) { + if (te.classname == "building_sentrygun") + self.has_sentry = 0; + else if (te.classname == "building_dispenser") + self.has_dispenser = 0; + } + dremove(te); + te = world; + } + Menu_Close(self); - self.is_building = 0; + self.is_building = FALSE; self.building_percentage = 0; - self.current_weapon = self.weapon; - W_SetCurrentAmmo(self); + if (!engineer_move) { + self.tfstate &= ~(TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); + self.movetype = MOVETYPE_WALK; + } } -float (entity obj, entity builder) CheckArea = { - local vector src; - local vector end; - local float pos; - local entity te; +void (float objtobuild, float offset) TeamFortress_Build = { + if (cb_prematch) { + sprint(self, PRINT_MEDIUM, "You cannot build during prematch\n"); + return; + } - pos = pointcontents(obj.origin); - if ((pos == -2) || (pos == -6)) { - return (0); - } - src_x = (obj.origin_x + obj.maxs_x) + 24; - src_y = (obj.origin_y + obj.maxs_y) + 24; - src_z = (obj.origin_z + obj.maxs_z) + 16; - pos = pointcontents(src); - if ((pos == -2) || (pos == -6)) { - return (0); - } - end_x = (obj.origin_x + obj.mins_x) - 16; - end_y = (obj.origin_y + obj.mins_y) - 16; - end_z = (obj.origin_z + obj.mins_z) - 16; - traceline(src, end, 1, obj); - if (trace_fraction != 1) { - return (0); - } - pos = pointcontents(end); - if ((pos == -2) || (pos == -6)) { - return (0); - } - src_x = (obj.origin_x + obj.mins_x) - 16; - src_y = (obj.origin_y + obj.maxs_y) + 16; - src_z = (obj.origin_z + obj.maxs_z) + 16; - pos = pointcontents(src); - if ((pos == -2) || (pos == -6)) { - return (0); - } - end_x = (obj.origin_x + obj.maxs_x) + 16; - end_y = (obj.origin_y + obj.mins_y) - 16; - end_z = (obj.origin_z + obj.mins_z) - 16; - traceline(src, end, 1, obj); - if (trace_fraction != 1) { - return (0); - } - pos = pointcontents(end); - if ((pos == -2) || (pos == -6)) { - return (0); - } - traceline(builder.origin, obj.origin, 1, builder); - if (trace_fraction != 1) { - return (0); - } - te = findradius(obj.origin, 64); - if (te != world) { - return (0); + if(no_fire_mode) { + sprint(self, PRINT_MEDIUM, "You cannot build right now\n"); + return; } - return (1); -}; -void (float objtobuild) TeamFortress_Build = { local float btime; local vector tmp1; local vector tmp2; @@ -355,16 +553,26 @@ void (float objtobuild) TeamFortress_Build = { tmp2 = '0 0 0'; newmis = spawn(); + makevectors(self.v_angle); + /* v_forward_z = 0; */ + /* v_forward = normalize(v_forward) * 64; */ + /* newmis.origin = self.origin + v_forward; */ + + local vector v_forward_sentry; + v_forward_sentry.z = (normalize(v_forward) * 64).z; v_forward_z = 0; - v_forward = normalize(v_forward) * 64; - newmis.origin = self.origin + v_forward; + local vector xy_pos = normalize(v_forward) * 64; + v_forward_sentry.x = xy_pos.x; + v_forward_sentry.y = xy_pos.y; + + newmis.origin = self.origin + v_forward_sentry; tmp1 = newmis.origin; tmp2 = newmis.origin - normalize(v_up) * 128; traceline(tmp1, tmp2, 1, world); if (trace_ent != world) { - sprint(self, PRINT_HIGH, "You cannot build here\n"); + sprint(self, PRINT_HIGH, "You can't build here\n"); dremove(newmis); return; } @@ -378,7 +586,7 @@ void (float objtobuild) TeamFortress_Build = { return; } - if (objtobuild == 1) { + if (objtobuild == BUILD_DISPENSER) { if (self.has_dispenser) { sprint(self, PRINT_HIGH, "You can only have one dispenser\n"); dremove(newmis); @@ -390,7 +598,7 @@ void (float objtobuild) TeamFortress_Build = { newmis.netname = "dispenser"; btime = time + 2; self.dispenser_ticks = 0; - } else if (objtobuild == 2) { + } else if (objtobuild == BUILD_SENTRYGUN) { if (self.has_sentry) { sprint(self, PRINT_HIGH, "You can only have one sentry gun\n"); dremove(newmis); @@ -403,8 +611,11 @@ void (float objtobuild) TeamFortress_Build = { btime = time + 5; self.sentry_ticks = 0; } - if (CheckArea(newmis, self) == 0) { - sprint(self, PRINT_HIGH, "Not enough room to build here\n"); + + setsize(newmis, tmp1, tmp2); + + if (!PlaceSentry(newmis, self.origin)) { + sprint(self, PRINT_HIGH, "You can't build here\n"); dremove(newmis); return; } @@ -413,23 +624,23 @@ void (float objtobuild) TeamFortress_Build = { sprint(self, PRINT_HIGH, "You cannot build in the water\n"); dremove(newmis); return; - } else if (!self.waterlevel) { - sprint(self, PRINT_HIGH, "You cannot build in the air\n"); - dremove(newmis); - return; } } - self.is_building = 1; - self.immune_to_check = time + 5; - self.tfstate = self.tfstate | TFSTATE_CANT_MOVE; - self.movetype = MOVETYPE_NONE; - self.weapon = self.current_weapon; - self.current_weapon = 0; - self.weaponmodel = ""; - self.weaponframe = 0; - TeamFortress_SetSpeed(self); - Status_Refresh(self); - Menu_Engineer_Cancel(); + + UpdateClientBuilding(self); + + if (objtobuild == BUILD_SENTRYGUN) { + UpdateClient_Sentry(self, newmis); + } + + self.is_building = objtobuild; + if (!engineer_move) { + self.immune_to_check = time + 5; + self.tfstate |= TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON; + self.movetype = MOVETYPE_NONE; + + Menu_Engineer_Cancel(); + } newmis.owner = self; newmis.classname = "timer"; @@ -438,15 +649,39 @@ void (float objtobuild) TeamFortress_Build = { newmis.think = TeamFortress_FinishedBuilding; newmis.colormap = self.colormap; newmis.weapon = objtobuild; - newmis.angles_y = anglemod(self.angles_y + 180); + newmis.angles_y = anglemod(self.angles_y + 180 + offset); newmis.velocity = '0 0 8'; newmis.movetype = 6; newmis.solid = 2; - setmodel(newmis, newmis.mdl); - setsize(newmis, tmp1, tmp2); + FO_SetModel(newmis, newmis.mdl); setorigin(newmis, newmis.origin); newmis.flags = newmis.flags - (newmis.flags & 512); + if (engineer_move) { + newmis.owner = world; + newmis.real_owner = self; + newmis.health = 50; + newmis.team_no = self.team_no; + newmis.max_health = newmis.health; + newmis.takedamage = DAMAGE_AIM; + if (objtobuild == BUILD_DISPENSER) { + self.has_dispenser = TRUE; + newmis.th_die = Dispenser_Die; + self.ammo_cells = self.ammo_cells - ENG_DISPENSER_COST; + newmis.classname = "building_dispenser"; + } + if (objtobuild == BUILD_SENTRYGUN) { + self.sentry_ent = newmis; + self.has_sentry = TRUE; + newmis.th_die = Sentry_Die; + newmis.th_pain = Sentry_Pain; + newmis.takedamage = DAMAGE_AIM; + self.ammo_cells = self.ammo_cells - ENG_SENTRY_COST; + newmis.classname = "building_sentrygun"; + newmis.weapon = 0; + } + } + oldmis = newmis; newmis = spawn(); newmis.owner = self; @@ -455,27 +690,31 @@ void (float objtobuild) TeamFortress_Build = { newmis.netname = "buildcheck_timer"; newmis.nextthink = time + 0.3; newmis.think = CF_CheckBuilding; + Status_Refresh(self); }; void () CF_CheckBuilding = { + local float max_dist = BUILD_SENTRYGUN_MAX_DISTANCE; local vector dist; local entity timer = self; local entity building = self.enemy; self = self.owner; - if (self.is_building == 0) { + if (!self.is_building) { dremove(timer); return; } - dist = self.origin - building.origin; - if (vlen(dist) > 128) { - TeamFortress_EngineerBuildStop(); - dremove(timer); - return; + if (!engineer_move) { + dist = self.origin - building.origin; + if (vlen(dist) > max_dist) { + TeamFortress_EngineerBuildStop(); + dremove(timer); + return; + } } - if (building.weapon == 2) { + if (building.weapon == 2 || building.weapon == 0) { self.sentry_ticks = self.sentry_ticks + 0.3; self.building_percentage = ceil((self.sentry_ticks / 5) * 100); } else if (building.weapon == 1) { @@ -672,7 +911,7 @@ void (entity disp, entity pl) Dispenser_StockPlayer = { // only play dispenser restock sound if something was given if ((shells + nails + rockets + cells + armor) > 0) - sound(pl, 3, "items/r_item1.wav", 0.7, 1); + FO_Sound(pl, CHAN_ITEM, "items/r_item1.wav", 0.7, 1); pl.duse = time; }; @@ -696,30 +935,55 @@ void () DispenserThink = { self.nextthink = time + 0.3; }; -void () TeamFortress_FinishedBuilding = { - local entity oldself; - - if (self.owner.is_building != 1) { +void UpdateClient_Dispenser(entity pl, entity disp) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_DISPENSER_POS); + WriteFloat(MSG_MULTICAST, disp.origin.x); + WriteFloat(MSG_MULTICAST, disp.origin.y); + WriteFloat(MSG_MULTICAST, disp.origin.z); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void () TeamFortress_FinishedBuilding = { + local entity oldself; + if (engineer_move) { + if (!self.real_owner.is_building) { + return; + } } + else { + if (!self.owner.is_building) { + return; + } + } + if (engineer_move) + self.owner = self.real_owner; + + local float csqcactive = infokeyf(self.owner, INFOKEY_P_CSQCACTIVE); + oldself = self; self = self.owner; oldself.owner = world; oldself.real_owner = self; self.is_building = FALSE; self.building_percentage = 0; - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - self.movetype = MOVETYPE_WALK; - self.current_weapon = self.weapon; + if (!engineer_move) { + self.tfstate &= ~(TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); + self.movetype = MOVETYPE_WALK; + } Status_Refresh(self); - TeamFortress_SetSpeed(self); + Menu_Close(self); if (oldself.weapon == 1) { self.has_dispenser = TRUE; sprint(self, PRINT_HIGH, "You finish building the dispenser\n"); teamsprint(self.team_no, self, self.netname); teamsprint(self.team_no, self, " has built a dispenser\n"); - self.ammo_cells = self.ammo_cells - 100; + if (!engineer_move) + self.ammo_cells = self.ammo_cells - ENG_DISPENSER_COST; oldself.classname = "building_dispenser"; oldself.netname = "dispenser"; oldself.blocked = T_Dispenser; @@ -750,10 +1014,13 @@ void () TeamFortress_FinishedBuilding = { self.ammo_cells = ceil(self.ammo_cells * 0.75); self.armorvalue = ceil(self.armorvalue * 0.75); oldself.solid = SOLID_BBOX; - setmodel(oldself, oldself.mdl); + FO_SetModel(oldself, oldself.mdl); setsize(oldself, '-8 -8 0', '8 8 24'); setorigin(oldself, (oldself.origin + '0 0 8')); - } else if (oldself.weapon == 2) { + if(csqcactive) { + UpdateClient_Dispenser(self, oldself); + } + } else if (oldself.weapon == 2 || (engineer_move && oldself.weapon == 0)) { self.has_sentry = TRUE; sprint(self, PRINT_HIGH, "You finish building the sentry gun\n"); teamsprint(self.team_no, self, self.netname); @@ -762,8 +1029,10 @@ void () TeamFortress_FinishedBuilding = { oldself.netname = "sentry gun"; oldself.takedamage = DAMAGE_NO; oldself.th_die = Sentry_Die; - oldself.team_no = self.team_no; - self.ammo_cells = self.ammo_cells - 130; + oldself.team_no = self.team_no; + oldself.nextthink = time + .1; + if (!engineer_move) + self.ammo_cells = self.ammo_cells - ENG_SENTRY_COST; setsize(oldself, '-16 -16 0', '16 16 4'); newmis = spawn(); newmis.classname = "building_sentrygun"; @@ -773,10 +1042,12 @@ void () TeamFortress_FinishedBuilding = { newmis.th_die = Sentry_Die; newmis.th_pain = Sentry_Pain; newmis.mdl = "progs/turrgun.mdl"; + newmis.dimension_ghost = DMN_GHOST; + newmis.dimension_ghost_alpha = 0.25; self.sentry_ent = newmis; - sound(oldself, 3, "weapons/turrset.wav", 1, 1); + FO_Sound(oldself, CHAN_ITEM, "weapons/turrset.wav", 1, 1); newmis.solid = SOLID_BBOX; - setmodel(newmis, newmis.mdl); + FO_SetModel(newmis, newmis.mdl); setsize(newmis, '-16 -16 0', '16 16 48'); setorigin(newmis, oldself.origin + '0 0 8'); newmis.view_ofs = '0 0 22'; @@ -809,8 +1080,10 @@ void () TeamFortress_FinishedBuilding = { newmis.ammo_shells = 25; newmis.maxammo_shells = 100; newmis.maxammo_rockets = 20; + if(csqcactive) { + UpdateClient_Sentry(self, oldself); + } } - W_SetCurrentAmmo(self); self = oldself; }; @@ -830,12 +1103,14 @@ void () T_Dispenser = { if ((other.building == world) && (other.building_wait < time)) { other.building = self; - dist_checker = spawn(); - dist_checker.classname = "timer"; - dist_checker.owner = other; - dist_checker.enemy = self; - dist_checker.think = CheckDistance; - dist_checker.nextthink = time + 0.3; + if(!infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + dist_checker = spawn(); + dist_checker.classname = "timer"; + dist_checker.owner = other; + dist_checker.enemy = self; + dist_checker.think = CheckDistance; + dist_checker.nextthink = time + 0.3; + } oldself = self; self = other; Menu_Dispenser(); @@ -847,17 +1122,10 @@ void () Dispenser_Explode = { local float sdmg; if (self.real_owner.has_disconnected != 1) { - deathmsg = 39; - if (disp_explosion) { - sdmg = 45 + self.ammo_rockets * 3 + self.ammo_cells; - if (sdmg > 350) { - sdmg = 350; - } - } else { - sdmg = 25 + self.ammo_rockets * 3 + self.ammo_cells; - if (sdmg > 250) { - sdmg = 250; - } + deathmsg = DMSG_DISPENSER_EXPLODE; + sdmg = 25 + self.ammo_rockets * 3 + self.ammo_cells; + if (sdmg > 250) { + sdmg = 250; } T_RadiusDamage(self, self.real_owner, sdmg, self); } @@ -878,10 +1146,39 @@ void () Dispenser_Die = { self.real_owner.has_dispenser = 0; self.think = Dispenser_Explode; self.nextthink = time + 0.1; + if (self.real_owner.is_building == BUILD_DISPENSER) { + self = self.real_owner; + TeamFortress_EngineerBuildStop(); + } }; void (entity disp) Engineer_UseDispenser = { local entity dist_checker; + // engineer hits enemy dispenser - dismantle + if(!(disp.team_no > 0 && disp.team_no == self.team_no)) { + self.ammo_cells = self.ammo_cells + 50; + disp.real_owner.has_dispenser = 0; + bprint(PRINT_HIGH, self.netname, " dismantled ", disp.real_owner.netname, "'s dispenser\n"); + TF_AddFrags(self, 1); + sprint(disp.real_owner, PRINT_HIGH, "Your dispenser was dismantled by ",self.netname,"\n"); + dremove (disp); + ThrowGib("progs/dgib1.mdl", -30); + ThrowGib("progs/dgib2.mdl", -50); + ThrowGib("progs/dgib3.mdl", -50); + return; + } + + if (self.team_no == disp.team_no) { + if (disp.real_owner.is_building) { + sprint (self, PRINT_HIGH, strcat("You stopped building a dispenser and got ", ftos(ENG_DISPENSER_COST), " cells back\n")); + self.ammo_cells = self.ammo_cells + ENG_DISPENSER_COST; + if (self.ammo_cells > self.maxammo_cells) + self.ammo_cells = self.maxammo_cells; + self = disp.real_owner; + TeamFortress_EngineerBuildStop(); + return; + } + } if (disp.health < disp.max_health && !old_spanner) { Engineer_Dispenser_Repair(disp); @@ -905,12 +1202,14 @@ void (entity disp) Engineer_UseDispenser = { self.building = disp; Menu_EngineerFix_Dispenser(); - dist_checker = spawn(); - dist_checker.classname = "timer"; - dist_checker.owner = self; - dist_checker.enemy = disp; - dist_checker.think = CheckDistance; - dist_checker.nextthink = time + 0.3; + if(!infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + dist_checker = spawn(); + dist_checker.classname = "timer"; + dist_checker.owner = self; + dist_checker.enemy = disp; + dist_checker.think = CheckDistance; + dist_checker.nextthink = time + 0.3; + } }; void (entity disp) Engineer_Dispenser_InsertAmmo = { @@ -949,7 +1248,6 @@ void (entity disp) Engineer_Dispenser_InsertAmmo = { disp.ammo_cells = (disp.ammo_cells + cells); if ((shells + nails + rockets + cells) > 0) { - W_SetCurrentAmmo(self); sprint(self, PRINT_HIGH, "You insert "); if (shells > 0) sprint(self, PRINT_HIGH, ftos(shells), " shells"); @@ -992,7 +1290,7 @@ void (entity disp) Engineer_Dispenser_InsertArmor = { if (self.armorvalue == 0) { self.armortype = 0; self.armorclass = 0; - self.items = (self.items - (self.items & ((IT_ARMOR1 | IT_ARMOR2) | IT_ARMOR3))); + self.items &= ~(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3); } if (armor > 0) @@ -1002,39 +1300,73 @@ void (entity disp) Engineer_Dispenser_InsertArmor = { }; void (entity disp) Engineer_Dispenser_Repair = { - local float metalcost, healamount; + local float metalcost, healamount, ratio; if (disp.health == disp.max_health) return; - metalcost = ceil((disp.max_health - disp.health) / 5); + ratio = (fo_repair_ratio == 0 ? 5 : fo_repair_ratio); + + metalcost = ceil((disp.max_health - disp.health) / ratio); if (metalcost > self.ammo_cells) metalcost = self.ammo_cells; self.ammo_cells = self.ammo_cells - metalcost; - healamount = floor(metalcost * 5); + healamount = floor(metalcost * ratio); disp.health = disp.health + healamount; if (metalcost > 0) { Status_Print(self, "\n\n\n\n\n\n\n", ftos(healamount), " hp repaired"); - sound(self, 3, "items/r_item2.wav", 1, 1); + FO_Sound(self, CHAN_ITEM, "items/r_item2.wav", 1, 1); } }; void (entity gun) Engineer_UseSentryGun = { + self.building = gun; // automate tasks if old_spanner setting is disabled if (!old_spanner) { - if (gun.weapon < 3 && self.ammo_cells >= 130) { - Engineer_SentryGun_Upgrade(gun); - return; - } else if (gun.health < gun.max_health && self.ammo_cells > 0) { - Engineer_SentryGun_Repair(gun); - return; - } else if ((gun.ammo_shells < gun.maxammo_shells && self.ammo_shells > 0) - || (gun.weapon == 3 && gun.ammo_rockets < gun.maxammo_rockets - && self.ammo_rockets > 0)) { - Engineer_SentryGun_InsertAmmo(gun); + if(gun.team_no > 0 && gun.team_no == self.team_no) { + if (gun.weapon > 0) { + //Menu_EngineerFix_SentryGun_Rotate(); + Engineer_SentryGun_ShowMenu(self.building); + if (gun.weapon < 3 && self.ammo_cells >= ENG_SENTRY_COST) { + Engineer_SentryGun_Upgrade(gun); + return; + } else if (gun.health < gun.max_health && self.ammo_cells > 0) { + Engineer_SentryGun_Repair(gun); + return; + } else if ((gun.ammo_shells < gun.maxammo_shells && self.ammo_shells > 0) + || (gun.weapon == 3 && gun.ammo_rockets < gun.maxammo_rockets + && self.ammo_rockets > 0)) { + Engineer_SentryGun_InsertAmmo(gun); + return; + } + } else { + sprint (self, PRINT_HIGH, strcat("You stopped building a sentry gun and got ", ftos(ENG_SENTRY_COST), " cells back\n")); + self.ammo_cells = self.ammo_cells + ENG_SENTRY_COST; + if (self.ammo_cells > self.maxammo_cells) + self.ammo_cells = self.maxammo_cells; + if (gun.trigger_field != world) + dremove (gun.trigger_field); + self = gun.real_owner; + TeamFortress_EngineerBuildStop(); + return; + } + } else { // hit enemy sentry + self.ammo_cells = self.ammo_cells + 65; + if (self.ammo_cells > self.maxammo_cells) + self.ammo_cells = self.maxammo_cells; + gun.real_owner.has_sentry = 0; + bprint(PRINT_HIGH, self.netname, " dismantled ", gun.real_owner.netname, "'s sentry gun\n"); + TF_AddFrags(self, 1); + sprint(gun.real_owner, PRINT_HIGH, "Your sentry gun was dismantled by ",self.netname,"\n"); + if (gun.trigger_field != world) + dremove (gun.trigger_field); + dremove (gun); + ThrowGib("progs/tgib1.mdl", -30); + ThrowGib("progs/tgib2.mdl", -50); + ThrowGib("progs/tgib3.mdl", -50); return; } } @@ -1065,19 +1397,20 @@ void (entity gun) Engineer_UseSentryGun = { } sprint(self, PRINT_HIGH, "\n"); - self.building = gun; Engineer_SentryGun_ShowMenu(self.building); }; void (entity gun) Engineer_SentryGun_ShowMenu = { local entity dist_checker; - dist_checker = spawn(); - dist_checker.classname = "timer"; - dist_checker.owner = self; - dist_checker.enemy = gun; - dist_checker.think = CheckDistance; - dist_checker.nextthink = time + 0.3; + if(!infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + dist_checker = spawn(); + dist_checker.classname = "timer"; + dist_checker.owner = self; + dist_checker.enemy = gun; + dist_checker.think = CheckDistance; + dist_checker.nextthink = time + 0.3; + } Menu_EngineerFix_SentryGun(); }; @@ -1104,7 +1437,6 @@ void (entity gun) Engineer_SentryGun_InsertAmmo = { } if ((shells + rockets) > 0) { - W_SetCurrentAmmo(self); ammo = "You insert "; if (shells > 0) ammo = strcat(ammo, strcat(ftos(floor(shells)), " shells")); @@ -1115,7 +1447,7 @@ void (entity gun) Engineer_SentryGun_InsertAmmo = { } ammo = strcat(ammo, " into sentry gun"); Status_Print(self, "\n\n\n\n\n\n\n", ammo); - sound(self, 3, "edge/backpack.wav", 1, 1); + FO_Sound(self, CHAN_ITEM, "edge/backpack.wav", 1, 1); } }; @@ -1129,11 +1461,11 @@ void (entity gun) Engineer_SentryGun_Upgrade = { gun.health = gun.max_health; gun.maxammo_shells = gun.maxammo_shells * 1.2; if (gun.weapon == 2) { - sound(gun, 3, "weapons/turrset.wav", 1, 1); + FO_Sound(gun, CHAN_ITEM, "weapons/turrset.wav", 1, 1); gun.think = lvl2_sentry_stand; gun.skin = 1; } else { - sound(gun, 3, "weapons/turrset.wav", 1, 1); + FO_Sound(gun, CHAN_ITEM, "weapons/turrset.wav", 1, 1); gun.think = lvl3_sentry_stand; gun.skin = 2; } @@ -1141,14 +1473,16 @@ void (entity gun) Engineer_SentryGun_Upgrade = { }; void (entity gun) Engineer_SentryGun_Repair = { - local float metalcost, healamount; + local float metalcost, healamount, ratio; - metalcost = ceil((gun.max_health - gun.health) / 5); + ratio = (fo_repair_ratio == 0 ? 5 : fo_repair_ratio); + + metalcost = ceil((gun.max_health - gun.health) / ratio); if (metalcost > self.ammo_cells) metalcost = self.ammo_cells; self.ammo_cells = self.ammo_cells - metalcost; - healamount = floor(metalcost * 5); + healamount = floor(metalcost * ratio); gun.health = gun.health + healamount; if (gun.health > gun.max_health) @@ -1156,7 +1490,7 @@ void (entity gun) Engineer_SentryGun_Repair = { if (metalcost > 0) { Status_Print(self, "\n\n\n\n\n\n\n", ftos(healamount), " hp repaired"); - sound(self, 3, "items/r_item2.wav", 1, 1); + FO_Sound(self, CHAN_ITEM, "items/r_item2.wav", 1, 1); } }; @@ -1180,7 +1514,7 @@ void () CheckDistance = { return; } dist = self.enemy.origin - self.owner.origin; - if (vlen(dist) > 80) { + if (vlen(dist) > ENG_BUILDING_MAINT_DISTANCE) { Menu_Close(self.owner); self.owner.building = world; dremove(self); @@ -1189,7 +1523,34 @@ void () CheckDistance = { self.nextthink = time + 0.3; }; -void (entity eng, string bld) DestroyBuilding = { +float IsEngEnt(entity ent) { + return ent.classname == "building_dispenser" || + ent.classname == "building_sentrygun"; + +} + +void RemoveEngEnt(entity bld, float explode) { + entity owner = bld.real_owner; + if (owner.building == bld) { + Menu_Close(owner); + owner.building = world; + } + + if (explode) { + TF_T_Damage(bld, world, world, 500, 0, 0); + } else { + if(bld.classname == "building_dispenser") + owner.has_dispenser = 0; + if(bld.classname == "building_sentrygun") { + owner.has_sentry = 0; + if (bld.trigger_field != world) + dremove (bld.trigger_field); + } + dremove (bld); + } +} + +void (entity eng, string bld, float explode) DestroyBuildingWithOptions = { local entity te; local entity oldself; local float pos; @@ -1203,20 +1564,32 @@ void (entity eng, string bld) DestroyBuilding = { self = eng; self.ammo_cells = self.ammo_cells + 100; bound_other_ammo(self); - W_SetCurrentAmmo(self); self = oldself; } - if (te.real_owner.building == te) { - Menu_Close(te.real_owner); - te.real_owner.building = world; - } - TF_T_Damage(te, world, world, 500, 0, 0); + + spawn_tfog(te.origin); + RemoveEngEnt(te, explode); + } te = find(te, classname, bld); } }; +void (entity eng, string bld) DestroyBuilding = { + DestroyBuildingWithOptions(eng, bld, TRUE); +} + +void (entity eng) Engineer_QuietlyRemoveBuildings = { + DestroyBuildingWithOptions(eng, "building_dispenser", FALSE); + DestroyBuildingWithOptions(eng, "building_sentrygun", FALSE); +}; + void (entity eng) Engineer_RemoveBuildings = { DestroyBuilding(eng, "building_dispenser"); DestroyBuilding(eng, "building_sentrygun"); }; + +void () info_empblock = { + self.weapon = 512; + self.netname = "EMP Block"; +}; diff --git a/ssqc/events.qc b/ssqc/events.qc new file mode 100644 index 000000000..e4878402e --- /dev/null +++ b/ssqc/events.qc @@ -0,0 +1,426 @@ +string (string text) clearString; +string (float tno) GetTeamName; + +float (entity player) isPlayerValid = { + if (player.has_disconnected == 1 + || !infokey(player, INFOKEY_P_USERID) + || infokey(player, INFOKEY_P_USERID) == "" + || !infokey(player, INFOKEY_P_NAME) + || infokey(player, INFOKEY_P_NAME) == "") { + return 0; + } + return 1; +} + +string () ISOTimemillis = { + string padding = string_null; + string timeuse = strftime(0,"%Y-%m-%dT%H:%M:%S."); + float randmillis = rint((random()*1000*random())); + if (randmillis < 10) + padding = "00"; + else if (randmillis < 100) + padding = "0"; + string padded = strcat(padding, ftos(randmillis)); + timeuse = strcat(timeuse, padded); + timeuse = strcat(timeuse, "Z"); + return timeuse; +} + +string (entity pl) getEntityNameOrLogin = { + if (loginRequired) + return pl.login; + else if (fo_login_required) + return pl.fo_login; + else + return clearString(pl.netname); +} + +void (string evt) logevent = { + string st = infokey (world, "event_debug"); + if (stof(st) > 0) + bprint(PRINT_HIGH, evt, "\n"); + fputs(logfilehandle, evt); +} + +void (entity player) LogEventPlayerStart = { + if (canlog == 0) + return; + if (isPlayerValid(player) == FALSE) + return; + + string event; + event = sprintf(",\n{\"type\": \"playerStart\", \"player\": \"%s\", \"classtime\": %s, \"time\": %s, \"gameTimeStamp\": \"%s\"}", getEntityNameOrLogin(player), ftos(player.classtime), ftos(gametime), gametimestamp); + logevent(event); +} + +void () LogEventGameStart = { + gametimestamp = ISOTimemillis(); + if (canlog == 0) + return; + string event; + entity player; + float numplayers = 0; + player = find (world, classname, "player"); + + while (player) { + if (isPlayerValid(player) == TRUE) + numplayers++; + player = find (player, classname, "player"); + } + + event = sprintf("{\"type\": \"gameStart\", \"map\": \"%s\", \"numPlayers\": %s, \"numTeams\": %s, \"time\": %s, \"demo\": \"%s\", \"gameTimeStamp\": \"%s\"}", mapname, ftos(numplayers), ftos(number_of_teams), ftos(gametime), cvar_string("serverdemo"), gametimestamp); + logevent(event); +} + +void (entity player, float previous, float next, float timeplayed) LogEventChangeClass = { + if (canlog == 0) + return; + if (isPlayerValid(player) == FALSE) + return; + if (previous == 0) + return; + + string event; + event = sprintf(",\n{\"type\": \"changeClass\", \"player\": \"%s\", \"playerClass\": %s, \"nextClass\": %s, \"team\": %s, \"timePlayed\": %s, \"time\": %s, \"gameTimeStamp\": \"%s\"}", + getEntityNameOrLogin(player), + ftos(previous), + ftos(next), + ftos(player.team_no), + ftos(timeplayed), + ftos(gametime), + gametimestamp + ); + logevent(event); +} + +string (float weapon) GetWeaponName = { + if (weapon >= 1 && weapon <= WEAP_LAST) { + string name = FO_GetWeapName(weapon); + name = strtolower(name); + name = strreplace(" ", "", name); + return name; + } + + return "undefined"; +} + +void (entity attacker, entity target, string afflictionType) LogAffliction = { + if (attacker.classname == "player" && target.classname == "player") { + if (attacker.team_no == target.team_no) + attacker.afflicted = attacker.afflicted + 1; + else + attacker.teamafflicted = attacker.teamafflicted + 1; + } +} + +void (entity attacker, entity target, entity inflictor, float damage, float truedam) LogEventDamage = { + if (!cb_prematch) { + entity realattacker = attacker; + if (attacker.classname == "building_sentrygun" || attacker.classname == "building_dispenser") + realattacker = attacker.real_owner; + + if (realattacker.team_no != target.team_no) { + if (realattacker != target && realattacker != target.real_owner) { + if (realattacker.classname == "player" && (target.classname == "player" || target.classname == "building_sentrygun")) + realattacker.damagegiven = realattacker.damagegiven + truedam; + + if (target.classname == "player") + target.damagetaken = target.damagetaken + truedam; + } + } + } + + if (canlog == 0) + return; + + string event; + string attackEvent; + string targetEvent; + string part1attack; + string part1target; + string part2; + string damageKind; + string attackername; + string targetname; + string inflictorId; + + attackername = getEntityNameOrLogin(attacker); + targetname = getEntityNameOrLogin(target); + if (attacker == target) + damageKind = "self"; + else if (attacker.team_no == target.team_no) + damageKind = "team"; + else + damageKind = "enemy"; + + + if (inflictor.classname == "player") { + inflictorId = GetWeaponName(FO_PlayerCurrentWeapon(attacker)); + } else { + inflictorId = inflictor.classname; + if ((inflictor.classname == "spike") && (attacker.playerclass == 3)) + inflictorId = "nailgrenspike"; + else if ((inflictor.classname == "spike") && (attacker.playerclass == 5)) + inflictorId = "superspike"; + + if (inflictorId == "worldspawn") { + attackername = "world"; + } else if (inflictorId == "building_sentrygun") { + attackername = getEntityNameOrLogin(attacker.real_owner); + if (damageKind == "damageTeam") + return; + } + else if (inflictorId == "grenade" && inflictor.fpp.gren_type >= GREN_FIRST) + inflictorId = FO_GrenDesc(inflictor.fpp.gren_type)->logname; + } + + if (getEntityNameOrLogin(target) != "") { + part1attack = sprintf(",\n{\"type\": \"damageDone\", \"kind\": \"%s\", \"player\": \"%s\", \"playerClass\": %s, \"playerTeam\": %s, \"target\": \"%s\", \"targetClass\": %s, \"targetTeam\": %s, ", + damageKind, + attackername, + ftos(attacker.playerclass), + ftos(attacker.team_no), + getEntityNameOrLogin(target), + ftos(target.playerclass), + ftos(target.team_no)); + part1target = sprintf(",\n{\"type\": \"damageTaken\", \"kind\": \"%s\", \"attacker\": \"%s\", \"attackerClass\": %s, \"attackerTeam\": %s, \"player\": \"%s\", \"playerClass\": %s, \"playerTeam\": %s, ", + damageKind, + attackername, + ftos(attacker.playerclass), + ftos(attacker.team_no), + getEntityNameOrLogin(target), + ftos(target.playerclass), + ftos(target.team_no)); + + + part2 = sprintf("\"inflictor\": \"%s\", \"damage\": %s, \"time\": %s, \"gameTimeStamp\": \"%s\"}", + inflictorId, + ftos(truedam), + ftos(gametime), + gametimestamp + ); + + attackEvent = strcat(part1attack, part2); + targetEvent = strcat(part1target, part2); + event = strcat(attackEvent, targetEvent); + logevent(event); + } +} + +void (entity attacker, entity target, entity inflictor) LogEventKill = { + if (!cb_prematch) { + entity realattacker = attacker; + if (attacker.classname == "building_sentrygun" || attacker.classname == "building_dispenser") + realattacker = attacker.real_owner; + + if (realattacker.classname == "player" && (target.classname == "player" || target.classname == "building_sentrygun")) { + if (realattacker != target && realattacker != target.real_owner) + { + if (realattacker.team_no == target.team_no) + realattacker.sbteamkills = realattacker.sbteamkills + 1; + else + realattacker.kills = realattacker.kills + 1; + } + + target.deaths = target.deaths + 1; + } + } + + if (canlog == 0) + return; + + if ((target.classname != "player") && (target.classname != "building_sentrygun")) + return; + + string killKind; + string attackername; + string targetname; + string inflictorId; + + attackername = getEntityNameOrLogin(attacker); + targetname = getEntityNameOrLogin(target); + if (attacker == target) + killKind = "self"; + else if (attacker.team_no == target.team_no) + killKind = "team"; + else + killKind = "enemy"; + + if (inflictor.classname == "player") { + inflictorId = GetWeaponName(FO_PlayerCurrentWeapon(attacker)); + } else { + inflictorId = inflictor.classname; + if ((inflictor.classname == "spike") && (attacker.playerclass == 3)) { + inflictorId = "nailgrenspike"; + } + else if ((inflictor.classname == "spike") && (attacker.playerclass == 5)) { + inflictorId = "superspike"; + } + + if (inflictorId == "worldspawn") + attackername = "world"; + else if (inflictorId == "building_sentrygun") + attackername = getEntityNameOrLogin(attacker.real_owner); + else if (inflictorId == "grenade" && inflictor.fpp.gren_type >= GREN_FIRST) + inflictorId = FO_GrenDesc(inflictor.fpp.gren_type)->logname; + } + + string event; + string attackEvent; + string targetEvent; + string part1attack; + string part1target; + string part2; + + part1attack = sprintf(",\n{\"type\": \"kill\", \"kind\": \"%s\", \"player\": \"%s\", \"playerClass\": %s, \"playerTeam\": %s, \"target\": \"%s\", \"targetClass\": %s, \"targetTeam\": %s, ", + killKind, + attackername, + ftos(attacker.playerclass), + ftos(attacker.team_no), + targetname, + ftos(target.playerclass), + ftos(target.team_no)); + part1target = sprintf(",\n{\"type\": \"death\", \"kind\": \"%s\", \"attacker\": \"%s\", \"attackerClass\": %s, \"attackerTeam\": %s, \"player\": \"%s\", \"playerClass\": %s, \"playerTeam\": %s, ", + killKind, + attackername, + ftos(attacker.playerclass), + ftos(attacker.team_no), + targetname, + ftos(target.playerclass), + ftos(target.team_no)); + part2 = sprintf("\"inflictor\": \"%s\", \"time\": %s, \"gameTimeStamp\": \"%s\"}", + inflictorId, + ftos(gametime), + gametimestamp + ); + attackEvent = strcat(part1attack, part2); + targetEvent = strcat(part1target, part2); + event = strcat(attackEvent, targetEvent); + logevent(event); +} + +void (entity attacker, entity target, float tfstate) LogEventAffliction = { + if (attacker != target) { + if (attacker.team_no == target.team_no) + attacker.teamafflicted = attacker.teamafflicted + 1; + else + attacker.afflicted = attacker.afflicted + 1; + } +}; + +void (entity player) LogEventGoal = { + if(player == world) + return; + + player.caps = player.caps + 1; + if (canlog == 0) + return; + + string event; + event = sprintf(",\n{\"type\": \"goal\", \"team\": %s, \"player\": \"%s\", \"playerClass\": \"%s\", \"time\": %s, \"gameTimeStamp\": \"%s\"}", + ftos(player.team_no), + getEntityNameOrLogin(player), + ftos(player.playerclass), + ftos(gametime), + gametimestamp); + logevent(event); +} + + +void (entity player) LogEventPickupGoal = { + player.touches = player.touches + 1; + if (canlog == 0) + return; + + string event; + event = sprintf(",\n{\"type\": \"pickup\", \"team\": %s, \"player\": \"%s\", \"playerClass\": \"%s\", \"time\": %s, \"gameTimeStamp\": \"%s\"}", + ftos(player.team_no), + getEntityNameOrLogin(player), + ftos(player.playerclass), + ftos(gametime), + gametimestamp); + logevent(event); +} + +void (entity player, float timecarried) LogEventFumble = { + if (canlog == 0) + return; + string event; + event = sprintf(",\n{\"type\": \"fumble\", \"team\": %s, \"player\": \"%s\", \"playerClass\": \"%s\", \"timeCarried\": %s, \"time\": %s, \"gameTimeStamp\": \"%s\"}", + ftos(player.team_no), + getEntityNameOrLogin(player), + ftos(player.playerclass), + ftos(timecarried), + ftos(gametime), + gametimestamp); + logevent(event); +} + +void (entity attacker) LogEventAttack = { + if (canlog == 0) + return; + string event; + event = sprintf(",\n{\"type\": \"attack\", \"player\": \"%s\", \"playerClass\": \"%s\", \"inflictor\": \"%s\", \"time\": %s, \"gameTimeStamp\": \"%s\"}", getEntityNameOrLogin(attacker), ftos(attacker.playerclass), GetWeaponName(FO_PlayerCurrentWeapon(attacker)), ftos(gametime), gametimestamp); + logevent(event); +} + +void () LogEventGameEnd = { + if (canlog == 0) + return; + string event; + event = sprintf(",\n{\"type\": \"gameEnd\", \"time\": %s, \"gameTimeStamp\": \"%s\"}", ftos(gametime), gametimestamp); + logevent(event); +} + +void () LogEventTeamScores = { + float win_score = 0; + float winning_team = 0; + + if (team1score > win_score) { + win_score = team1score; + winning_team = 1; + } + + if (team2score > win_score) { + win_score = team2score; + winning_team = 2; + } else if (team2score == win_score) { + winning_team = 0; + } + + if (team3score > win_score) { + win_score = team3score; + winning_team = 3; + } else if (team3score == win_score) { + winning_team = 0; + } + + if (team4score > win_score) { + win_score = team4score; + winning_team = 4; + } else if (team4score == win_score) { + winning_team = 0; + } + + string teamscores; + teamscores = sprintf("\"team1Score\": %s", ftos(team1score)); + if (number_of_teams > 1) + teamscores = strcat(teamscores, sprintf(", \"team2Score\": %s", ftos(team2score))); + if (number_of_teams > 2) + teamscores = strcat(teamscores, sprintf(", \"team3Score\": %s", ftos(team3score))); + if (number_of_teams > 3) + teamscores = strcat(teamscores, sprintf(", \"team4Score\": %s", ftos(team4score))); + + string teamnames; + teamnames = sprintf("\"team1Name\": \"%s\"", GetTeamName(1)); + if (number_of_teams > 1) + teamnames = strcat(teamnames, sprintf(", \"team2Name\": \"%s\"", GetTeamName(2))); + if (number_of_teams > 2) + teamnames = strcat(teamnames, sprintf(", \"team3Name\": \"%s\"", GetTeamName(3))); + if (number_of_teams > 3) + teamnames = strcat(teamnames, sprintf(", \"team4Name\": \"%s\"", GetTeamName(4))); + + string event; + event = sprintf(",\n{\"type\": \"teamScores\", %s, %s, \"winningTeam\": %s, \"time\": %s, \"gameTimeStamp\": \"%s\"}", teamscores, teamnames, ftos(winning_team), ftos(gametime), gametimestamp); + logevent(event); +} diff --git a/ssqc/extraents.qc b/ssqc/extraents.qc new file mode 100644 index 000000000..d60181afb --- /dev/null +++ b/ssqc/extraents.qc @@ -0,0 +1,170 @@ +void (float tno, float scoretoadd) TeamFortress_TeamIncreaseScore; +void (float all) TeamFortress_TeamShowScores; +void () InitTrigger; +void FO_SetModel(entity e, string fomdl); + +/* ====== Vote Ball ======= */ + +float() crandom; + +void (vector o, float z) ball_kick = { + + local vector v; + + v_x = ((o_x * 1.65) + (random() * 20) ); + v_y = ((o_y * 1.65) + (random() * 20) ); + + if (o_x < 0.000) { + o_x = o_x * -1.000; + } + if (o_y < 0.000) { + o_y = o_y * -1.000; + } + + v_z = ((100 + (random() * 30)) + ((o_y + o_x) * 0.20) + (z * 2.5) ); + + + self.flags = self.flags - ( self.flags & 512.000); + self.velocity = v; + +}; + +void (vector o) ball_fly = { + local vector v; + + v_x = (o_x * 0.600) + (crandom() * 40); + v_y = (o_y * 0.600) + (crandom() * 40); + + if (o_x < 0.000) { + o_x = o_x * -1.000; + } + if (o_y < 0.000) { + o_y = o_y * -1.000; + } + + + v_z = (140 + (crandom() * 40)) + ((o_y + o_x) * 0.20); + + self.flags = self.flags - ( self.flags & 512.000); + self.velocity = v; + + +}; + +void () ball_touch = { + if (self.watertype == CONTENT_LAVA) { + /*self.velocity_x = 0; + self.velocity_y = 100; + self.velocity_z = 0;*/ + setorigin (self,self.oldorigin); + return; + } + + if (other.classname == "worldspawn") { + return; + } + + if (round_over) { + return; + } + if (other.classname == "player") { + self.oldenemy = other; + ball_kick(other.velocity, (other.v_angle_x * -1.000)); + return; + + } + self.oldenemy = other.owner; + ball_fly(other.velocity); + return; +}; + +void () ball_reset = { + self.solid = 1.000; + self.velocity = '0 0 0'; + self.origin = self.oldorigin; + setorigin (self,self.origin); + + if ( !droptofloor () ) { + dprint ("GoalItem (ball) fell out of level at "); + dprint (vtos (self.origin)); + dprint ("\n"); + dremove (self); + return ; + } +}; + +void () item_ball = { + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_BOUNCE; + //self.flags= FL_ITEM; + + if ( self.mdl ) { + precache_model (self.mdl); + precache_model2 (self.mdl); + FO_SetModel (self,self.mdl); + } else { + self.mdl = "progs/lavaball.mdl"; + FO_SetModel (self,self.mdl); + } + + if ( !self.netname ) { + self.netname = "ball"; + } + + if ( (self.goal_min == '0 0 0') ) { + self.goal_min = '-12 -12 -12'; + } + if ( (self.goal_max == '0 0 0') ) { + self.goal_max = '12 12 12'; + } + setsize (self,self.goal_min,self.goal_max); + + //setsize (self,'-0 -0 -0','24 24 24'); + setorigin (self,self.origin); + self.oldorigin = self.origin; + self.touch = ball_touch; +}; + +void () soccer_goal_touch = { + if (round_over) return; + + if (cb_prematch) return; + + if (other.classname == "item_ball") { + if (other.solid) { + TeamFortress_TeamIncreaseScore (self.owned_by,self.count); + TeamFortress_TeamShowScores (2); + sound (self,FL_ITEM,self.noise,1,ATTN_NONE); + //round_winner = self.pteam; + other.solid = SOLID_NOT; + other.nextthink = (time + 0.3); + other.think = ball_reset; + } + return; + } +}; + +void () info_soccer_goal = { + if (!self.noise ) { + self.noise = "items/tf2kfgc.wav"; + } + + precache_sound (self.noise); + + if (self.owned_by == 1) { + //self.pteam = pteam1; + } + else if (self.owned_by == 2) { + //self.pteam = pteam2; + } + else if (self.owned_by == 3) { + //self.pteam = pteam3; + } + else if (self.owned_by == 4) { + //self.pteam = pteam4; + } + InitTrigger(); + + self.touch = soccer_goal_touch; + + }; diff --git a/flare.qc b/ssqc/flare.qc similarity index 95% rename from flare.qc rename to ssqc/flare.qc index 4379617ef..77b505e5c 100644 --- a/flare.qc +++ b/ssqc/flare.qc @@ -4,7 +4,7 @@ //==================================== void () FlareGrenadeTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); if (pointcontents(self.origin) == CONTENT_SKY) { dremove(self); return; diff --git a/ssqc/fo_logic.qc b/ssqc/fo_logic.qc new file mode 100644 index 000000000..12baa4a43 --- /dev/null +++ b/ssqc/fo_logic.qc @@ -0,0 +1,282 @@ +class fo_logic : entity +{ + float if_goal_no; + float if_group_no; + string if_fieldname; + float compare_goal_no; + float compare_group_no; + string compare_fieldname; + + float activate_goal_no_if_equal; + float activate_goal_no_if_not_equal; + float activate_goal_no_if_less_than; + float activate_goal_no_if_greater_than; + float activate_group_no_if_equal; + float activate_group_no_if_not_equal; + float activate_group_no_if_less_than; + float activate_group_no_if_greater_than; + float set_value_on_goal_no_if_equal; + float set_value_on_goal_no_if_not_equal; + float set_value_on_goal_no_if_less_than; + float set_value_on_goal_no_if_greater_than; + string set_from_fieldname; + float set_from_goal_no; + string set_to_fieldname; + float set_to_goal_no; + + float fieldtype; + float compare_fieldtype; + float set_fieldnum; + float fieldnum_used; + .__variant fieldref; + .__variant compare_fieldref; + .__variant test; + + void () fo_logic = + { + float argc = tokenize(__fullspawndata)-1; + for (float i = 1; i < argc;) + { + string field = argv(i++); + string val = argv(i++); + float i2 = findentityfield(strcat(this.classname, "::__m", field)); + + if (i2) + { + putentityfieldstring(i2, this, val); + } + } + + if (!if_goal_no && !if_group_no) + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' does not have if_goal_no or if_group_no set!\n"); + remove(this); + return; + } + + if (!compare_goal_no && !compare_group_no) + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' does not have compare_goal_no or compare_group_no set!\n"); + remove(this); + return; + } + + if (this.if_fieldname == __NULL__) + this.if_fieldname = ""; + + if (this.if_fieldname == "") + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' does not have fieldname set!\n"); + remove(this); + return; + } + + if (this.compare_fieldname == __NULL__) + this.compare_fieldname = ""; + + if (this.compare_fieldname == "") + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' does not have compare_fieldname set!\n"); + remove(this); + return; + } + + if (!activate_goal_no_if_equal && !activate_goal_no_if_not_equal + && !activate_goal_no_if_less_than && !activate_goal_no_if_greater_than + && !activate_group_no_if_equal && !activate_group_no_if_not_equal + && !activate_group_no_if_less_than && !activate_group_no_if_greater_than + && (!set_to_goal_no && !set_from_goal_no)) + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' does not have any values in activate_goal_no_if_equal, activate_goal_no_if_not_equal, activate_goal_no_if_less_than, activate_goal_no_if_greater_than, activate_group_no_if_equal, activate_group_no_if_not_equal, activate_group_no_if_less_than, activate_group_no_if_greater_than,, set_to_goal_no, set_from_goal_no!\n"); + remove(this); + return; + } + + float fieldnum = findentityfield(this.if_fieldname); + fieldtype = entityfieldtype(fieldnum); + + float compare_fieldnum = findentityfield(this.compare_fieldname); + compare_fieldtype = entityfieldtype(compare_fieldnum); + + if (fieldtype != compare_fieldtype) + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' cannot compare if_fieldname and compare_fieldname due to mismatched types!\n"); + remove(this); + return; + } + fieldref = entityfieldref(fieldnum); + compare_fieldref = entityfieldref(compare_fieldnum); + + if (this.set_to_fieldname == __NULL__) + this.set_to_fieldname = ""; + + if (this.set_to_fieldname != "") + { + set_fieldnum = findentityfield(this.set_to_fieldname); + fieldnum_used = TRUE; + } + }; + + nonvirtual void () SetValue = + { + if (!fieldnum_used) + return; + if (!set_from_goal_no) + return; + + float fieldnum = findentityfield(this.set_from_fieldname); + float set_fieldtype = entityfieldtype(fieldnum); + entity from = findfloat(world, ::goal_no, set_from_goal_no); + .__variant set_fieldref = entityfieldref(fieldnum); + string set_value = __NULL__; + switch (set_fieldtype) + { + case EV_STRING: + set_value = from.((.string)set_fieldref); + break; + case EV_FLOAT: + set_value = ftos(from.((.float)set_fieldref)); + break; + } + + entity to = findfloat(world, ::goal_no, set_to_goal_no); + float set_to_fieldnum = findentityfield(this.set_to_fieldname); + if (!putentityfieldstring(set_to_fieldnum, to, set_value)) + { + bprint(PRINT_HIGH, "fo_logic entity with netname: '", this.netname, "' set_value failed!\n"); + } + }; + + nonvirtual void (float gno) ActivateGoal = + { + if (!gno) + return; + + entity act = findfloat(world, ::goal_no, gno); + if (act) + { + AttemptToActivate(act, other, this); + } + + }; + + nonvirtual void (float gno) ActivateGroup = + { + if (!gno) + return; + + entity act = findfloat(world, ::group_no, gno); + while (act) + { + AttemptToActivate(act, other, this); + act = findfloat(act, ::group_no, gno); + } + }; + + nonvirtual void (entity goal, entity compare_goal) CompareGoals = + { + entity act = world; + switch (fieldtype) + { + case EV_STRING: + string sval = goal.((.string)fieldref); + string compare_sval = compare_goal.((.string)compare_fieldref); + if (sval == compare_sval) + { + this.SetValue(); + this.ActivateGoal(activate_goal_no_if_equal); + this.ActivateGroup(activate_group_no_if_equal); + } + else + { + this.ActivateGoal(activate_goal_no_if_not_equal); + this.ActivateGroup(activate_group_no_if_not_equal); + } + break; + case EV_FLOAT: + float fval = goal.((.float)fieldref); + float compare_fval = compare_goal.((.float)compare_fieldref); + + if (fval == compare_fval) + { + this.SetValue(); + this.ActivateGoal(activate_goal_no_if_equal); + this.ActivateGroup(activate_group_no_if_equal); + } + else + { + this.ActivateGoal(activate_goal_no_if_not_equal); + this.ActivateGroup(activate_group_no_if_not_equal); + } + + if (fval > compare_fval) + { + this.ActivateGoal(activate_goal_no_if_greater_than); + this.ActivateGroup(activate_group_no_if_greater_than); + } + else if (fval < compare_fval) + { + this.ActivateGoal(activate_goal_no_if_less_than); + this.ActivateGroup(activate_group_no_if_less_than); + } + break; + default: + // TODO - lazy, don't want to do this right now + bprint(PRINT_HIGH, "fo_logic entity can only compare fields that are string or float\n"); + break; + } + }; + + nonvirtual void (entity goal) ExecuteLogic = + { + entity compare_goal = world; + if (this.compare_goal_no) + { + compare_goal = findfloat(world, ::goal_no, compare_goal_no); + if (compare_goal) + { + this.CompareGoals(goal, compare_goal); + } + } + + if (this.compare_group_no) + { + compare_goal = world; + compare_goal = findfloat(world, ::group_no, compare_group_no); + + while (compare_goal) + { + this.CompareGoals(goal, compare_goal); + compare_goal = findfloat(compare_goal, ::group_no, compare_group_no); + } + } + }; + + virtual void () use = + { + float gnum = this.if_goal_no; + entity goal = world; + + if (gnum) + { + goal = findfloat(world, ::goal_no, gnum); + if (goal) + { + this.ExecuteLogic(goal); + } + } + + gnum = this.if_group_no; + if (gnum) + { + goal = world; + goal = findfloat(world, ::group_no, gnum); + + while (goal) + { + this.ExecuteLogic(goal); + goal = findfloat(goal, ::group_no, gnum); + } + } + }; +}; \ No newline at end of file diff --git a/ssqc/fo_math.qc b/ssqc/fo_math.qc new file mode 100644 index 000000000..736ae331d --- /dev/null +++ b/ssqc/fo_math.qc @@ -0,0 +1,66 @@ +class fo_math : entity +{ + float value; + float min_value; + float max_value; + + float add; + float subtract; + float multiply; + float divide; + + float activate_goal_no_on_max_value; + float activate_goal_no_on_min_value; + + float perform_reset_on_max_value; + float perform_reset_on_min_value; + float reset_value_on_max_value; + float reset_value_on_min_value; + + // state + float hit_max_value; + float hit_min_value; + + nonvirtual void (float hit_value, float activate_goal_no) ValidateValue = + { + entity act = world; + + // only execute once after it is past the max/min value + if (!hit_value) + { + if (activate_goal_no) + { + act = findfloat(world, ::goal_no, activate_goal_no); + if (act) + AttemptToActivate(act, other, this); + } + } + }; + + nonvirtual void () Process = + { + entity act = world; + value = value + add; + if (value > max_value) + { + ValidateValue(hit_max_value, activate_goal_no_on_max_value); + hit_max_value = TRUE; + + if (perform_reset_on_max_value) + { + value = reset_value_on_max_value; + } + } + + act = world; + value = value - subtract; + if (value < min_value) + { + ValidateValue(hit_min_value, activate_goal_no_on_min_value); + hit_min_value = TRUE; + + if (perform_reset_on_min_value) + value = reset_value_on_min_value; + } + }; +}; \ No newline at end of file diff --git a/ssqc/fo_misc_info.qc b/ssqc/fo_misc_info.qc new file mode 100644 index 000000000..b82d37896 --- /dev/null +++ b/ssqc/fo_misc_info.qc @@ -0,0 +1,29 @@ +class fo_misc_info : entity +{ + void () fo_misc_info = + { + if (this.mdl) + { + precache_model(this.mdl); + precache_model2(this.mdl); + this.model = this.mdl; + + if (this.spawnflags & FO_MISC_INFO_NO_Z_TEST) + { + this.nextthink = time + .01; + } + else + { + FO_SetModel(this, this.mdl); + } + + if (this.goal_state == TFGS_REMOVED) + RemoveGoal(this); + } + }; + + virtual void () think = + { + this.nextthink = time + .01; + }; +}; \ No newline at end of file diff --git a/ssqc/functions.qc b/ssqc/functions.qc new file mode 100644 index 000000000..1b8b22732 --- /dev/null +++ b/ssqc/functions.qc @@ -0,0 +1,172 @@ +float (float tno) TeamFortress_TeamSet; +string (float tno) TeamFortress_TeamGetColor; +string (entity pov, float tno) TeamFortress_TeamGetColorFor; +void (entity p) SetTeamName; +void (entity pl) Menu_Close; + +void (float tno) playerSetTeam = { + local string st; + TeamFortress_TeamSet(tno); + self.team_no = tno; + stuffcmd(self, "color "); + st = TeamFortress_TeamGetColor(tno); + stuffcmd(self, st); + stuffcmd(self, "\n"); + SetTeamName(self); +}; + +float () PlayerCount = { + local entity te; + local float tmp = 0; + + te = find(world, classname, "player"); + while (te != world) { + tmp = tmp + 1; + te = find(te, classname, "player"); + } + return tmp; +}; + +float () SpectatorCount = { + local entity te; + local float tmp = 0; + + te = find(world, classname, "observer"); + while (te != world) { + tmp = tmp + 1; + te = find(te, classname, "observer"); + } + return tmp; +}; + +void () nextCaptain = { + local entity te; + + te = find(world, classname, "player"); + while (te != world) { + if (te.captain == 4) + te.captain = 3; + else if (te.captain == 3) + te.captain = 2; + else if (te.captain == 2) { + te.captain = 1; + stuffcmd(te,"reload\n"); // Required to send one impulse in order to show menu + } else if (te.captain == 1) + te.captain = number_of_teams; + te = find(te, classname, "player"); + } +}; + +void () disableCaptain = { + captainmode = 0; + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s Captain Mode Disabled!\n"); +}; + +void () randomizeCaptains = { + local entity te; + local float tmp = 0; + local float teamno = 0; + local float capteam[4] = {0, 0, 0, 0}; + + te = find(world, classname, "player"); + while (te != world) { + if (te.captain == 9 && tmp < number_of_teams) { + do { + teamno = floor((random() * number_of_teams)); + } while (capteam[teamno] == 1); + capteam[teamno] = 1; + te.captain = teamno + 1; + if (te.captain == 1) + stuffcmd(te, "reload\n"); + tmp = tmp + 1; + } + te = find(te, classname, "player"); + } +}; + +void () randomizeTeams = { + local entity te, temp; + local float tmp = 0; + local float teamno = 0; + local float randteam[4] = {0, 0, 0, 0}; + + te = find(world, classname, "player"); + while (te != world) { + if (tmp >= number_of_teams) { + tmp = 0; + randteam[0] = 0; + randteam[1] = 0; + randteam[2] = 0; + randteam[3] = 0; + } + if (tmp < number_of_teams) { + do { + teamno = floor((random() * number_of_teams)); + } while (randteam[teamno] == 1); + + randteam[teamno] = 1; + temp = self; + self = te; + playerSetTeam(teamno + 1); + self = temp; + tmp = tmp + 1; + } + te = find(te, classname, "player"); + } +}; + +string (string text) clearString = { + local float i; + string specialChars[60] = {"�", "", "","","","","","",""," "," "," ","","","","","","","",""," ","?","Â","‚","Æ’","„","…","†","‡","ˆ","‰","Å ","‹","Å’","Â","Ž","Â","Â","‘","Å“","Ã…","Å“","Â","ž","Ÿ", "<", ">", "|", ":", "*", "?", "\\", "/", "\"", "&", "~", "`", ",", " ", "."}; + text = strconv(1,1,1,text); + text = strireplace("__", "_", text); + for (i = 0; i < 58; i++) { + text = strireplace(specialChars[i], "", text); + } + + return text; +} + +void () PrintLoginMessage = { + CenterPrint6(self, "\n\n\n\n\n\nžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸ\n", "Login required, please use\n\s\"cmd login \"\s \nbefore joining the game\n", "žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸ\n", "If you don't have an account, visit\n\s", webpageUrl,"\s\n"); + return; +} + +void () EndGameThink = { + local string m = mapname; + if(vote_result != string_null && vote_result != "") { + m = vote_result; + } else if(nextmap != string_null && nextmap != "") { + m = nextmap; + } + localcmd("changelevel "); + localcmd(m); + localcmd("\n"); + dremove(self); +} + +void () MapEndSequence = { + local entity player; + local entity maprestarttimer; + player = find (world, classname, "player"); + while (player) + { + if (player.playerclass != 0 && self.has_disconnected != 1) { + local float timeplayed = gametime - player.classtime; + LogEventChangeClass(player, player.playerclass, 0, timeplayed); + player.classtime = gametime; + } + player = find (player, classname, "player"); + } + LogEventTeamScores(); + LogEventGameEnd(); + if (logfilehandle > 0) + fclose(logfilehandle); + canlog = 0; + + maprestarttimer = spawn(); + maprestarttimer.classname = "timer"; + maprestarttimer.netname = "maprestarttimer"; + maprestarttimer.think = EndGameThink; + maprestarttimer.nextthink = time + map_restart_time; +} diff --git a/ssqc/help.qc b/ssqc/help.qc new file mode 100644 index 000000000..97394308d --- /dev/null +++ b/ssqc/help.qc @@ -0,0 +1,160 @@ +// CLASS HELP FOR FORTRESSONE +// ========================== +// Shows class bindings for each class. + +// functions by order of appearance +void () Help_Show; +void () Help_ShowScout; +void () Help_ShowSniper; +void () Help_ShowSoldier; +void () Help_ShowDemoman; +void () Help_ShowMedic; +void () Help_ShowHWGuy; +void () Help_ShowPyro; +void () Help_ShowSpy; +void () Help_ShowEngineer; + +// global variables +// + +// shows a list of key bindings and aliases for current class +// called from weapons.qc:ImpulseCommands() +void () Help_Show = { + if (self.playerclass == PC_SCOUT) + Help_ShowScout(); + if (self.playerclass == PC_SNIPER) + Help_ShowSniper(); + if (self.playerclass == PC_SOLDIER) + Help_ShowSoldier(); + if (self.playerclass == PC_DEMOMAN) + Help_ShowDemoman(); + if (self.playerclass == PC_MEDIC) + Help_ShowMedic(); + if (self.playerclass == PC_HVYWEAP) + Help_ShowHWGuy(); + if (self.playerclass == PC_PYRO) + Help_ShowPyro(); + if (self.playerclass == PC_SPY) + Help_ShowSpy(); + if (self.playerclass == PC_ENGINEER) + Help_ShowEngineer(); +}; + +void () Help_ShowScout = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Scout:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Nailgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Axe\n"); + sprint(self, PRINT_HIGH, Q"\se\s - Toggle Scanner on/off\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Throw Caltrop Canisters\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Concussion Grenade\n"); + sprint(self, PRINT_HIGH, "\nClass aliases for Scout:\n"); + sprint(self, PRINT_HIGH, Q"\sautoscan\s - Toggle Scanner on/off\n"); + sprint(self, PRINT_HIGH, Q"\sscansound\s - Toggle Scanner sound on/off\n"); + sprint(self, PRINT_HIGH, Q"\sscane\s - Toggle scanning of enemies on/off\n"); + sprint(self, PRINT_HIGH, Q"\sscanf\s - Toggle scanning of friendlies on/off\n"); +}; + +void () Help_ShowSniper = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Sniper:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Sniper Rifle\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Sniper Rifle on Full Auto\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Nailgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Axe\n"); + sprint(self, PRINT_HIGH, Q"\se\s - Toggle zoom mode\n"); + sprint(self, PRINT_HIGH, Q"\smwheelup\s - Zoom in (while in zoom mode)\n"); + sprint(self, PRINT_HIGH, Q"\smwheeldown\s - Zoom out (while in zoom mode)\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Throw Flare\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); + sprint(self, PRINT_HIGH, "\nClass aliases for Sniper:\n"); + sprint(self, PRINT_HIGH, Q"\szoomin\s - Zoom in (for adjusting zoom while in zoom mode)\n"); + sprint(self, PRINT_HIGH, Q"\szoomout\s - Zoom out (for adjusting zoom while in zoom mode)\n"); + sprint(self, PRINT_HIGH, "Usage: setinfo \n"); +}; + +void () Help_ShowSoldier = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Soldier:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Rocket Launcher\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Super Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Axe\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw Nail/Shock Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); +}; + +void () Help_ShowDemoman = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Demolitions Man:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Grenade Launcher\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Pipebomb Launcher\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Axe\n"); + sprint(self, PRINT_HIGH, Q"\s5\s - Detpack menu\n"); + sprint(self, PRINT_HIGH, Q"\se\s - Detonate pipebombs\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw Mirv Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); + sprint(self, PRINT_HIGH, "\nClass aliases for Demolitions Man:\n"); + sprint(self, PRINT_HIGH, Q"\sdetpipe\s - Detonate pipebombs\n"); + sprint(self, PRINT_HIGH, Q"\s+det5\s - Place detpack with 5 second timer\n"); + sprint(self, PRINT_HIGH, Q"\s+det20\s - Place detpack with 20 second timer\n"); + sprint(self, PRINT_HIGH, Q"\s+det50\s - Place detpack with 50 second timer\n"); + sprint(self, PRINT_HIGH, Q"\s+det255\s - Place detpack with 255 second timer\n"); +}; + +void () Help_ShowMedic = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Combat Medic:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Super Nailgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Super Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Medikit\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw Concussion/Blast Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); +}; + +void () Help_ShowHWGuy = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Heavy Weapons Guy:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Assault Cannon\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Super Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Axe\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw Mirv Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); +}; + +void () Help_ShowPyro = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Pyro:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Flamethrower\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Incendiary Cannon\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Axe\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw Napalm Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); +}; + +void () Help_ShowSpy = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Spy:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Tranquiliser Gun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Super Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Nailgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Knife\n"); + sprint(self, PRINT_HIGH, Q"\s5\s - Disguise menu\n"); + sprint(self, PRINT_HIGH, Q"\se\s - Silently feign death\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw Gas Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); + sprint(self, PRINT_HIGH, "\nClass aliases for Spy:\n"); + sprint(self, PRINT_HIGH, Q"\sfeign\s - Feign death\n"); + sprint(self, PRINT_HIGH, Q"\s+feign\s - Hold to feign death\n"); + sprint(self, PRINT_HIGH, Q"\ssfeign\s - Silently feign death\n"); +}; + +void () Help_ShowEngineer = { + sprint(self, PRINT_HIGH, "\nDefault bindings for Engineer:\n"); + sprint(self, PRINT_HIGH, Q"\s1\s - Equip Railgun\n"); + sprint(self, PRINT_HIGH, Q"\s2\s - Equip Super Shotgun\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Equip Spanner\n"); + sprint(self, PRINT_HIGH, Q"\s4\s - Build/destroy menu\n"); + sprint(self, PRINT_HIGH, Q"\sf\s - Prime/throw EMP Grenade\n"); + sprint(self, PRINT_HIGH, Q"\smouse2\s - Prime/throw Hand Grenade\n"); + sprint(self, PRINT_HIGH, "\nClass aliases for Engineer:\n"); + sprint(self, PRINT_HIGH, Q"\sdetdispenser\s - Detonate Dispenser\n"); + sprint(self, PRINT_HIGH, Q"\sdetsentry\s - Detonate Sentry Gun\n"); +}; diff --git a/ssqc/helpers.qc b/ssqc/helpers.qc new file mode 100644 index 000000000..2b85c437e --- /dev/null +++ b/ssqc/helpers.qc @@ -0,0 +1,59 @@ +enum { + kRegionUS = 1, + kRegionEU, + kRegionOCE, + kRegionLeague, + kRegionUnknown, +}; + +struct RegionMatch { + string channel; + float region; +}; + +static RegionMatch region_matches[] = { + { "504171613793681408", kRegionUS }, // US pug + { "513699536846323712", kRegionEU }, // EU pug + { "542237808895459338", kRegionOCE }, // OCE pug + { "1147341454851719219", kRegionLeague }, // Scrim + { "1026405619231625257", kRegionLeague }, // Tourney +}; + +float ServerRegion() { + static float region; + + if (region) + return region; + + // Can also query FO_REGION env but that does not appear to be + // reliably/consistently set at the moment. + region = kRegionUnknown; + for (int i = 0; i < region_matches.length; i++) { + if (discord_channel_id == region_matches[i].channel) { + region = region_matches[i].region; + break; + } + } + + return region; +} + +float ServerIsOCE() { + if (ServerRegion() == kRegionOCE) + return TRUE; + + // Additional check beyond kRegion for picking up OCE Scrim/Tourney etc + // This is pretty awful, for at least these servers we should just fix FO_REGION + // to be consistent in the future. + string hostname = serverkey("hostname"); + + return strstrofs(hostname, "Sydney") >= 0 || + strstrofs(hostname, "Melbourne") >= 0 || + strstrofs(hostname, "New Zealand") >= 0; +} + +float ServerIsStaging() { + string hostname = serverkey("hostname"); + + return strstrofs(hostname, "Staging") >= 0; +} diff --git a/ssqc/hwguy.qc b/ssqc/hwguy.qc new file mode 100644 index 000000000..d27ace81a --- /dev/null +++ b/ssqc/hwguy.qc @@ -0,0 +1,115 @@ +void AssCanBulletThink () +{ + self.movetype = MOVETYPE_TOSS; + self.think = SUB_Remove; + + if (asscanrangedie > 0) { + self.nextthink = time + asscanrangedie; + } else { + self.nextthink = time + 3; + } +} + +void AssCanBulletTouch() +{ + if (self.voided) + return; // Marked for removal + self.voided = TRUE; + + if (other == self.owner) + return; // Touching self, do nothing + + deathmsg = self.weapon; + if (other.health) + { + TF_T_Damage(other, self, self.owner, 8, TF_TD_NOTTEAM, + TF_TD_OTHER); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_BLOOD); + WriteByte(MSG_MULTICAST, 1); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PVS); + } + else + { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 1); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PVS); + } + + dremove_sent(self); +}; + +void FO_FireAssCanPellet(vector org, vector dir, float proj_speed, int index) +{ + local float num; + + entity proj = FOProj_Create(FPP_ASSAULT_CANNON); + proj.owner = self; + proj.classname = "proj_bullet"; + + proj.movetype = MOVETYPE_FLY; // Small collision + + proj.solid = SOLID_BBOX; + proj.touch = AssCanBulletTouch; + + if (asscanrange > 0) { + num = (1 / proj_speed) * asscanrange; + proj.think = AssCanBulletThink; + proj.nextthink = time + num; // Projectile range / gravity + } else { + proj.think = SUB_Remove; + proj.nextthink = time + 5; // Stop projectile going forever + } + + proj.frame = hwguy_random()*15; // Full range of sizes + proj.skin = 16 + hwguy_random()*7; // Bright colours + proj.weapon = DMSG_ASSAULTCANNON; + + proj.velocity = dir * proj_speed; // Constant speed multiplier + proj.angles = vectoangles(dir); // Create direction angle + setorigin (proj, org); // Move to starting position + + proj.fpp.ammo_index = self.ammo_shells * 100 + index; + FOProj_Finalize(proj); +} + +void StopAssCan() { + if (self.tfstate & TFSTATE_AC_FIRING) + player_asscan_down1(); +} + +void FO_LockToggle () { + self.tfstate ^= TFSTATE_LOCK; + + Status_Refresh(self); + /* self.impulse = 0; */ + /* this shouldn't be here, I think we just need to allow this impulse while shooting */ + /* worth checking detpipes for this too */ +} + +float AssCanTryBeginFire() { + if (get_shells() < 1) + return FALSE; + + if (FO_CheckForReload()) + return FALSE; + + if (!W_ConsumeAmmoIfPossible(AMMO_CELLS, PC_HVYWEAP_CELL_FIRE)) { + // Not worth optimizing w/ client side. + if (time >= self.antispam_assault_cannon) { + sprint(self, PRINT_MEDIUM, "Not enough cells to power up the Assault Cannon\n"); + self.antispam_assault_cannon = time + 3; + } + return FALSE; + } + + player_asscan_up1(); + return TRUE; +} diff --git a/items.qc b/ssqc/items.qc similarity index 76% rename from items.qc rename to ssqc/items.qc index 7190336b3..75505f9bc 100644 --- a/items.qc +++ b/ssqc/items.qc @@ -1,6 +1,7 @@ float (entity Retriever, float AmmoType) TeamFortress_GetMaxAmmo; float (entity Retriever, float WeaponType) TeamFortress_CanGetWeapon; void (entity Retriever, entity Items) TeamFortress_AddBackpackItems; +float IsFeigned(entity ent); void (entity p) TeamFortress_SetSpeed; @@ -9,7 +10,7 @@ void () tfgoal_touch; void () SUB_regen = { self.model = self.mdl; self.solid = SOLID_TRIGGER; - sound(self, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); setorigin(self, self.origin); }; @@ -30,7 +31,7 @@ void () q_touch = { return; self.mdl = self.model; - sound(other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + FO_Sound(other, CHAN_VOICE, self.noise, 1, ATTN_NORM); stuffcmd(other, "bf\n"); self.solid = SOLID_NOT; other.items = other.items | IT_QUAD; @@ -56,7 +57,7 @@ void (float timeleft) DropQuad = { item.solid = SOLID_TRIGGER; item.movetype = MOVETYPE_TOSS; item.noise = "items/damage.wav"; - setmodel(item, "progs/quaddama.mdl"); + FO_SetModel(item, "progs/quaddama.mdl"); setsize(item, VEC_HULL_MIN, VEC_HULL_MAX); item.cnt = time + timeleft; item.touch = q_touch; @@ -74,7 +75,7 @@ void () r_touch = { return; self.mdl = self.model; - sound(other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + FO_Sound(other, CHAN_VOICE, self.noise, 1, ATTN_NORM); stuffcmd(other, "bf\n"); self.solid = 0; other.items = other.items | IT_INVISIBILITY; @@ -100,7 +101,7 @@ void (float timeleft) DropRing = { item.solid = SOLID_TRIGGER; item.movetype = MOVETYPE_TOSS; item.noise = "items/inv1.wav"; - setmodel(item, "progs/invisibl.mdl"); + FO_SetModel(item, "progs/invisibl.mdl"); setsize(item, VEC_HULL_MIN, VEC_HULL_MAX); item.cnt = time + timeleft; item.touch = r_touch; @@ -190,21 +191,21 @@ void () item_health = { if (self.spawnflags & H_ROTTEN) { precache_model("maps/b_bh10.bsp"); precache_sound("items/r_item1.wav"); - setmodel(self, "maps/b_bh10.bsp"); + FO_SetModel(self, "maps/b_bh10.bsp"); self.noise = "items/r_item1.wav"; self.healamount = 15; self.healtype = 0; } else if (self.spawnflags & H_MEGA) { precache_model("maps/b_bh100.bsp"); precache_sound("items/r_item2.wav"); - setmodel(self, "maps/b_bh100.bsp"); + FO_SetModel(self, "maps/b_bh100.bsp"); self.noise = "items/r_item2.wav"; self.healamount = 100; self.healtype = 2; } else { precache_model("maps/b_bh25.bsp"); precache_sound("items/health1.wav"); - setmodel(self, "maps/b_bh25.bsp"); + FO_SetModel(self, "maps/b_bh25.bsp"); self.noise = "items/health1.wav"; self.healamount = 25; self.healtype = 1; @@ -217,18 +218,14 @@ void () health_touch = { local float medi; local string s; - if (other.classname != "player") { + if (other.classname != "player") return; - } - if (other.is_feigning) { + if (IsFeigned(other)) return; - } - if ((other.tfstate & 65536) || (other.tfstate & 2048)) { + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; - } - if (cb_prematch_time > time) { + if (cb_prematch == 1) return; - } medi = 0; if (self.healtype == 2) { if (!(other.tfstate & 16)) { @@ -241,7 +238,7 @@ void () health_touch = { } } else { if (!T_Heal(other, self.healamount, 0)) { - if (other.weapons_carried & 4) { + if (FO_WeaponsMask(other) & WEAP_MEDIKIT) { if (other.ammo_medikit < other.maxammo_medikit) { other.ammo_medikit = other.ammo_medikit + self.healamount; @@ -251,7 +248,7 @@ void () health_touch = { s = ftos(self.healamount); sprint(other, PRINT_LOW, "You gather ", s, " medikit ammo\n"); - sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + FO_Sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); stuffcmd(other, "bf\n"); self.model = string_null; self.solid = 0; @@ -262,7 +259,6 @@ void () health_touch = { self.think = SUB_regen; } activator = other; - W_SetCurrentAmmo(self); SUB_UseTargets(); } } @@ -277,7 +273,7 @@ void () health_touch = { s = ftos(self.healamount); sprint(other, PRINT_LOW, "You receive ", s, " health\n"); } - sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + FO_Sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); stuffcmd(other, "bf\n"); self.model = string_null; self.solid = 0; @@ -318,36 +314,30 @@ void () armor_touch = { local float value; local float bit; local string s; - local entity oldself; - if (other.health <= 0) { + if (other.health <= 0) return; - } - if (other.classname != "player") { + if (other.classname != "player") return; - } - if (other.is_feigning) { + if (IsFeigned(other)) return; - } - if ((other.tfstate & 65536) || (other.tfstate & 2048)) { + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; - } - if (cb_prematch_time > time) { + if (cb_prematch == 1) return; - } if (self.classname == "item_armor1") { type = 0.300; value = 100; - bit = 8192; + bit = IT_ARMOR1; } else { if (self.classname == "item_armor2") { type = 0.600; value = 150; - bit = 16384; + bit = IT_ARMOR2; } else { type = 0.800; value = 200; - bit = 32768; + bit = IT_ARMOR3; } } if ((other.armortype * other.armorvalue) >= (type * value)) { @@ -396,10 +386,6 @@ void () armor_touch = { if (other.ammo_cells > other.maxammo_cells) { other.ammo_cells = other.maxammo_cells; } - oldself = self; - self = other; - W_SetCurrentAmmo(self); - self = oldself; } value = other.maxarmor; } @@ -407,7 +393,7 @@ void () armor_touch = { other.armortype = type; other.armorvalue = value; other.items = - (other.items - (other.items & ((8192 | 16384) | 32768))) + bit; + (other.items - (other.items & ((IT_ARMOR1 | IT_ARMOR2) | IT_ARMOR3))) + bit; } if (self.armorclass > 0) { other.armorclass = self.armorclass; @@ -421,11 +407,11 @@ void () armor_touch = { self.nextthink = time + 40; } self.think = SUB_regen; - sound(other, 3, "items/armor1.wav", 1, 1); + FO_Sound(other, CHAN_ITEM, "items/armor1.wav", 1, 1); stuffcmd(other, "bf\n"); activator = other; SUB_UseTargets(); -}; +} void () item_armor1 = { if (CheckExistence() == FALSE) { @@ -434,7 +420,7 @@ void () item_armor1 = { } self.touch = armor_touch; precache_model("progs/armor.mdl"); - setmodel(self, "progs/armor.mdl"); + FO_SetModel(self, "progs/armor.mdl"); self.skin = 0; setsize(self, '-16 -16 0', '16 16 56'); StartItem(); @@ -447,7 +433,7 @@ void () item_armor2 = { } self.touch = armor_touch; precache_model("progs/armor.mdl"); - setmodel(self, "progs/armor.mdl"); + FO_SetModel(self, "progs/armor.mdl"); self.skin = 1; setsize(self, '-16 -16 0', '16 16 56'); StartItem(); @@ -460,7 +446,7 @@ void () item_armorInv = { } self.touch = armor_touch; precache_model("progs/armor.mdl"); - setmodel(self, "progs/armor.mdl"); + FO_SetModel(self, "progs/armor.mdl"); self.skin = 2; setsize(self, '-16 -16 0', '16 16 56'); StartItem(); @@ -485,29 +471,11 @@ void (entity p) bound_other_ammo = { if (p.armorvalue > p.maxarmor) { p.armorvalue = p.maxarmor; } - if (p.no_grenades_1 > 4) { - p.no_grenades_1 = 4; - } - if (p.no_grenades_2 > 4) { - p.no_grenades_2 = 4; - } - if ((p.tp_grenades_1 == 3) && (p.no_grenades_1 > 2)) { - p.no_grenades_1 = 2; - } - if ((p.tp_grenades_2 == 3) && (p.no_grenades_2 > 2)) { - p.no_grenades_2 = 2; + if (p.no_grenades_1 > p.max_grenades_1) { + p.no_grenades_1 = p.max_grenades_1; } - if ((p.tp_grenades_1 == 2) && (p.no_grenades_1 > 3)) { - p.no_grenades_1 = 3; - } - if ((p.tp_grenades_2 == 2) && (p.no_grenades_2 > 3)) { - p.no_grenades_2 = 3; - } - if ((p.tp_grenades_1 == 10) && (p.no_grenades_1 > 3)) { - p.no_grenades_1 = 3; - } - if ((p.tp_grenades_2 == 10) && (p.no_grenades_2 > 3)) { - p.no_grenades_2 = 3; + if (p.no_grenades_2 > p.max_grenades_2) { + p.no_grenades_2 = p.max_grenades_2; } }; @@ -527,9 +495,6 @@ float (float w) RankForWeapon = { return 7; }; -void (float old, float new) Deathmatch_Weapon = { -}; - void () ammo_touch; void (entity ritem, entity act) Respawn_Item = { @@ -551,99 +516,92 @@ void (entity ritem, entity act) Respawn_Item = { self = oldself; }; -float () W_BestWeapon; - void () weapon_touch = { local float hadammo; - local float best; local float new; local float old; - local entity stemp; local float leave; + float other_weapons_carried = FO_WeaponsMask(other); new = 0; if (!(other.flags & 8)) return; - if (other.is_feigning) + if (IsFeigned(other)) return; - if ((other.tfstate & 65536) || (other.tfstate & 2048)) + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; - if (cb_prematch_time > time) + if (cb_prematch == 1) return; - stemp = self; - self = other; - best = W_BestWeapon(); - self = stemp; if ((deathmatch == 2) || coop) { leave = 1; } else { leave = 0; } if (self.classname == "weapon_nailgun") { - if (leave && (other.weapons_carried & 512)) { + if (leave && (other_weapons_carried & WEAP_NAILGUN)) { return; } - if (!TeamFortress_CanGetWeapon(other, 512)) { + if (!TeamFortress_CanGetWeapon(other, WEAP_NAILGUN)) { return; } hadammo = other.ammo_nails; - new = 512; + new = WEAP_NAILGUN; other.ammo_nails = other.ammo_nails + 30; } else { if (self.classname == "weapon_supernailgun") { - if (leave && (other.weapons_carried & 1024)) { + if (leave && (other_weapons_carried & WEAP_SUPER_NAILGUN)) { return; } - if (!TeamFortress_CanGetWeapon(other, 1024)) { + if (!TeamFortress_CanGetWeapon(other, WEAP_SUPER_NAILGUN)) { return; } hadammo = other.ammo_rockets; - new = 1024; + new = WEAP_SUPER_NAILGUN; other.ammo_nails = other.ammo_nails + 30; } else { if (self.classname == "weapon_supershotgun") { - if (leave && (other.weapons_carried & 256)) { + if (leave && (other_weapons_carried & WEAP_SUPER_SHOTGUN)) { return; } - if (!TeamFortress_CanGetWeapon(other, 256)) { + if (!TeamFortress_CanGetWeapon(other, WEAP_SUPER_SHOTGUN)) { return; } hadammo = other.ammo_rockets; - new = 256; + new = WEAP_SUPER_SHOTGUN; other.ammo_shells = other.ammo_shells + 5; } else { if (self.classname == "weapon_rocketlauncher") { - if (leave && (other.weapons_carried & 8192)) { + if (leave && (other_weapons_carried & WEAP_ROCKET_LAUNCHER)) { return; } - if (!TeamFortress_CanGetWeapon(other, 8192)) { + if (!TeamFortress_CanGetWeapon(other, WEAP_ROCKET_LAUNCHER)) { return; } hadammo = other.ammo_rockets; - new = 8192; + new = WEAP_ROCKET_LAUNCHER; other.ammo_rockets = other.ammo_rockets + 5; } else { if (self.classname == "weapon_grenadelauncher") { - if (leave && (other.weapons_carried & 2048)) { + if (leave && (other_weapons_carried & WEAP_GRENADE_LAUNCHER)) { return; } - if (!TeamFortress_CanGetWeapon(other, 2048)) { + if (!TeamFortress_CanGetWeapon(other, WEAP_GRENADE_LAUNCHER)) { return; } hadammo = other.ammo_rockets; - new = 2048; + new = WEAP_GRENADE_LAUNCHER; other.ammo_rockets = other.ammo_rockets + 5; } else { if (self.classname == "weapon_lightning") { - if (leave && (other.weapons_carried & 65536)) { + if (leave && (other_weapons_carried & WEAP_LIGHTNING)) { return; } - if (!TeamFortress_CanGetWeapon(other, 65536)) { + if (!TeamFortress_CanGetWeapon(other, WEAP_LIGHTNING)) { return; } hadammo = other.ammo_rockets; - new = 65536; + new = WEAP_LIGHTNING; other.ammo_cells = other.ammo_cells + 15; } else { objerror("weapon_touch: unknown classname"); @@ -654,16 +612,9 @@ void () weapon_touch = { } } sprint(other, PRINT_LOW, "You got the ", self.netname, "\n"); - sound(other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + FO_Sound(other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); stuffcmd(other, "bf\n"); bound_other_ammo(other); - old = other.weapons_carried; - other.weapons_carried = other.weapons_carried | new; - stemp = self; - self = other; - Deathmatch_Weapon(old, new); - W_SetCurrentAmmo(self); - self = stemp; if (leave) return; Respawn_Item(self, other); @@ -675,7 +626,7 @@ void () weapon_supershotgun = { return; } precache_model("progs/g_shot.mdl"); - setmodel(self, "progs/g_shot.mdl"); + FO_SetModel(self, "progs/g_shot.mdl"); self.weapon = WEAP_SUPER_SHOTGUN; self.netname = "Double-barrelled Shotgun"; self.touch = weapon_touch; @@ -689,7 +640,7 @@ void () weapon_nailgun = { return; } precache_model("progs/g_nail.mdl"); - setmodel(self, "progs/g_nail.mdl"); + FO_SetModel(self, "progs/g_nail.mdl"); self.weapon = 512; self.netname = "nailgun"; self.touch = weapon_touch; @@ -703,7 +654,7 @@ void () weapon_supernailgun = { return; } precache_model("progs/g_nail2.mdl"); - setmodel(self, "progs/g_nail2.mdl"); + FO_SetModel(self, "progs/g_nail2.mdl"); self.weapon = 1024; self.netname = "Super Nailgun"; self.touch = weapon_touch; @@ -717,7 +668,7 @@ void () weapon_grenadelauncher = { return; } precache_model("progs/g_rock.mdl"); - setmodel(self, "progs/g_rock.mdl"); + FO_SetModel(self, "progs/g_rock.mdl"); self.weapon = 3; self.netname = "Grenade Launcher"; self.touch = weapon_touch; @@ -731,7 +682,7 @@ void () weapon_rocketlauncher = { return; } precache_model("progs/g_rock2.mdl"); - setmodel(self, "progs/g_rock2.mdl"); + FO_SetModel(self, "progs/g_rock2.mdl"); self.weapon = 3; self.netname = "Rocket Launcher"; self.touch = weapon_touch; @@ -745,7 +696,7 @@ void () weapon_lightning = { return; } precache_model("progs/g_light.mdl"); - setmodel(self, "progs/g_light.mdl"); + FO_SetModel(self, "progs/g_light.mdl"); self.weapon = 3; self.netname = "Thunderbolt"; self.touch = weapon_touch; @@ -753,99 +704,47 @@ void () weapon_lightning = { StartItem(); }; -void (entity pl, float typ) PrintGrenadeType = { - local string st; - st = ""; - - if (typ == 1) { - st = "normal"; - } else if (typ == 2) { - st = "concussion"; - } else if (typ == 3) { - st = "nail"; - } else if (typ == 4) { - st = "mirv"; - } else if (typ == 5) { - st = "napalm"; - } else if (typ == 6) { - st = "flare"; - } else if (typ == 7) { - st = "gas"; - } else if (typ == 8) { - st = "EMP"; - } else if (typ == 10) { - st = "caltrop"; - } else if (typ == 9) { - st = "flash"; - } - sprint(pl, PRINT_HIGH, st); -}; - -float (float pf_grenade_number, float pf_grenade_type) GetMaxGrenades = { - if (pf_grenade_number == 1) { - if (pf_grenade_type == GR_TYPE_NAIL) { - return 2; - } - if (pf_grenade_type == GR_TYPE_CALTROP) { - return 3; - } - } else { - if (pf_grenade_type == GR_TYPE_NAIL) { - return 2; - } - if (pf_grenade_type == GR_TYPE_CONCUSSION) { - return 3; - } - } - return 4; -} - void (entity pe_player, float pf_grenade_type, float pf_grenade_count) PrintFoundGrenade = { if (!pf_grenade_count) return; - sprint(pe_player, PRINT_HIGH, "You found "); - if (pf_grenade_count > 1) - sprint(pe_player, PRINT_HIGH, ftos(pf_grenade_count), " "); - else - sprint(pe_player, PRINT_HIGH, "a "); - PrintGrenadeType(pe_player, pf_grenade_type); - if (pf_grenade_type == GR_TYPE_CALTROP) - sprint(pe_player, PRINT_HIGH, " canister"); - else - sprint(pe_player, PRINT_HIGH, " grenade"); - if (pf_grenade_count > 1) - sprint(pe_player, PRINT_HIGH, "s"); - sprint(pe_player, PRINT_HIGH, "\n"); + string buf = sprintf("You found %s%s %s%s\n", + pf_grenade_count > 1 ? " " : "a ", + FO_GrenDesc(pf_grenade_type)->name, + pf_grenade_type == GREN_CALTROP ? "cannister" : "grenade", + pf_grenade_count > 1 ? "s" : ""); + sprint(pe_player, PRINT_HIGH, buf); }; float () GetGrenadePossibility = { - local float maxg; - - if (random() < 0.500) { + if (random() < 0.500) return 0; + + float index = -1; + switch (disable_resup_gren) { + case 0: index = random() < 0.5 ? 0 : 1; break; + case 1: index = 1; break; + case 2: index = 0; break; + default: + return 0; } - if (random() < 0.500) { - maxg = GetMaxGrenades(1, other.tp_grenades_1); - if ((other.tp_grenades_1 != 0) && (other.no_grenades_1 < maxg)) { - other.no_grenades_1 = other.no_grenades_1 + 1; - PrintFoundGrenade(other, other.tp_grenades_1, 1); - return 1; - } - } else { - maxg = GetMaxGrenades(1, other.tp_grenades_2); - if ((other.tp_grenades_2 != 0) && (other.no_grenades_2 < maxg)) { - other.no_grenades_2 = other.no_grenades_2 + 1; - PrintFoundGrenade(other, other.tp_grenades_2, 1); - return 1; - } - } - return 0; -}; + + FO_GrenInfo* gt = FO_PlayerGren(other, index); + if (gt->id == GREN_NONE) + return 0; + + if (index == 0 && other.no_grenades_1 < other.max_grenades_1) + other.no_grenades_1 += 1; + else if (index == 1 && other.no_grenades_2 < other.max_grenades_2) + other.no_grenades_2 += 1; + else + return 0; + + PrintFoundGrenade(other, gt->id, 1); + return 1; +} void () ammo_touch = { - local entity stemp; - local float best; local float gotgren; local float gotbox; @@ -855,18 +754,14 @@ void () ammo_touch = { if (other.health <= 0) { return; } - if ((other.tfstate & 65536) || (other.tfstate & 2048)) { + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) { return; } - if (cb_prematch_time > time) { + if (cb_prematch == 1) { return; } gotgren = 0; gotbox = 0; - stemp = self; - self = other; - best = W_BestWeapon(); - self = stemp; if (self.weapon == 1) { if (other.ammo_shells >= TeamFortress_GetMaxAmmo(other, 256)) { return; @@ -903,15 +798,11 @@ void () ammo_touch = { if (!gotbox && !gotgren) { return; } - sound(other, 3, "weapons/lock4.wav", 1, 1); + FO_Sound(other, CHAN_ITEM, "weapons/lock4.wav", 1, 1); stuffcmd(other, "bf\n"); if (gotbox) { bound_other_ammo(other); sprint(other, PRINT_LOW, "You got the ", self.netname, "\n"); - stemp = self; - self = other; - W_SetCurrentAmmo(self); - self = stemp; } Respawn_Item(self, other); }; @@ -924,11 +815,11 @@ void () item_shells = { self.touch = ammo_touch; if (self.spawnflags & 1) { precache_model("maps/b_shell1.bsp"); - setmodel(self, "maps/b_shell1.bsp"); + FO_SetModel(self, "maps/b_shell1.bsp"); self.aflag = 40; } else { precache_model("maps/b_shell0.bsp"); - setmodel(self, "maps/b_shell0.bsp"); + FO_SetModel(self, "maps/b_shell0.bsp"); self.aflag = 20; } self.weapon = 1; @@ -945,11 +836,11 @@ void () item_spikes = { self.touch = ammo_touch; if (self.spawnflags & 1) { precache_model("maps/b_nail1.bsp"); - setmodel(self, "maps/b_nail1.bsp"); + FO_SetModel(self, "maps/b_nail1.bsp"); self.aflag = 50; } else { precache_model("maps/b_nail0.bsp"); - setmodel(self, "maps/b_nail0.bsp"); + FO_SetModel(self, "maps/b_nail0.bsp"); self.aflag = 25; } self.weapon = 2; @@ -966,11 +857,11 @@ void () item_rockets = { self.touch = ammo_touch; if (self.spawnflags & 1) { precache_model("maps/b_rock1.bsp"); - setmodel(self, "maps/b_rock1.bsp"); + FO_SetModel(self, "maps/b_rock1.bsp"); self.aflag = 10; } else { precache_model("maps/b_rock0.bsp"); - setmodel(self, "maps/b_rock0.bsp"); + FO_SetModel(self, "maps/b_rock0.bsp"); self.aflag = 5; } self.weapon = 3; @@ -987,11 +878,11 @@ void () item_cells = { self.touch = ammo_touch; if (self.spawnflags & 1) { precache_model("maps/b_batt1.bsp"); - setmodel(self, "maps/b_batt1.bsp"); + FO_SetModel(self, "maps/b_batt1.bsp"); self.aflag = 12; } else { precache_model("maps/b_batt0.bsp"); - setmodel(self, "maps/b_batt0.bsp"); + FO_SetModel(self, "maps/b_batt0.bsp"); self.aflag = 6; } self.weapon = 4; @@ -1009,11 +900,11 @@ void () item_weapon = { if (self.spawnflags & 1) { if (self.spawnflags & 8) { precache_model("maps/b_shell1.bsp"); - setmodel(self, "maps/b_shell1.bsp"); + FO_SetModel(self, "maps/b_shell1.bsp"); self.aflag = 40; } else { precache_model("maps/b_shell0.bsp"); - setmodel(self, "maps/b_shell0.bsp"); + FO_SetModel(self, "maps/b_shell0.bsp"); self.aflag = 20; } self.weapon = 1; @@ -1022,11 +913,11 @@ void () item_weapon = { if (self.spawnflags & 4) { if (self.spawnflags & 8) { precache_model("maps/b_nail1.bsp"); - setmodel(self, "maps/b_nail1.bsp"); + FO_SetModel(self, "maps/b_nail1.bsp"); self.aflag = 40; } else { precache_model("maps/b_nail0.bsp"); - setmodel(self, "maps/b_nail0.bsp"); + FO_SetModel(self, "maps/b_nail0.bsp"); self.aflag = 20; } self.weapon = 2; @@ -1035,11 +926,11 @@ void () item_weapon = { if (self.spawnflags & 2) { if (self.spawnflags & 8) { precache_model("maps/b_rock1.bsp"); - setmodel(self, "maps/b_rock1.bsp"); + FO_SetModel(self, "maps/b_rock1.bsp"); self.aflag = 10; } else { precache_model("maps/b_rock0.bsp"); - setmodel(self, "maps/b_rock0.bsp"); + FO_SetModel(self, "maps/b_rock0.bsp"); self.aflag = 5; } self.weapon = 3; @@ -1050,26 +941,20 @@ void () item_weapon = { }; void () key_touch = { - if (other.classname != "player") { + if (other.classname != "player") return; - } - if (other.health <= 0) { + if (other.health <= 0) return; - } - if (other.items & self.items) { + if (other.items & self.items) return; - } - if (other.is_feigning) { + if (IsFeigned(other)) return; - } - if ((other.tfstate & 65536) || (other.tfstate & 2048)) { + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; - } - if (cb_prematch_time > time) { + if (cb_prematch == 1) return; - } sprint(other, PRINT_LOW, "You got the ", self.netname, "\n"); - sound(other, 3, self.noise, 1, 1); + FO_Sound(other, CHAN_ITEM, self.noise, 1, 1); stuffcmd(other, "bf\n"); other.items = other.items | self.items; if (!coop) { @@ -1107,17 +992,17 @@ void () item_key1 = { } if (world.worldtype == 0) { precache_model("progs/w_s_key.mdl"); - setmodel(self, "progs/w_s_key.mdl"); + FO_SetModel(self, "progs/w_s_key.mdl"); self.netname = "silver key"; } else { if (world.worldtype == 1) { precache_model("progs/m_s_key.mdl"); - setmodel(self, "progs/m_s_key.mdl"); + FO_SetModel(self, "progs/m_s_key.mdl"); self.netname = "silver runekey"; } else { if (world.worldtype == 2) { precache_model2("progs/b_s_key.mdl"); - setmodel(self, "progs/b_s_key.mdl"); + FO_SetModel(self, "progs/b_s_key.mdl"); self.netname = "silver keycard"; } } @@ -1136,17 +1021,17 @@ void () item_key2 = { } if (world.worldtype == 0) { precache_model("progs/w_g_key.mdl"); - setmodel(self, "progs/w_g_key.mdl"); + FO_SetModel(self, "progs/w_g_key.mdl"); self.netname = "gold key"; } if (world.worldtype == 1) { precache_model("progs/m_g_key.mdl"); - setmodel(self, "progs/m_g_key.mdl"); + FO_SetModel(self, "progs/m_g_key.mdl"); self.netname = "gold runekey"; } if (world.worldtype == 2) { precache_model2("progs/b_g_key.mdl"); - setmodel(self, "progs/b_g_key.mdl"); + FO_SetModel(self, "progs/b_g_key.mdl"); self.netname = "gold keycard"; } key_setsounds(); @@ -1161,14 +1046,14 @@ void () sigil_touch = { return; if (other.health <= 0) return; - if (other.is_feigning) + if (IsFeigned(other)) return; - if ((other.tfstate & 65536) || (other.tfstate & 2048)) + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; - if (cb_prematch_time > time) + if (cb_prematch == 1) return; - sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + FO_Sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); stuffcmd(other, "bf\n"); self.solid = SOLID_NOT; self.model = string_null; @@ -1178,47 +1063,16 @@ void () sigil_touch = { SUB_UseTargets(); }; -void () item_sigil = { - if (CheckExistence() == 0) { - dremove(self); - return; - } - if (!self.spawnflags) { - objerror("no spawnflags"); - } - precache_sound("misc/runekey.wav"); - self.noise = "misc/runekey.wav"; - if (self.spawnflags & 1) { - precache_model("progs/end1.mdl"); - setmodel(self, "progs/end1.mdl"); - } - if (self.spawnflags & 2) { - precache_model2("progs/end2.mdl"); - setmodel(self, "progs/end2.mdl"); - } - if (self.spawnflags & 4) { - precache_model2("progs/end3.mdl"); - setmodel(self, "progs/end3.mdl"); - } - if (self.spawnflags & 8) { - precache_model2("progs/end4.mdl"); - setmodel(self, "progs/end4.mdl"); - } - self.touch = sigil_touch; - setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); - StartItem(); -}; - void () powerup_touch = { if (other.classname != "player") return; if (other.health <= 0) return; - if (other.is_feigning) + if (IsFeigned(other)) return; - if ((other.tfstate & 65536) || (other.tfstate & 2048)) + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; - if (cb_prematch_time > time) + if (cb_prematch == 1) return; sprint(other, PRINT_LOW, "You got the "); @@ -1243,7 +1097,7 @@ void () powerup_touch = { self.think = SUB_regen; } } - sound(other, 2, self.noise, 1, 1); + FO_Sound(other, CHAN_VOICE, self.noise, 1, 1); stuffcmd(other, "bf\n"); self.solid = 0; other.items = other.items | self.items; @@ -1279,7 +1133,7 @@ void () item_artifact_invulnerability = { precache_sound("items/protect2.wav"); precache_sound("items/protect3.wav"); self.noise = "items/protect.wav"; - setmodel(self, "progs/invulner.mdl"); + FO_SetModel(self, "progs/invulner.mdl"); self.effects = self.effects | EF_RED; self.netname = "Pentagram of Protection"; self.items = IT_INVULNERABILITY; @@ -1297,7 +1151,7 @@ void () item_artifact_envirosuit = { precache_sound("items/suit.wav"); precache_sound("items/suit2.wav"); self.noise = "items/suit.wav"; - setmodel(self, "progs/suit.mdl"); + FO_SetModel(self, "progs/suit.mdl"); self.netname = "Biosuit"; self.items = IT_SUIT; setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); @@ -1315,7 +1169,7 @@ void () item_artifact_invisibility = { precache_sound("items/inv2.wav"); precache_sound("items/inv3.wav"); self.noise = "items/inv1.wav"; - setmodel(self, "progs/invisibl.mdl"); + FO_SetModel(self, "progs/invisibl.mdl"); self.netname = "Ring of Shadows"; self.items = IT_INVISIBILITY; setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); @@ -1333,7 +1187,7 @@ void () item_artifact_super_damage = { precache_sound("items/damage2.wav"); precache_sound("items/damage3.wav"); self.noise = "items/damage.wav"; - setmodel(self, "progs/quaddama.mdl"); + FO_SetModel(self, "progs/quaddama.mdl"); self.effects = self.effects | EF_BLUE; self.netname = "Quad Damage"; self.items = IT_QUAD; @@ -1345,7 +1199,7 @@ void () BackpackTouch = { local string s; local float maxg, giveg; - if (other.classname != "player" || other.is_feigning) + if (other.classname != "player" || IsFeigned(other)) return; if (other.health <= 0) return; @@ -1359,22 +1213,22 @@ void () BackpackTouch = { if (drop_grenpack && other.playerclass != PC_SCOUT) { // give grenade type 1 - maxg = GetMaxGrenades(1, other.tp_grenades_1); + maxg = other.max_grenades_1; if ((other.no_grenades_1 + self.no_grenades_1) >= maxg) giveg = maxg - other.no_grenades_1; else giveg = self.no_grenades_1; other.no_grenades_1 = other.no_grenades_1 + giveg; - PrintFoundGrenade(other, other.tp_grenades_1, giveg); + PrintFoundGrenade(other, FO_PlayerGren(other, 0)->id, giveg); // give grenade type 2 - maxg = GetMaxGrenades(2, other.tp_grenades_2); + maxg = other.max_grenades_2; if ((other.no_grenades_2 + self.no_grenades_2) >= maxg) giveg = maxg - other.no_grenades_2; else giveg = self.no_grenades_2; other.no_grenades_2 = other.no_grenades_2 + giveg; - PrintFoundGrenade(other, other.tp_grenades_2, giveg); + PrintFoundGrenade(other, FO_PlayerGren(other, 1)->id, giveg); } bound_other_ammo(other); @@ -1396,7 +1250,7 @@ void () BackpackTouch = { sprint(other, PRINT_LOW, s, " cells "); } if ((self.armorvalue && (other.playerclass == 9)) && - (other.ammo_cells < other.maxammo_cells)) { + (other.ammo_cells < other.maxammo_cells) && !standardizedeathammo) { s = ftos(self.armorvalue); sprint(other, PRINT_LOW, s, " metal "); other.ammo_cells = other.ammo_cells + self.armorvalue; @@ -1405,15 +1259,14 @@ void () BackpackTouch = { } } sprint(other, PRINT_LOW, "\n"); - sound(other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + FO_Sound(other, CHAN_AUTO, "weapons/lock4.wav", 1, ATTN_NORM); stuffcmd(other, "bf\n"); dremove(self); self = other; - W_SetCurrentAmmo(self); }; void () DropBackpack = { - if ((cb_prematch_time + 3) > time) + if (cb_prematch == 1) return; if (!(self.ammo_shells + self.ammo_nails + self.ammo_rockets + self.ammo_cells)) { @@ -1424,10 +1277,21 @@ void () DropBackpack = { newmis = spawn(); newmis.origin = self.origin - '0 0 24'; - newmis.ammo_shells = self.ammo_shells; - newmis.ammo_nails = self.ammo_nails; - newmis.ammo_rockets = self.ammo_rockets; - newmis.ammo_cells = self.ammo_cells; + if(standardizedeathammo) + { + newmis.ammo_shells = deathammo_shells; + newmis.ammo_nails = deathammo_nails; + newmis.ammo_rockets = deathammo_rockets; + newmis.ammo_cells = deathammo_cells; + } + else + { + newmis.ammo_shells = self.ammo_shells; + newmis.ammo_nails = self.ammo_nails; + newmis.ammo_rockets = self.ammo_rockets; + newmis.ammo_cells = self.ammo_cells; + } + if (drop_grenpack) { if (drop_gren1 == -1) newmis.no_grenades_1 = self.no_grenades_1; @@ -1456,9 +1320,15 @@ void () DropBackpack = { newmis.flags = FL_ITEM; newmis.solid = SOLID_TRIGGER; newmis.movetype = MOVETYPE_TOSS; - setmodel(newmis, "progs/backpack.mdl"); + + if(splitbackpackmodels) + FO_SetModel(newmis, "progs/deathbag.mdl"); + else + FO_SetModel(newmis, "progs/backpack.mdl"); + setsize(newmis, '-16 -16 0', '16 16 56'); newmis.touch = BackpackTouch; + newmis.classname = "backpack"; newmis.nextthink = time + 120; newmis.think = SUB_Remove; diff --git a/ssqc/locfiles.qc b/ssqc/locfiles.qc new file mode 100644 index 000000000..532dfadcb --- /dev/null +++ b/ssqc/locfiles.qc @@ -0,0 +1,115 @@ +#pragma target fte +string prepq3fstring (string s); +float locationMultiplier; + +void loadloc() { + locationMultiplier = TRUE; + local float i; + local string ln; + local string out; + local float locfilehandle; + + local string path = strcat("locs/", mapname); + path = strcat(path, ".loc"); + locfilehandle = fopen(path, FILE_READ); + if (locfilehandle >= 0) { + ln = fgets(locfilehandle); + while (ln) { // test for null + if (ln != "") // test for empty + { + numlocs++; + } + + ln = fgets(locfilehandle); + } + fclose(locfilehandle); + + locs = memalloc(sizeof(*locs)*numlocs); + + locfilehandle = fopen(path, FILE_READ); + ln = fgets(locfilehandle); + i = 0; + while (ln && i < numlocs) { + ln = strtrim(ln); + if (ln != "") + { + float s1 = strstrofs(ln, " ", 0); + float s2 = strstrofs(ln, " ", s1+1); + float s3 = strstrofs(ln, " ", s2+1); + + local string px = substring(ln, 0, s1); + local string py = substring(ln, s1+1, (s2-(s1+1))); + local string pz = substring(ln, s2+1, (s3-(s2+1))); + + locs[i].pos.x = stof(px); + locs[i].pos.y = stof(py); + locs[i].pos.z = stof(pz); + locs[i].desc = substring(ln, s3+1, strlen(ln)-s3+2); + i++; + } + + ln = fgets(locfilehandle); + } + fclose(locfilehandle); + out = strcat("Loaded ", mapname); + out = strcat(out, ".loc with "); + out = strcat(out, ftos(numlocs)); + out = strcat(out, " locations\n"); + } + else { + out = strcat("Couldn't find ", mapname); + out = strcat(out, ".loc\n"); + + // check for q3f target_locations + numlocs = 0; + entity targ; + targ = find(world, classname, "target_location"); + while (targ) + { + numlocs++; + targ = find(targ, classname, "target_location"); + } + + if (numlocs > 0) + { + locationMultiplier = FALSE; + locs = memalloc(sizeof(*locs)*numlocs); + targ = find(world, classname, "target_location"); + i = 0; + while (targ) + { + locs[i].pos = targ.origin; + locs[i].desc = strtrim(prepq3fstring(targ.message)); + i++; + targ.nextthink = time + 0.01; + targ.think = SUB_Remove; + targ = find(targ, classname, "target_location"); + } + + out = "Loaded locs from target_location entities\n"; + } + } + dprint(out); +} + +string getLocationName(vector location) { + local float bestdist; + local string desc; + local float i; + + desc = "someplace"; + location = location * ((locationMultiplier == TRUE) ? 8 : 1); + bestdist = 0; + for (i = 0; i < numlocs; i++) { + float dist = vlen(location - locs[i].pos); + if (bestdist == 0) { + bestdist = dist; + desc = locs[i].desc; + } + else if (dist < bestdist) { + bestdist = dist; + desc = locs[i].desc; + } + } + return desc; +} \ No newline at end of file diff --git a/ssqc/login.qc b/ssqc/login.qc new file mode 100644 index 000000000..50c394aae --- /dev/null +++ b/ssqc/login.qc @@ -0,0 +1,125 @@ +void (entity player, string login, string secret) performLogin = { + string data = strcat("login=", login); + data = strcat(data,"&secret="); + data = strcat(data,secret); + uri_get(loginUrl, BR_LOGIN_REQUEST, "application/x-www-form-urlencoded", data); + self.login_in_progress = 1; + dprint(infokey(self,"name")); + dprint(" ["); + dprint(infokey(self,"ip")); + dprint("]"); + dprint(" trying to login as "); + dprint(login); + dprint("\n"); +} + +void (entity player, string token) performFoLogin = { + local string uri = sprintf("%s/results/api/v1/fo_login", backend_address); + uri_get(uri, FO_LOGIN_REQUEST, "application/json", sprintf("{ \"auth_token\": \"%s\" }", token)); + dprint(sprintf("%s [%s] logging in\n", infokey(player, "name"), infokey(player, "ip"))); +} + +void(float reqid, float responsecode, string resourcebody, int resourcebytes) URI_Get_Callback = { + local float success = !responsecode; + + switch(reqid) { + case FO_QUAD_STARTED_REQUEST: + local string msg; + + if (success && resourcebody) { + match_id = resourcebody; + msg = sprintf("Quad start successfully logged. ID: %s\n", match_id); + bprint(PRINT_HIGH, msg); + dprint(msg); + } else { + msg = sprintf("Quad start failed to log. Response code: %d\n", responsecode); + bprint(PRINT_HIGH, msg); + dprint(msg); + } + + break; + case FO_QUAD_FINISHED_REQUEST: + local string msg; + + if (success && resourcebody == match_id) { + msg = sprintf("Quad result successfully logged. ID: %s\n", match_id); + bprint(PRINT_HIGH, msg); + dprint(msg); + } else { + msg = sprintf("Quad result failed to log. Response code: %d\n", responsecode); + bprint(PRINT_HIGH, msg); + dprint(msg); + } + + break; + case FO_LOGIN_REQUEST: + local string msg; + + if (!responsecode) { + self.fo_login = resourcebody; + CenterPrint(self, sprintf("You have logged in as %s\n", self.fo_login)); + msg = sprintf("%s logged in as %s\n", infokey(self, "name"), self.fo_login); + bprint(PRINT_HIGH, msg); + dprint(msg); + } else { + CenterPrint(self, "Login failed\n"); + msg = (sprintf("%s login failed. Response code: %d\n", infokey(self, "name"), responsecode)); + sprint(self, PRINT_HIGH, msg); + dprint(msg); + } + + break; + case BR_LOGIN_REQUEST: + local float got_one = 0; + self.login_in_progress = 0; + local float csqcactive = infokeyf(self, INFOKEY_P_CSQCACTIVE); + if (!responsecode) { + float num_args = tokenizebyseparator(resourcebody,";"); + self.login = argv(0); + forceinfokey(self,"*login", self.login); + bprint(2, infokey(self,"name")); + bprint(2, " has logged in as \s"); + bprint(2, self.login); + bprint(2, "\s\n"); + dprint(infokey(self,"name")); + dprint(" logged in as "); + dprint(self.login); + dprint("\n"); + CenterPrint3(self, "You have logged in as ", self.login, "\n"); + if (num_args > 1) { + if (argv(1) == "true") { + self.is_admin = TRUE; + forceinfokey(self,"*admin", ftos(self.is_admin)); + Admin_Aliases (); + bprint(2, self.netname, " \sgains full admin status!\s\n"); + // bprint (3, "\n"); + sprint(self, 2, "Type \scommands\s for admin commands.\n"); + } + } + if(self.team_no == 0 && !intermission_running) { + if (clanbattle && (self.has_disconnected != 1)) { + if (self.tf_id != 0) { + got_one = RejoinWithTfId(); + ResetAndRespawnPlayer(self); + } + if (!got_one) { + CreateTfIdAndJoin(); + } + } + if (!got_one) { + if (csqcactive) + Menu_Team(0); + else + Menu_Team(1); + } + } + } else { + dprint(infokey(self,"name")); + dprint(" login failed: "); + dprint(self.login); + dprint("\n"); + CenterPrint3(self, ftos(responsecode), " - Login FAILED, invalid Login/Secret", "\n"); + } + break; + } +} diff --git a/medic.qc b/ssqc/medic.qc similarity index 81% rename from medic.qc rename to ssqc/medic.qc index 91b03527f..af64b495d 100644 --- a/medic.qc +++ b/ssqc/medic.qc @@ -42,7 +42,7 @@ void () CF_Medic_AuraFindPlayers = { self.nextthink = time + PC_MEDIC_AURA_HEAL_TIME; - if (!self.owner.aura_active || self.owner.ammo_cells < 50) + if (!self.owner.aura_active || self.owner.ammo_cells < ceil(PC_MEDIC_MAXAMMO_CELL / 2)) return; e_find = findradius(self.owner.origin, PC_MEDIC_AURA_RANGE); @@ -57,7 +57,7 @@ void () CF_Medic_AuraFindPlayers = { } if (f_healtotal) { - sound(self.owner, 3, "items/r_item2.wav", 1, 1); + FO_Sound(self.owner, CHAN_ITEM, "items/r_item2.wav", 1, 1); if (time < self.owner.aura_healtime) self.owner.aura_healamount = self.owner.aura_healamount + f_healtotal; else @@ -72,19 +72,14 @@ void () CF_Medic_AuraFindPlayers = { self.owner.aura_healtime = 0; } - self.owner.ammo_cells = self.owner.ammo_cells - 5; - if (self.owner.current_weapon == WEAP_MEDIKIT && !old_medikit) - self.owner.currentammo = self.owner.ammo_cells; + self.owner.ammo_cells = self.owner.ammo_cells - ceil(PC_MEDIC_MAXAMMO_CELL / 20); }; // increases the medic's cells two times per second // called from tfort.qc:TeamFortress_SetEquipment() void () CF_Medic_RegenerateCells = { - local float f_newcells = self.owner.ammo_cells + min(PC_MEDIC_CELL_REGEN_AMOUNT, (100 - self.owner.ammo_cells)); - - // don't regenerate cells if not using aura or new medikit - if (!medicaura && old_medikit) - return; + local entity oldself; + local float f_newcells = self.owner.ammo_cells + min(ceil(PC_MEDIC_MAXAMMO_CELL * (PC_MEDIC_CELL_REGEN_PERCENT / 100)), ceil(PC_MEDIC_MAXAMMO_CELL - self.owner.ammo_cells)); // skip this regen tick if on cooldown if (time <= self.owner.regen_cooldown) { @@ -94,17 +89,22 @@ void () CF_Medic_RegenerateCells = { self.nextthink = time + PC_MEDIC_CELL_REGEN_TIME; - if (self.owner.ammo_cells < 100) { + if (self.owner.ammo_cells < PC_MEDIC_MAXAMMO_CELL) { self.owner.ammo_cells = f_newcells; - // refresh status bar if cells just hit 100 - if (f_newcells >= 100) + // refresh status bar if cells just hit max + if (f_newcells >= PC_MEDIC_MAXAMMO_CELL) Status_Refresh(self.owner); - else if (f_newcells >= 50 && f_newcells < 60) + else if (f_newcells >= ceil(PC_MEDIC_MAXAMMO_CELL / 2) && f_newcells < ceil(PC_MEDIC_MAXAMMO_CELL / 2 + PC_MEDIC_MAXAMMO_CELL / 10)) Status_Refresh(self.owner); + } - if (self.owner.current_weapon == WEAP_MEDIKIT && !old_medikit) - self.owner.currentammo = self.owner.ammo_cells; + // Update drop ammo menu if cells are + if ((self.owner.menu_input == Menu_Drop_Input) && (self.owner.ammo_cells >= DROP_CELLS)) { + oldself = self; + self = self.owner; + Menu_Drop(); + self = oldself; } }; @@ -118,12 +118,8 @@ void () CF_Medic_Regenerate = { return; } - // only regen if 50 or more cells with new medikit - if (!old_medikit) { - if (self.owner.health >= self.owner.max_health || self.owner.ammo_cells < 50) { - return; - } - } else if (self.owner.health >= self.owner.max_health || self.owner.ammo_medikit) { + // only regen if half of max or more cells with new medikit + if (self.owner.health >= self.owner.max_health || self.owner.ammo_cells < ceil(PC_MEDIC_MAXAMMO_CELL / 2)) { return; } @@ -174,7 +170,8 @@ void () BioInfection_Decay = { te.tfstate = te.tfstate | TFSTATE_INFECTED; te.infection_team_no = self.owner.infection_team_no; - + LogEventAffliction(self.enemy, te, TFSTATE_INFECTED); + LogEventAffliction(self.owner, te, TFSTATE_INFECTED); sprint(te, PRINT_MEDIUM, "You have been infected by ", self.owner.netname, "\n"); sprint(self.owner, PRINT_MEDIUM, "You have infected ", te.netname, "\n"); } @@ -188,7 +185,7 @@ void () BioInfection_Decay = { else self.nextthink = time + 1; deathmsg = DMSG_BIOWEAPON; - TF_T_Damage(self.owner, self, self.enemy, 8, TF_TD_IGNOREARMOUR, TF_TD_OTHER); + TF_T_Damage(self.owner, self, self.enemy, 8, TF_TD_IGNOREARMOR, TF_TD_OTHER); SpawnBlood(self.owner.origin, 30); }; diff --git a/ssqc/menu.qc b/ssqc/menu.qc new file mode 100644 index 000000000..0404bc4c5 --- /dev/null +++ b/ssqc/menu.qc @@ -0,0 +1,1893 @@ +//====================================================== +// This file handles all menu functions and displays. +//====================================================== + +void (entity pe_player, float pf_class, float is_user) CF_Spy_ChangeSkin; +void (entity pe_player, float pf_team_no, float is_user) CF_Spy_ChangeColor; +void (float issilent, float force) FO_Spy_Feign; +void () FO_Spy_FeignOnNextDamage; +void () FO_Spy_Unfeign; +void () CF_Spy_Invisible; +void () CF_Spy_DisguiseStop; + +float (float pf_team_no, float pf_class) CF_GetClassRestriction; +float (float pf_team_no, float pf_class) CF_GetClassPlayers; +float (float pf_team_no, float pf_class) CF_ClassIsRestricted; + +void (entity spy) Spy_RemoveDisguise; + +void (entity eng, string bld) DestroyBuilding; + +void (float objtobuild, float offset) TeamFortress_Build; + +void () lvl1_sentry_stand; +void () lvl2_sentry_stand; +void () lvl3_sentry_stand; + +float (float tno) TeamFortress_TeamSet; +string (float tno) TeamFortress_TeamGetColor; +float () TeamFortress_TeamPutPlayerInTeam; +float (float tno) TeamFortress_TeamIsCivilian; +float (float tno) TeamFortress_TeamGetNoPlayers; +float () TeamFortress_GetNoPlayers; + +float (float pc) IsLegalClass; +void (float inp) TeamFortress_ChangeClass; +void (entity p) TeamFortress_SetSkin; + +void (float timer) TeamFortress_SetDetpack; +void (float timer) TeamFortress_ToggleDetpack; +void () TeamFortress_DetpackStop; + +void (float type, float amount) TeamFortress_DropAmmo; +void (entity disp) Engineer_Dispenser_InsertAmmo; +void (entity disp) Engineer_Dispenser_InsertArmor; +void (entity disp) Engineer_Dispenser_Repair; +void (entity disp) Engineer_SentryGun_InsertAmmo; +void (entity disp) Engineer_SentryGun_Upgrade; +void (entity disp) Engineer_SentryGun_Repair; +void () Menu_Engineer_Cancel; +void () TeamFortress_EngineerBuildStop; + +void (entity targ, entity inflictor, entity attacker, float damage, + float T_flags, float T_AttackType) TF_T_Damage; + +void (entity p) bound_other_ammo; +float (float v) anglemod; + +void (float tno, entity ignore, string st) teamsprint; + +void (float update) Menu_Team; +void (float update) Menu_Class; +string (float pc, float tno) TeamFortress_ClassGetNoPlayersString; +void () Menu_Drop; +void () PlayerObserverMode; +void () Menu_Scout; +void (entity pe_player) Menu_Spy; +void () Menu_Spy_Skin; +void () Menu_Spy_Color; + +void (float inp) Menu_Scout_Input; +void (float inp) Menu_Spy_Input; +void (float inp) Menu_Spy_Skin_Input; +void (float inp) Menu_Spy_Color_Input; +void (entity player, float is_user) FO_Spy_DisguiseLast; +void (entity player, float is_user) FO_Spy_DisguiseLastSpawned; + +void () Menu_Demoman; +void () Menu_Demoman_Cancel; + +void (float inp) Menu_Demoman_Input; +void (float inp) Menu_Demoman_Cancel_Input; + +void (entity player) Menu_Engineer; +void () Menu_Engineer_Update; +void () Menu_EngineerFix_Dispenser; +void () Menu_EngineerFix_SentryGun; + +void (float inp) Menu_Engineer_Input; +void (float inp) Menu_EngineerFix_Dispenser_Input; +void (float inp) Menu_EngineerFix_SentryGun_Input; + +void () Menu_Dispenser; +void (float inp) Menu_Dispenser_Input; + +void () Menu_Admin; +void (float inp) Menu_Admin_Input; +void () Admin_DoKick; +void () Admin_ForceSpectator; + +void () Admin_Pause; +void () Admin_CeaseFire; +void () Broadcast_Players_NotReady; +void () StartTimer; +void () ForceStartMatch; +void (float inp) SetQuadRounds; + +float (string ps_short, string ps_setting, string ps_default) CF_GetSetting; + +void (entity pl) UpdateClientMenu_Team; +void (entity pl, float team) UpdateClientClasses; +void (entity pl) UpdateClientMenu_Class; +void (entity pl) UpdateClientMenu_DropAmmo; +void (entity pl) UpdateClientMenu_Scout; +void (entity pl) UpdateClientMenu_Spy; +void (entity pl) UpdateClientMenu_Spy_Skin; +void (entity pl) UpdateClientMenu_Spy_Team; +void (entity pl, float cancel_detpack) UpdateClientMenu_Detpack; +void (entity pl) UpdateClientMenu_Build; +void (entity pl) UpdateClientMenu_FixSentry; +void (entity pl) UpdateClientMenu_FixDispenser; +void (entity pl, entity disp) UpdateClientMenu_UseDispenser; +void (entity pl) UpdateClientMenu_Admin; + +void (entity pl) Menu_Close = +{ + pl.menu_input = nil; + Status_Print(pl, ""); +}; + +string (string text, float maxlength) Menu_Indent_line = +{ + local float spaces; + local float i; + + spaces = maxlength - (strlen(text)); + for (i = 0; i < spaces; i = i + 1) + { + text = strcat(text," "); + } + text = strcat(text,"\n"); + return text; +} + +void (float inp) Menu_Input = +{ + var f_void_float tmp; tmp = self.menu_input; + self.menu_input = nil; + self.impulse = 0; + if(tmp) + tmp(inp); + Status_Print(self, ""); +}; + +void (float inp) Menu_Team_Input = { + if (self.classname == "observer") + return; + + if (inp == 0) { + return; + } + + if (inp == 5) { + TeamFortress_TeamPutPlayerInTeam(); + } else if ((inp <= number_of_teams) && (inp > 0)) { + TeamFortress_TeamSet(inp); + } else if ((number_of_teams == 0) && (inp <= 4)) { + TeamFortress_TeamSet(inp); + } else { + Menu_Team(0); + return; + } + + if ((self.playerclass == 0) && (self.lives != 0)) { + Menu_Class(0); + } +}; + +void () Menu_Team_Update = { + if (self.owner.menu_input == Menu_Team_Input) { + self.nextthink = time + 0.5; + self = self.owner; + Menu_Team(1); + } else { + self.owner.has_menutimer = 0; + dremove(self); + } +}; + +string (float pf_team_no, string ps_team) Menu_Team_TeamString = { + local string s_string = ""; + local float f_gap = 2; + local float f_players = TeamFortress_TeamGetNoPlayers(pf_team_no); + + if (number_of_teams >= pf_team_no) { + s_string = strpadl(ftos(f_players), f_gap); + if (f_players < 10) { + s_string = strpadl(s_string, (1 + f_gap)); + if (f_players == 1) + s_string = strcat(s_string, " player \n"); + else + s_string = strcat(s_string, " players\n"); + } else + s_string = strcat(s_string, " players\n"); + s_string = strcat(ps_team, s_string); + } + + return strzone(s_string); +}; + +void (float inp) Menu_No_Input = { + return; +} + +void () Menu_Login = { + local string st1, st2, st3; + + sprint (self, 2, "Login required, please use \"cmd login \" before joining the game\n"); + st1 = "Login required, please use\n\s\"cmd login \"\s \nbefore joining the game\n"; + st2 = "If you don't have an account, visit\n\s"; + st3 = webpageUrl; + st3 = strcat(st3, "\s\n"); + st2 = strcat(st2, st3); + st1 = strcat(st1, st2); + Status_Menu(self, Menu_No_Input, st1); + return; +} + +void (float update) Menu_Team = { + local entity timer; + + if (self.classname == "observer") + /* Status_Menu(self, Menu_Team_Input, ""); */ + return; + + //If there's only one team, just join it + if (number_of_teams == 1 && self.team_no == 0) { + Menu_Close(self); + TeamFortress_TeamSet(1); + if ((self.playerclass == 0) && (self.lives != 0)) { + Menu_Class(0); + } + return; + } + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Team(self); + return; + } + + // allow toggling team menu using any method to show it + if (!update && self.menu_input == Menu_Team_Input) { + Menu_Input(0); + return; + } + + if ((toggleflags & TFLAG_AUTOTEAM) && teamplay) { + if (TeamFortress_TeamPutPlayerInTeam()) + return; + } + + // prepare team strings + local string s_select = "Select team:\n\n"; + local string s_blue = Q"\s[1]\s Blue team "; + local string s_red = Q"\s[2]\s Red team "; + local string s_yellow = Q"\s[3]\s Yellow team"; + local string s_green = Q"\s[4]\s Green team "; + local string s_auto = Q"\s[5]\s Auto-assign team"; + + // put together team strings + s_blue = Menu_Team_TeamString(1, s_blue); + s_red = Menu_Team_TeamString(2, s_red); + s_yellow = Menu_Team_TeamString(3, s_yellow); + s_green = Menu_Team_TeamString(4, s_green); + s_auto = strpadr(s_auto, (strlen(s_blue) - 1)); + + // don't show auto team if already assigned a team + if (self.team_no) + s_auto = ""; + + // update menu every 0.5 seconds + if (!self.has_menutimer) { + self.has_menutimer = 1; + timer = spawn(); + timer.classname = "menu_timer"; + timer.owner = self; + timer.think = Menu_Team_Update; + timer.nextthink = time + 0.5; + } + + Status_Menu(self, Menu_Team_Input, s_select, s_blue, s_red, s_yellow, s_green, "\n", s_auto); + + strunzone(s_blue); strunzone(s_red); strunzone(s_yellow); strunzone(s_green); +}; + +void (float inp) Menu_Class_Input = { + if (!inp) + return; + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + // keep showing menu if class is invalid + if (inp > 10 || (!IsLegalClass(inp) && !override_mapclasses) || CF_ClassIsRestricted(self.team_no, inp)) + Menu_Class(0); + + // don't try to change class if class is forbidden + if ((!IsLegalClass(inp) && !override_mapclasses) || CF_GetClassRestriction(self.team_no, inp) == -1) + return; + + // close menu if selected class is current class + if (self.playerclass == inp || (inp == 10 && (self.tfstate & TFSTATE_RANDOMPC))) + Menu_Close(self); + + TeamFortress_ChangeClass(inp); +}; + +void () Menu_Class_Update = { + if (self.owner.menu_input == Menu_Class_Input) { + self.nextthink = time + 0.5; + self = self.owner; + Menu_Class(1); + } else { + self.owner.has_menutimer = 0; + dremove(self); + } +}; + +string (float pf_class, string ps_class) Menu_Class_ClassString = { + local string s_string; + local float f_gap = 5; + local float f_max = CF_GetClassRestriction(self.team_no, pf_class); + local float f_players = CF_GetClassPlayers(self.team_no, pf_class); + + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + if ((IsLegalClass(pf_class) || override_mapclasses) && f_max >= 0) { + if (f_players < 10) + s_string = strpadl(ftos(f_players), (1 + f_gap)); + else + s_string = strpadl(ftos(f_players), f_gap); + s_string = strcat(s_string, " / "); + if (f_max < 10 && TeamFortress_TeamGetNoPlayers(self.team_no) >= 10) + s_string = strpadr(s_string, (5 + f_gap)); + s_string = strcat(s_string, ftos(f_max)); + s_string = strcat(s_string, "\n"); + s_string = strcat(ps_class, s_string); + } else { + if (TeamFortress_TeamGetNoPlayers(self.team_no) >= 10) + s_string = strpadr(ps_class, (12 + f_gap - 3)); + else + s_string = strpadr(ps_class, (12 + f_gap - 3)); + s_string = strcat(s_string, "disabled\n"); + } + + return strzone(s_string); +}; + +void (float update) Menu_Class = { + local entity timer; + if (TeamFortress_TeamIsCivilian(self.team_no)) { + sprint(self, PRINT_HIGH, "Your team can only be civilians\n"); + UpdateClientClasses(self, self.team_no); + Menu_Close(self); + return; + } + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own class menu + //ask the client to activate it + Menu_Close(self); + UpdateClientClasses(self, self.team_no); + UpdateClientMenu_Class(self); + return; + } + // allow toggling team menu using any method to show it + if (!update && self.menu_input == Menu_Class_Input) { + Menu_Input(0); + return; + } + + // print map specific class menu + local entity e_info = find(world, classname, "info_tfdetect"); + if (e_info) { + if (self.team_no == 1) { + if (e_info.noise1 != string_null) { + Status_Menu(self, Menu_Class_Input, e_info.noise1); + return; + } + } else if (self.team_no == 2) { + if (e_info.noise2 != string_null) { + Status_Menu(self, Menu_Class_Input, e_info.noise2); + return; + } + } else if (self.team_no == 3) { + if (e_info.noise3 != string_null) { + Status_Menu(self, Menu_Class_Input, e_info.noise3); + return; + } + } else if (self.team_no == 4) { + if (e_info.noise4 != string_null) { + Status_Menu(self, Menu_Class_Input, e_info.noise4); + return; + } + } + } + + // prepare class strings + local string s_select = "Select class:\n\n"; + local string s_scout = Q"\s[1]\s Scout "; + local string s_sniper = Q"\s[2]\s Sniper "; + local string s_soldier = Q"\s[3]\s Soldier "; + local string s_demoman = Q"\s[4]\s Demoman "; + local string s_medic = Q"\s[5]\s Medic "; + local string s_hwguy = Q"\s[6]\s HWGuy "; + local string s_pyro = Q"\s[7]\s Pyro "; + local string s_spy = Q"\s[8]\s Spy "; + local string s_engineer = Q"\s[9]\s Engineer"; + local string s_randompc = Q"\s[0]\s RandomPC"; + + // put together class strings - all strings are strzoned + s_scout = Menu_Class_ClassString(PC_SCOUT, s_scout); + s_sniper = Menu_Class_ClassString(PC_SNIPER, s_sniper); + s_soldier = Menu_Class_ClassString(PC_SOLDIER, s_soldier); + s_demoman = Menu_Class_ClassString(PC_DEMOMAN, s_demoman); + s_medic = Menu_Class_ClassString(PC_MEDIC, s_medic); + s_hwguy = Menu_Class_ClassString(PC_HVYWEAP, s_hwguy); + s_pyro = Menu_Class_ClassString(PC_PYRO, s_pyro); + s_spy = Menu_Class_ClassString(PC_SPY, s_spy); + s_engineer = Menu_Class_ClassString(PC_ENGINEER, s_engineer); + s_randompc = Menu_Class_ClassString(PC_RANDOM, s_randompc); + + // update menu every 0.5 seconds + if (!self.has_menutimer) { + self.has_menutimer = 1; + timer = spawn(); + timer.classname = "menu_timer"; + timer.owner = self; + timer.think = Menu_Class_Update; + timer.nextthink = time + 0.5; + } + + // print out class menu + self.menu_input = nil; +// if (TeamFortress_TeamIsCivilian(self.team_no)) +// Status_Print(self, "Your team can only be civilians\n"); +// else + Status_Menu(self, Menu_Class_Input, s_select, s_scout, s_sniper, s_soldier, s_demoman, s_medic, s_hwguy, s_pyro, s_spy, s_engineer, s_randompc); + + strunzone(s_scout); strunzone(s_sniper); strunzone(s_soldier); strunzone(s_demoman); strunzone(s_medic); + strunzone(s_hwguy); strunzone(s_pyro); strunzone(s_spy); strunzone(s_engineer); strunzone(s_randompc); +}; + +void (float inp) Menu_Drop_Input = { + if ((inp > 0) && (inp < 5)) { + TeamFortress_DropAmmo(inp, 0); + Menu_Drop(); + } +}; + +void () Menu_Drop = { + local string s_drop; + local string s_shells = Q"\s[1]\s Shells \n"; + local string s_nails = Q"\s[2]\s Nails \n"; + local string s_rockets = Q"\s[3]\s Rockets \n"; + local string s_cells = Q"\s[4]\s Cells \n"; + local string s_nothing = Q"\n\s[5]\s Nothing "; + + if (!(self.ammo_shells + self.ammo_nails + self.ammo_rockets + self.ammo_cells)) { + sprint(self, PRINT_HIGH, "Not enough ammo\n"); + return; + } + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + + Menu_Close(self); + UpdateClientMenu_DropAmmo(self); + return; + } + + if (self.ammo_shells < DROP_SHELLS) + s_shells = "\n"; + if (self.ammo_nails < DROP_NAILS) + s_nails = "\n"; + if (self.ammo_rockets < DROP_ROCKETS) + s_rockets = "\n"; + if (self.ammo_cells < DROP_CELLS) + s_cells = "\n"; + + if (self.playerclass == PC_ENGINEER) { + if ((self.ammo_shells < DROP_SHELLS) && ((self.ammo_cells / AMMO_COST_SHELLS) > (DROP_SHELLS - self.ammo_shells))) + s_shells = Q"\s[1]\s Shells (make) \n"; + if ((self.ammo_nails < DROP_NAILS) && ((self.ammo_cells / AMMO_COST_NAILS) > (DROP_NAILS - self.ammo_nails))) + s_nails = Q"\s[2]\s Nails (make) \n"; + if ((self.ammo_rockets < DROP_ROCKETS) && ((self.ammo_cells / AMMO_COST_ROCKETS) > (DROP_ROCKETS - self.ammo_rockets))) + s_rockets = Q"\s[3]\s Rockets (make)\n"; + if (self.ammo_cells < DROP_CELLS) + s_cells = "\n"; + } + + if (s_shells == "\n" && s_nails == "\n" && s_rockets == "\n" && s_cells == "\n") + return; + + + + self.menu_input = nil; + if (self.playerclass == PC_ENGINEER) + s_drop = "Drop or make:\n\n"; + else + s_drop = "Drop:\n\n"; + Status_Menu(self, Menu_Drop_Input, s_drop, s_shells, s_nails, s_rockets, s_cells, s_nothing); +}; + +void (float inp) Menu_Scout_Input = { + if (inp == 1) + self.impulse = TF_SCAN; + else if (inp == 2) + self.impulse = TF_SCAN_ENEMY; + else if (inp == 3) + self.impulse = TF_SCAN_FRIENDLY; + else if (inp == 4) + self.impulse = TF_SCAN_SOUND; + else + self.impulse = 0; +}; + +void () Menu_Scout = { + local string s_action = "Scanner settings:\n\n"; + local string s_scan, s_scane, s_scanf, s_scansound; + local string s_nothing = Q"\n\s[5]\s Nothing \n\n"; + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Scout(self); + return; + } + + if (!self.ScannerOn) + s_scan = Q"\s[1]\s Turn Scanner on \n"; + else + s_scan = Q"\s[1]\s Turn Scanner off \n"; + + if (self.tf_items_flags & NIT_SCANNER_ENEMY) + s_scane = Q"\s[2]\s Do not scan for enemies \n"; + else + s_scane = Q"\s[2]\s Scan for enemies \n"; + + + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) + s_scanf = Q"\s[3]\s Do not scan for friendlies\n"; + else + s_scanf = Q"\s[3]\s Scan for friendlies \n"; + + if (self.tf_items_flags & 4) + s_scansound = Q"\s[4]\s Turn off scan sound \n"; + else + s_scansound = Q"\s[4]\s Turn on scan sound \n"; + + Status_Menu(self, Menu_Scout_Input, s_action, s_scan, s_scane, s_scanf, s_scansound, s_nothing); +}; + +void (float inp) Menu_Spy_Input = { + if ((inp == 1) || (inp == 2)) { + if (self.effects & (EF_DIMLIGHT | EF_BRIGHTLIGHT)) { + sprint(self, PRINT_HIGH, "You cannot go undercover while glowing\n"); + return; + } + if (self.is_unabletospy) { + sprint(self, PRINT_HIGH, "You cannot go undercover right now\n"); + return; + } + } + if (inp == 1) { + if (invis_only) + CF_Spy_Invisible(); + else if (self.is_undercover == 2) + CF_Spy_DisguiseStop(); + else + Menu_Spy_Skin(); + } else if (inp == 2 && !invis_only) { + FO_Spy_DisguiseLast(self, TRUE); + } else if (inp == 3) { + FO_Spy_Feign(1, 0); + if (IsFeigned(self)) { + Menu_Spy(self); + } + } else if (inp == 4) { + Spy_RemoveDisguise(self); + } +}; + +void (entity pe_player) Menu_Spy = { + local string s_action = "Action:\n\n"; + local string s_skin = Q"\s[1]\s Disguise \n"; + local string s_last = Q"\s[2]\s Last disguise \n"; + local string s_feign, s_reset; + local string s_nothing = Q"\n\s[5]\s Nothing "; + + if (pe_player.effects & (EF_DIMLIGHT | EF_BRIGHTLIGHT) || pe_player.is_unabletospy == 1) { + return; + } + if(infokeyf(pe_player, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(pe_player); + UpdateClientMenu_Spy(pe_player); + return; + } + + if (invis_only) { + if (pe_player.is_undercover == 1) + s_skin = Q"\s[1]\s Become visible \n"; + else if (pe_player.is_undercover == 2) + s_skin = Q"\s[1]\s Stop going invisible \n"; + else + s_skin = Q"\s[1]\s Go invisible \n"; + } else if (pe_player.is_undercover == 2) + s_skin = Q"\s[1]\s Stop disguising \n"; + + if ((!pe_player.last_selected_skin && !pe_player.last_team) || invis_only) + s_last = "\n"; + + if (IsFeigned(pe_player)) + s_feign = Q"\s[3]\s Stop feigning \n"; + else + s_feign = Q"\s[3]\s Start feigning (silent)\n"; + + if (pe_player.undercover_team && pe_player.undercover_skin) + s_reset = Q"\s[4]\s Reset disguise \n"; + else if (pe_player.undercover_team) + s_reset = Q"\s[4]\s Reset color \n"; + else if (pe_player.undercover_skin) + s_reset = Q"\s[4]\s Reset skin \n"; + else + s_reset = "\n"; + + Status_Menu(pe_player, Menu_Spy_Input, s_action, s_skin, s_last, s_feign, s_reset, s_nothing); +}; + +void (float inp) Menu_Spy_Skin_Input = { + if (inp == 10) + return; + + if (self.effects & (EF_DIMLIGHT | EF_BRIGHTLIGHT)) { + sprint(self, PRINT_MEDIUM, "You cannot disguise while glowing\n"); + return; + } + + if (self.is_unabletospy) { + sprint(self, PRINT_HIGH, "You cannot go undercover right now\n"); + return; + } + + if(infokeyf(self, "smt")) { + Menu_Spy_Color(); + } else { + if (number_of_teams == 1) + sprint(self, PRINT_HIGH, "There is no other team\n"); + else if (number_of_teams > 2) + Menu_Spy_Color(); + else if (self.team_no == 1) + CF_Spy_ChangeColor(self, 2, TRUE); + else + CF_Spy_ChangeColor(self, 1, TRUE); + } + + if (self.skin != inp) + CF_Spy_ChangeSkin(self, inp, TRUE); +}; + +void () Menu_Spy_Skin = { + if (self.is_unabletospy == 1) + return; + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Spy_Skin(self); + return; + } + + local string s_disguise = "Disguise as enemy:\n\n"; + local string s_scout = Q"\s[1]\s Scout \n"; + local string s_sniper = Q"\s[2]\s Sniper \n"; + local string s_soldier = Q"\s[3]\s Soldier \n"; + local string s_demoman = Q"\s[4]\s Demoman \n"; + local string s_medic = Q"\s[5]\s Medic \n"; + local string s_hwguy = Q"\s[6]\s HWGuy \n"; + local string s_pyro = Q"\s[7]\s Pyro \n"; + local string s_spy = Q"\s[8]\s Spy \n"; + local string s_engineer = Q"\s[9]\s Engineer \n"; + local string s_nothing = Q"\n\s[0]\s Nothing \n"; + + Status_Menu(self, Menu_Spy_Skin_Input, s_disguise, s_scout, s_sniper, s_soldier, s_demoman, s_medic, s_hwguy, s_pyro, s_spy, s_engineer, s_nothing); +}; + +void (float inp) Menu_Spy_Color_Input = { + local float color = stof(infokey(self, "bottomcolor")); + + if (inp == 1 && color == 13) + Menu_Spy_Color(); + else if (inp == 2 && color == 4) + Menu_Spy_Color(); + else if (inp == 3 && color == 12) + Menu_Spy_Color(); + else if (inp == 4 && color == 11) + Menu_Spy_Color(); + else if (inp > 0 && inp <= number_of_teams) + CF_Spy_ChangeColor(self, inp, TRUE); +}; + +void () Menu_Spy_Color = { + local float color = stof(infokey(self, "bottomcolor")); + local string s_disguise = "Disguise as:\n\n"; + local string s_blue = Q"\s[1]\s Blue team \n"; + local string s_red = Q"\s[2]\s Red team \n"; + local string s_yellow = Q"\s[3]\s Yellow team\n"; + local string s_green = Q"\s[4]\s Green team \n"; + local string s_nothing = Q"\n\s[5]\s Nothing "; + + if (number_of_teams == 0) { + sprint(self, PRINT_HIGH, "No color changing allowed in deathmatch\n"); + return; + } + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Spy_Team(self); + return; + } + + // don't display your own team + if (color == 13) + s_blue = "\n"; + else if (color == 4) + s_red = "\n"; + else if (color == 12) + s_yellow = "\n"; + else if (color == 11) + s_green = "\n"; + + self.menu_input = nil; + if (number_of_teams == 1) + sprint(self, PRINT_HIGH, "There is no other team\n"); + else if (number_of_teams == 2) + Status_Menu(self, Menu_Spy_Color_Input, s_disguise, s_blue, s_red, s_nothing, "\n\n"); + else if (number_of_teams == 3) + Status_Menu(self, Menu_Spy_Color_Input, s_disguise, s_blue, s_red, s_yellow, s_nothing, "\n"); + else + Status_Menu(self, Menu_Spy_Color_Input, s_disguise, s_blue, s_red, s_yellow, s_green, s_nothing); +}; + +void (float inp) Menu_Demoman_Input = { + if (inp == 1) + TeamFortress_SetDetpack(5); + else if (inp == 2) + TeamFortress_SetDetpack(20); + else if (inp == 3) + TeamFortress_SetDetpack(50); + else if (inp == 4) + TeamFortress_SetDetpack(255); +}; + +void () Menu_Demoman = { + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Detpack(self, FALSE); + return; + } + + local string s_detpack = "Set detpack for:\n\n"; + local string s_5 = Q"\s[1]\s 5 seconds \n"; + local string s_20 = Q"\s[2]\s 20 seconds \n"; + local string s_50 = Q"\s[3]\s 50 seconds \n"; + local string s_255 = Q"\s[4]\s 255 seconds\n"; + local string s_nothing = Q"\n\s[5]\s Nothing "; + + Status_Menu(self, Menu_Demoman_Input, s_detpack, s_5, s_20, s_50, s_255, s_nothing); +} + +void (float inp) Menu_Demoman_Cancel_Input = { + if (inp == 1) + TeamFortress_DetpackStop(); + else + Menu_Demoman_Cancel(); +} + +void () Menu_Demoman_Cancel = { + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Detpack(self, TRUE); + return; + } + local string s_detpack = "Setting detpack...\n\n"; + local string s_cancel = Q"\s[1]\s Cancel!\n\n\n\n\n"; + + Status_Menu(self, Menu_Demoman_Cancel_Input, s_detpack, s_cancel); +} + +void (float offset) TeamFortress_BuildSentry = { + if (self.has_sentry) return; + + if (self.ammo_cells >= ENG_SENTRY_COST) + TeamFortress_Build(BUILD_SENTRYGUN, offset); + else + Status_Print(self, "\n\n\n\n\n\n\n", strcat("You need ", ftos(ENG_SENTRY_COST - self.ammo_cells), " more cells to build a sentry gun")); +} + +void (float inp) Menu_Engineer_Input = { + switch (inp) { + case 5: + break; + + // build sentry + case 1: + TeamFortress_BuildSentry(0); + break; + + // build dispenser + case 2: + if (!self.has_dispenser) { + if (self.ammo_cells >= ENG_DISPENSER_COST) + TeamFortress_Build(BUILD_DISPENSER, 0); + else + Status_Print(self, "\n\n\n\n\n\n\n", strcat("You need ", ftos(ENG_DISPENSER_COST), " cells to build a dispenser")); + } + break; + + // dismantle or destroy sentry + case 3: + if (self.has_sentry) { + local float dismantle_sentrygun = 0; + local entity te = findradius(self.origin, ENG_BUILDING_DISMANTLE_DISTANCE); + while (te) { + if (te.classname == "building_sentrygun" && te.real_owner == self) { + if (te.weapon == 0) { + sprint(self, PRINT_HIGH, strcat("You stopped building a sentry gun and got ", ftos(ENG_SENTRY_COST), " cells back\n")); + self.ammo_cells += ENG_SENTRY_COST; + if (self.ammo_cells > self.maxammo_cells) + self.ammo_cells = self.maxammo_cells; + if (te.trigger_field != world) + dremove(te.trigger_field); + self = te.real_owner; + TeamFortress_EngineerBuildStop(); + } else { + sprint(self, PRINT_HIGH, "You dismantled the sentry gun and got 65 cells\n"); + self.ammo_cells += 65; + dremove(te.trigger_field); + dremove(te); + self.has_sentry = 0; + dismantle_sentrygun = 1; + } + } + te = te.chain; + } + if (dismantle_sentrygun == 0) + DestroyBuilding(self, "building_sentrygun"); + } + break; + + // dismantle or destroy dispenser + case 4: + if (self.has_dispenser) { + local float dismantle_dispenser = 0; + local entity te = findradius(self.origin, ENG_BUILDING_DISMANTLE_DISTANCE); + while (te) { + if (te.classname == "building_dispenser" && te.real_owner == self) { + sprint(self, PRINT_HIGH, "You dismantled the dispenser and got 50 cells\n"); + self.ammo_cells += 50; + dremove(te); + self.has_dispenser = 0; + dismantle_dispenser = 1; + } + te = te.chain; + } + if (dismantle_dispenser == 0) + DestroyBuilding(self, "building_dispenser"); + } + break; + } +}; + +void (entity player) Menu_Engineer = { + local entity te, dist_checker; + local string s_action = "Action:\n\n"; + local string s_sentry = "\n"; + local string s_disp = "\n"; + local string s_dsentry = "\n"; + local string s_ddisp = "\n"; + local string s_nothing = Q"\n\s[5]\s Nothing "; + + if(infokeyf(player, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(player); + UpdateClientMenu_Build(player); + return; + } + + if (player.has_sentry) { + s_sentry = "\n"; + s_dsentry = Q"\s[3]\s Destroy sentry gun \n"; + te = findradius(player.origin, 100); + while (te) { + if (te.classname == "building_sentrygun") { + if (te.real_owner == player) + s_dsentry = Q"\s[3]\s Dismantle sentry gun\n"; + } + te = te.chain; + } + } else if (player.ammo_cells >= 130) { + s_sentry = Q"\s[1]\s Build sentry gun \n"; + } + + if (player.has_dispenser) { + s_ddisp = Q"\s[4]\s Destroy dispenser \n"; + te = findradius(player.origin, 100); + while (te) { + if (te.classname == "building_dispenser") { + if (te.real_owner == player) + s_ddisp = Q"\s[4]\s Dismantle dispenser \n"; + } + te = te.chain; + } + } else if (player.ammo_cells >= 100) { + s_disp = Q"\s[2]\s Build dispenser \n"; + } + + if ((player.has_dispenser || player.has_sentry) && !player.has_menutimer) { + player.has_menutimer = 1; + dist_checker = spawn(); + dist_checker.classname = "menu_timer"; + dist_checker.owner = player; + dist_checker.think = Menu_Engineer_Update; + dist_checker.nextthink = time + 0.3; + } + + Status_Menu(player, Menu_Engineer_Input, s_action, s_sentry, s_disp, s_dsentry, s_ddisp, s_nothing); +}; + +void () Menu_Engineer_Update = { + if (self.owner.menu_input == Menu_Engineer_Input) { + Menu_Engineer(self.owner); + self.nextthink = time + 0.3; + } else { + self.owner.has_menutimer = 0; + dremove(self); + } +}; + +void (float inp) Menu_EngineerFix_Dispenser_Input = { + if (self.classname != "player" || self.building == world) + return; + + if (inp == 1) { + Engineer_Dispenser_InsertAmmo(self.building); + } else if (inp == 2) { + Engineer_Dispenser_InsertArmor(self.building); + } else if (inp == 3 && old_spanner) { + Engineer_Dispenser_Repair(self.building); + } + + self.building = world; +}; + +void () Menu_EngineerFix_Dispenser = { + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_FixDispenser(self); + return; + } + + local string s_action = "Dispenser maintenance:\n\n"; + local string s_ammo, s_armor, s_repair; + local string s_nothing = Q"\n\s[5]\s Nothing \n\n"; + + if ((self.ammo_shells > 0 && self.building.ammo_shells < 400) + || (self.ammo_nails > 0 && self.building.ammo_nails < 600) + || (self.ammo_rockets > 0 && self.building.ammo_rockets < 300) + || (self.ammo_cells > 0 && self.building.ammo_cells < 400)) + s_ammo = Q"\s[1]\s Insert ammo \n"; + else + s_ammo = "\n"; + + if (self.armorvalue > 0 && self.building.armorvalue < 500) + s_armor = Q"\s[2]\s Insert armor\n"; + else + s_armor = "\n"; + + if (old_spanner && self.building.health < self.building.max_health) + s_repair = Q"\s[3]\s Repair \n"; + else + s_repair = "\n"; + + Status_Menu(self, Menu_EngineerFix_Dispenser_Input, s_action, s_ammo, s_armor, s_repair, s_nothing); +}; + +void (float inp) Menu_EngineerFix_SentryGun_Rotate_Input = { + if (inp == 1) { + sprint(self, PRINT_HIGH, "Rotating 45 degrees anticlockwise...\n"); + self.building.waitmin = anglemod(self.building.waitmin + 45); + self.building.waitmax = anglemod(self.building.waitmax + 45); + } else if (inp == 2) { + sprint(self, PRINT_HIGH, "Rotating 180 degrees...\n"); + self.building.waitmin = anglemod(self.building.waitmin + 180); + self.building.waitmax = anglemod(self.building.waitmax + 180); + } else if (inp == 3) { + sprint(self, PRINT_HIGH, "Rotating 45 degrees clockwise...\n"); + self.building.waitmin = anglemod(self.building.waitmin - 45); + self.building.waitmax = anglemod(self.building.waitmax - 45); + } +}; + +void () Menu_EngineerFix_SentryGun_Rotate = { + local string action = "Rotate sentry gun:\n\n"; + local string rotl = Q"\s[1]\s anticlockwise\n"; + local string rot180 = Q"\s[2]\s 180 degrees \n"; + local string rotr = Q"\s[3]\s clockwise \n"; + local string nothing = Q"\n\s[5]\s Nothing \n"; + + + if (!self.building.real_owner.has_sentry || self.building.real_owner != self + || self.classname != "player" || self.building == world) { + return; + } + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_FixSentry(self); + return; + } + + Status_Menu(self, Menu_EngineerFix_SentryGun_Rotate_Input, action, rotl, rot180, rotr, nothing); +}; + +void (float inp) Menu_EngineerFix_SentryGun_Input = { + if (!self.building.real_owner.has_sentry || self.building.real_owner != self + || self.classname != "player" || self.building == world) + return; + + if (inp == 1) { + Engineer_SentryGun_InsertAmmo(self.building); + } else if (inp == 2 && self.building.weapon < 3 && self.ammo_cells >= 130) { + Engineer_SentryGun_Upgrade(self.building); + } else if (inp == 3) { + Engineer_SentryGun_Repair(self.building); + } else if (inp == 4) { + Menu_EngineerFix_SentryGun_Rotate(); + } +}; + +void () Menu_EngineerFix_SentryGun = { + + // only show this menu if old_spanner setting is enabled, otherwise show rotate menu + if (!old_spanner) { + Menu_EngineerFix_SentryGun_Rotate(); + return; + } + + local string action = "Sentry gun maintenance:\n\n"; + local string putammo, upgrade, repair, rotate; + local string nothing = Q"\n\s[5]\s Nothing "; + + if ((self.ammo_shells > 0 && self.building.ammo_shells < self.building.maxammo_shells) + || (self.ammo_rockets > 0 && self.building.weapon == 3 && self.building.ammo_rockets < self.building.maxammo_rockets)) + putammo = Q"\s[1]\s Insert ammo\n"; + else + putammo = "\n"; + + if (self.building.weapon < 3 && self.ammo_cells >= 130) + upgrade = Q"\s[2]\s Upgrade \n"; + else + upgrade = "\n"; + + if (self.building.health < self.building.max_health) + repair = Q"\s[3]\s Repair \n"; + else + repair = "\n"; + + if (self.building.real_owner == self) + rotate = Q"\s[4]\s Rotate \n"; + else + rotate = "\n"; + + Status_Menu(self, Menu_EngineerFix_SentryGun_Input, action, putammo, upgrade, repair, rotate, nothing); +}; + +void (float inp) Menu_Dispenser_Input = { + local float am; + local float empty; + + empty = FALSE; + if (inp == 1) { + if ((self.building.ammo_shells == 0) + && (self.building.ammo_nails == 0) + && (self.building.ammo_rockets == 0) + && (self.building.ammo_cells == 0)) { + empty = TRUE; + } else { + am = self.maxammo_shells - self.ammo_shells; + if (am > self.building.ammo_shells) + am = self.building.ammo_shells; + self.building.ammo_shells = self.building.ammo_shells - am; + self.ammo_shells = self.ammo_shells + am; + + am = self.maxammo_nails - self.ammo_nails; + if (am > self.building.ammo_nails) + am = self.building.ammo_nails; + self.building.ammo_nails = self.building.ammo_nails - am; + self.ammo_nails = self.ammo_nails + am; + + am = self.maxammo_rockets - self.ammo_rockets; + if (am > self.building.ammo_rockets) + am = self.building.ammo_rockets; + self.building.ammo_rockets = self.building.ammo_rockets - am; + self.ammo_rockets = self.ammo_rockets + am; + + am = self.maxammo_cells - self.ammo_cells; + if (am > self.building.ammo_cells) + am = self.building.ammo_cells; + self.building.ammo_cells = self.building.ammo_cells - am; + self.ammo_cells = self.ammo_cells + am; + } + } else if (inp == 2) { + if (self.building.armorvalue == 0) { + empty = TRUE; + } else { + am = self.maxarmor - self.armorvalue; + if (am > self.building.armorvalue) + am = self.building.armorvalue; + + if (self.armortype == 0) { + self.armortype = 0.3; + self.items = self.items | IT_ARMOR1; + } + self.building.armorvalue = self.building.armorvalue - am; + self.armorvalue = self.armorvalue + am; + } + } + if ((inp >= 1) && (inp <= 3)) { + if (empty) + sprint(self, PRINT_HIGH, "The dispenser is empty\n"); + + self.building = world; + self.building_wait = time + 0.5; + + bound_other_ammo(self); + if (self.armorvalue == 0) { + self.armortype = 0; + self.armorclass = 0; + self.items &= ~(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3); + } + } +}; + +void () Menu_Dispenser = { + local string s_action = "Use dispenser:\n\n"; + local string s_ammo, s_armor; + local string s_nothing = Q"\n\s[5]\s Nothing \n\n"; + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_UseDispenser(self, self.building); + return; + } + + if ((self.building.ammo_shells > 0 && self.ammo_shells < self.maxammo_shells) + || (self.building.ammo_nails > 0 && self.ammo_nails < self.maxammo_nails) + || (self.building.ammo_rockets > 0 && self.ammo_rockets < self.maxammo_rockets) + || (self.building.ammo_cells > 0 && self.ammo_cells < self.maxammo_cells)) + s_ammo = Q"\s[1]\s Withdraw some ammo \n"; + else + s_ammo = "\n"; + + if (self.building.armorvalue > 0 && self.armorvalue < self.maxarmor) + s_armor = Q"\s[2]\s Withdraw some armor\n"; + else + s_armor = "\n"; + + Status_Menu(self, Menu_Dispenser_Input, s_action, s_ammo, s_armor, s_nothing); +}; + +void (float inp) Menu_Engineer_Cancel_Input = { + if (inp == 1) + TeamFortress_EngineerBuildStop(); + else + Menu_Engineer_Cancel(); +} + +void () Menu_Engineer_Cancel = { + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Build(self); + return; + } + local string s_build = "Building...\n\n"; + local string s_cancel = Q"\s[1]\s Cancel!\n\n\n\n\n"; + + Status_Menu(self, Menu_Engineer_Cancel_Input, s_build, s_cancel); +} + +void () ClanMode; +void () QuadMode; +void () PubMode; +void () DuelMode; +void () RestartMap; +void () Admin_UpdateServer; +void () Menu_Admin = +{ + if(self.is_admin && infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(self); + UpdateClientMenu_Admin(self); + return; + } + local string s_menu1; + local string s_menu2; + local string override; + local string blanks = " "; + + /* More menu lines, commented for the sake of unecessary warnings + * local string s_menu3; + * local string s_menu4; + * local string s_menu5; + * local string s_menu6; + */ + local string s_menu7; + local string s_menu8; + local string s_menu9; + + local entity te; + local entity temp; + + local float f_tmp; + local float f_tmp2; + + self.impulse = 0; + s_menu1 = ""; + s_menu2 = ""; + + switch (self.current_menu_type) + { + case ADMIN_MENU_TYPE_MAIN: + s_menu1 = "FortressOne Admin Menu: \n\n"; + if (self.current_menu_page == 1) { + s_menu1 = strcat(s_menu1, Q"\s[1]\s Ceasefire \n"); + override = strcat("\x10",ftos(timelimit/60),"\x11"); + override = strcat(substring(blanks, 0, 19 - strlen(override)), override); + s_menu1 = strcat(s_menu1, Q"\s[2]\s Timelimit ", override, "\n"); + s_menu1 = strcat(s_menu1, Q"\s[3]\s Kick \n"); + s_menu1 = strcat(s_menu1, Q"\s[4]\s Ban \n"); + if(captainmode) { + override = " \x10on\x11"; + } else { + override = "\x10off\x11"; + } + s_menu1 = strcat(s_menu1, Q"\s[5]\s Captain ",override,"\n"); + s_menu1 = strcat(s_menu1, Q"\s[6]\s Randomize Teams \n"); + s_menu1 = strcat(s_menu1, Q"\s[7]\s Restart current map \n"); + } else if (self.current_menu_page == 2) { + if(clanbattle) { + override = " \x10on\x11"; + } else { + override = "\x10off\x11"; + } + s_menu1 = strcat(s_menu1, Q"\s[1]\s Clan Mode ",override,"\n"); + if(quadmode) { + override = " \x10on\x11"; + } else { + override = "\x10off\x11"; + } + s_menu1 = strcat(s_menu1, Q"\s[2]\s Quad Mode ",override,"\n"); + if(!clanbattle && !quadmode && !duelmode) { + override = " \x10on\x11"; + } else { + override = "\x10off\x11"; + } + s_menu1 = strcat(s_menu1, Q"\s[3]\s Pub Mode ",override,"\n"); + if(duelmode) { + override = " \x10on\x11"; + } else { + override = "\x10off\x11"; + } + s_menu1 = strcat(s_menu1, Q"\s[4]\s Duel Mode ",override,"\n"); + s_menu1 = strcat(s_menu1, Q"\s[5]\s Force Spectate \n"); + s_menu1 = strcat(s_menu1, Q"\s[6]\s Ready Status \n"); + s_menu1 = strcat(s_menu1, Q"\s[7]\s Force Match Start (Be Nice) \n"); + } else if (self.current_menu_page == 3) { + s_menu1 = strcat(s_menu1, Q"\s[1]\s Class Settings \n"); + } + s_menu1 = strcat(s_menu1, "\n\n"); + s_menu1 = strcat(s_menu1, Q"\s[8]\s Previous Page \n"); + s_menu1 = strcat(s_menu1, Q"\s[9]\s Next Page \n"); + s_menu1 = strcat(s_menu1, Q"\s[0]\s Exit Menu \n"); + + Status_Menu(self, Menu_Admin_Input, s_menu1); + break; + case ADMIN_MENU_TYPE_QUADMODE: + s_menu1 = "Quad Mode Admin Menu: \n\n"; + + s_menu1 = strcat(s_menu1, Q"\s[1]\s Rounds \n"); + s_menu1 = strcat(s_menu1, Q"\s[2]\s Round Timelimit \n"); + s_menu1 = strcat(s_menu1, "\n\n"); + s_menu1 = strcat(s_menu1, Q"\s[0]\s Exit Menu \n"); + + Status_Menu(self, Menu_Admin_Input, s_menu1); + break; + case ADMIN_MENU_TYPE_QUAD_ROUNDNUM: + s_menu1 = "Number of Rounds Input Menu: \n\n"; + s_menu1 = strcat(s_menu1, "Enter a number between 1 and 10 \n"); + s_menu1 = strcat(s_menu1, "\n\n"); + + Status_Menu(self, Menu_Admin_Input, s_menu1); + break; + case ADMIN_MENU_TYPE_QUAD_ROUNDTL: + s_menu1 = "Round Time Input Menu: \n\n"; + //s_menu1 = strcat(s_menu1, "Enter a number between 1 and 10 \n"); + //s_menu1 = strcat(s_menu1, "\n\n"); + + s_menu1 = strcat(s_menu1, Q"\s[1]\s 1 minute \n"); + s_menu1 = strcat(s_menu1, Q"\s[2]\s 5 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[3]\s 10 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[4]\s 15 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[5]\s 20 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[6]\s 25 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[7]\s 30 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[8]\s 35 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[9]\s 45 minutes \n"); + s_menu1 = strcat(s_menu1, Q"\s[0]\s 60 minutes \n"); + + Status_Menu(self, Menu_Admin_Input, s_menu1); + break; + case ADMIN_MENU_TYPE_KICK: + s_menu1 = "Admin Kick Menu: \n\n"; + break; + case ADMIN_MENU_TYPE_BAN: + s_menu1 = "Admin Ban Menu: \n\n"; + break; + case ADMIN_MENU_TYPE_CAPTAINTEAMONE: + s_menu1 = "Captain Team 1 Select Menu: \n\n"; + break; + case ADMIN_MENU_TYPE_CAPTAINTEAMTWO: + s_menu1 = "Captain Team 2 Select Menu: \n\n"; + break; + case ADMIN_MENU_TYPE_CAPTAINSELECT: + s_menu1 = strcat(Q"\x10\sCaptain\s\x11 Team ", ftos(self.team_no)); + s_menu1 = strcat(s_menu1, " : \n\n"); + break; + case ADMIN_MENU_TYPE_CLASSES: + s_menu1 = "Class Management Menu: \n\n"; + //override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + if(override_mapclasses) { + override = " \x10on\x11"; + } else { + override = "\x10off\x11"; + } + if (self.current_menu_page == 1) { + s_menu1 = strcat(s_menu1, Q"\s[1]\s Override Map Settings ", override, "\n"); + //s_menu1 = strcat(s_menu1, Q"\s[1]\s Override Map Settings \n"); + //s_menu1 = strcat(s_menu1, Q"\s[2]\s Scout \n"); + //s_menu1 = strcat(s_menu1, Q"\s[3]\s Sniper \n"); + //s_menu1 = strcat(s_menu1, Q"\s[4]\s Soldier \n"); + //s_menu1 = strcat(s_menu1, Q"\s[5]\s Demoman \n"); + //s_menu1 = strcat(s_menu1, Q"\s[6]\s Medic \n"); + //s_menu1 = strcat(s_menu1, Q"\s[7]\s Heavy \n"); + } else if (self.current_menu_page == 2) { + //s_menu1 = strcat(s_menu1, Q"\s[1]\s Pyro \n"); + //s_menu1 = strcat(s_menu1, Q"\s[2]\s Spy \n"); + //s_menu1 = strcat(s_menu1, Q"\s[3]\s Engineer \n"); + //s_menu1 = strcat(s_menu1, Q"\s[4]\s RandomPC \n"); + //s_menu1 = strcat(s_menu1, Q"\s[5]\s \n"); + //s_menu1 = strcat(s_menu1, Q"\s[6]\s \n"); + //s_menu1 = strcat(s_menu1, Q"\s[7]\s \n"); + } + s_menu1 = strcat(s_menu1, "\n\n"); + s_menu1 = strcat(s_menu1, Q"\s[8]\s Previous Page \n"); + s_menu1 = strcat(s_menu1, Q"\s[9]\s Next Page \n"); + s_menu1 = strcat(s_menu1, Q"\s[0]\s Exit Menu \n"); + + Status_Menu(self, Menu_Admin_Input, s_menu1); + break; + } + + // jesus christ what is this, this should be separated out for the good of the people + // or maybe made understandable + if (self.current_menu_type == ADMIN_MENU_TYPE_KICK || self.current_menu_type == ADMIN_MENU_TYPE_BAN + || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMONE || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO + || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_FORCESPEC) + { + f_tmp = 0; + f_tmp2 = 0; + s_menu2 = strcat (s_menu2, "\bPlayers:\b \n"); + s_menu2 = "\sPage\s "; + s_menu2 = strcat (s_menu2, ftos(self.current_menu_page)); + s_menu2 = strcat (s_menu2, "\n"); + s_menu2 = Menu_Indent_line(s_menu2, 30); + + te = find (world, classname, "player"); + while (te != world) { + if ( (f_tmp < (self.current_menu_page * 7) ) && ( f_tmp >= ((self.current_menu_page - 1) * 7) ) && ( ( ( self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO ) && !te.captain) || (self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINSELECT && self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINTEAMTWO) ) ) { + f_tmp2 = f_tmp2 + 1; + f_tmp = f_tmp + 1; + s_menu2 = strcat( s_menu2, Q"\s[\s" ); + s_menu2 = strcat( s_menu2, ftos(f_tmp2)); + s_menu2 = strcat( s_menu2, Q"\s]\s " ); + if (strlen(te.netname) <= 26) { + s_menu2 = strcat( s_menu2, te.netname ); + } + else { + s_menu2 = strcat( s_menu2, substring(te.netname,0,25)); + } + s_menu2 = Menu_Indent_line(s_menu2, 30); + } + + if (( f_tmp < (self.current_menu_page - 1) * 7) && ( ( ( self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO ) && !te.captain) || ( self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINSELECT && self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINTEAMTWO ) ) ) { + f_tmp = f_tmp + 1; + } + + te = find (te, classname, "player"); + } + + if (self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINTEAMONE && self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINTEAMTWO && self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINSELECT && self.current_menu_type != ADMIN_MENU_TYPE_FORCESPEC ) { + te = find (world, classname, "observer"); + while (te != world) { + if ( (f_tmp <= (self.current_menu_page * 7) ) && (f_tmp > (self.current_menu_page - 1) * 7) && te.netname != "") { + f_tmp2 = f_tmp2 + 1; + s_menu2 = strcat( s_menu2, Q"\s[\s" ); + s_menu2 = strcat( s_menu2, ftos(f_tmp2)); + s_menu2 = strcat( s_menu2, Q"\s]\s " ); + s_menu2 = strcat( s_menu2, te.netname ); + if (strlen(te.netname) <= 26) { + s_menu2 = strcat( s_menu2, te.netname ); + } + else { + s_menu2 = strcat( s_menu2, substring(te.netname,0,25)); + } + s_menu2 = Menu_Indent_line(s_menu2, 30); + } + f_tmp = f_tmp + 1; + te = find (te, classname, "observer"); + } + } + + if (f_tmp == 0 && ( self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO ) ) { + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s \sTeams are set, let's start the game!\s\n"); + temp = self; + te = find (world, classname, "player"); + while (te != world) { + te.captain = 0; + self = te; + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + te = find (te, classname, "player"); + } + self = temp; + captainmode = 0; + return; + } + s_menu2 = strcat( s_menu2, "\n"); + s_menu7 = "\b[\b8\b]\b \bPrevious Page\b "; + s_menu7 = Menu_Indent_line(s_menu7, 30); + s_menu8 = "\b[\b9\b]\b \bNext Page\b "; + s_menu8 = Menu_Indent_line(s_menu8, 30); + if (self.is_admin || self.captain == 1) { + s_menu9 = "\b[\b0\b]\b \bBack to Main Menu\b "; + s_menu9 = Menu_Indent_line(s_menu9, 30); + } else { + s_menu9 = "\n"; + } + if ((self.is_admin || self.captain == 1) && captainmode) { + s_menu9 = strcat(s_menu9, "Option 0 results in canceling captain mode.\n"); + } + + s_menu1 = strcat(s_menu1, s_menu2); + s_menu1 = strcat(s_menu1, s_menu7); + s_menu1 = strcat(s_menu1, s_menu8); + s_menu1 = strcat(s_menu1, s_menu9); + + Status_Menu(self, Menu_Admin_Input, s_menu1); + } +}; + +void (float inp) Menu_Admin_Input = +{ + local entity temp; + local entity te; + local string s_temp; + local float f_tmp; + + // Actions for AdminMenu Page 0 + if (self.current_menu_type == ADMIN_MENU_TYPE_MAIN) { + if (self.current_menu_page == 1) + { + switch (inp) + { + case 1: // Ceasefire + if (ceasefire_type) + Admin_Pause(); + else + Admin_CeaseFire(); + return; + case 2: // Change Timelimit + if (stof(infokey (world, "timelimit")) > 35) { + s_temp = "5"; + } else { + s_temp = ftos(timelimit/60 + 5); + } + + localcmd ("timelimit "); + localcmd(s_temp); + localcmd("\n"); + + bprint ( 2, self.netname); + bprint ( 2, " sets "); + bprint ( 2, "\stimelimit\s to: "); + bprint ( 2, s_temp); + bprint ( 2, "\n"); + break; + case 3: // Kick Player Menu + self.current_menu_type = ADMIN_MENU_TYPE_KICK; + self.current_menu_page = 1; + Menu_Admin(); + break; + case 4: // Ban Player Menu + self.current_menu_type = ADMIN_MENU_TYPE_BAN; + self.current_menu_page = 1; + Menu_Admin(); + break; + case 5: // Toggle Captain (pickup) Mode + if (!captainmode) { + captainmode = 1; + self.current_menu_type = ADMIN_MENU_TYPE_CAPTAINTEAMONE; + self.current_menu_page = 1; + + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s is now \sON\s.\n"); + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s ONLY CAPTAINS CAN TALK! YOU ARE ALL \sMUTED\s.\n"); + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s ONLY CAPTAINS CAN TALK! YOU ARE ALL \sMUTED\s.\n"); + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s ONLY CAPTAINS CAN TALK! YOU ARE ALL \sMUTED\s.\n"); + + temp = find (world, classname, "player"); + while (temp != world) { + temp.captain = 0; + temp = find (temp, classname, "player"); + } + + } else { // end captainmode1 + disableCaptain(); + local entity pl; + local entity temppl; + pl = self; + temppl = find (world, classname, "player"); + while (temppl != world) { + self = temppl; + temppl.captain = 0; + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + temppl = find (temppl, classname, "player"); + } + self = pl; + } + Menu_Admin(); + break; + case 6: // Randomize Teams Option + randomizeTeams(); + break; + + case 7: // Restart Current Map Option + RestartMap(); + break; + case 9: + Admin_UpdateServer(); + break; + } + } + else if (self.current_menu_page == 2) + { + switch (inp) + { + case 1: // Toggle Clanmode Option + ClanMode(); + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + break; + case 2: // quad mode + QuadMode(); + self.current_menu_type = ADMIN_MENU_TYPE_QUADMODE; + self.current_menu_page = 1; + Menu_Admin(); + break; + case 3: // pub mode + PubMode(); + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + break; + case 4: // duel mode + DuelMode(); + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + break; + case 5: // force spectator + self.current_menu_type = ADMIN_MENU_TYPE_FORCESPEC; + self.current_menu_page = 1; + Menu_Admin(); + break; + case 6: // ready status + Broadcast_Players_NotReady(); + Menu_Close(self); + return; + case 7: // force match start + ForceStartMatch(); + Menu_Close(self); + return; + } + } + else if (self.current_menu_page == 3) + { + switch (inp) + { + case 1: + self.current_menu_type = ADMIN_MENU_TYPE_CLASSES; + self.current_menu_page = 1; + Menu_Admin(); + return; + } + } + } + else if ((self.current_menu_type == ADMIN_MENU_TYPE_KICK || self.current_menu_type == ADMIN_MENU_TYPE_BAN || self.current_menu_type == ADMIN_MENU_TYPE_FORCESPEC) + && inp >= 1 && inp <= 7) + { // Kick / Ban Actions + //bprint(PRINT_HIGH, "Getting player ", ftos(inp), "\n"); + f_tmp = 1; + self.admin_use = find (world, classname, "player"); + while (self.admin_use != world) { + if (f_tmp < ((self.current_menu_page - 1) * 7) + inp ) { + f_tmp = f_tmp + 1; + self.admin_use = find (self.admin_use, classname, "player"); + } else { + break; + } + } + if (f_tmp < ((self.current_menu_page - 1) * 7) + inp) { + self.admin_use = find (world, classname, "observer"); + while (self.admin_use != world) { + if (f_tmp < ((self.current_menu_page - 1) * 7) + inp && self.admin_use.netname != "") { + f_tmp = f_tmp + 1; + } else { + break; + } + self.admin_use = find (self.admin_use, classname, "observer"); + } + } + if (self.admin_use) { + //bprint(PRINT_HIGH, "Player found: ", ftos(f_tmp), ", ", ftos(((self.current_menu_page - 1) * 7) + inp),", ", self.admin_use.classname , "\n"); + if (f_tmp == ((self.current_menu_page - 1) * 7) + inp) { + if(self.current_menu_type == ADMIN_MENU_TYPE_FORCESPEC) { + Admin_ForceSpectator(); + Menu_Close(self); + } else { + self.admin_use.ip = infokey (self.admin_use, "ip"); + if (self.current_menu_type == ADMIN_MENU_TYPE_BAN) { + localcmd("addip "); + localcmd(self.admin_use.ip); + localcmd("\n"); + } + Admin_DoKick(); + } + } + } + } + else if ((self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMONE + || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO + || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT) && inp >= 1 && inp <= 7) + { // Captain Actions + f_tmp = 1; + self.admin_use = find (world, classname, "player"); + while (self.admin_use != world) { + if ( (f_tmp < ((self.current_menu_page - 1) * 7) + inp ) && ( ( (self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO) && !self.admin_use.captain) || (self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINSELECT && self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINTEAMTWO) ) ) { + f_tmp = f_tmp + 1; + } else if ( (f_tmp == ((self.current_menu_page - 1) * 7) + inp) && ( ( (self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO) && !self.admin_use.captain) || (self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINSELECT && self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINTEAMTWO) ) ) { + break; + } + self.admin_use = find (self.admin_use, classname, "player"); + + } + + if (self.current_menu_type != ADMIN_MENU_TYPE_CAPTAINSELECT) { + if (self.admin_use != world) { + if (f_tmp == ((self.current_menu_page - 1) * 7) + inp) { + temp = self; + self = self.admin_use; + self.captain = 9; + if (temp.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMONE) { + // Captain for Team 1 + self.team_no = 1; + temp.current_menu_type = ADMIN_MENU_TYPE_CAPTAINTEAMTWO; + Menu_Admin(); + } else if (temp.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO) { + // Captain for Team 2 + self.team_no = 2; + + randomizeCaptains(); + + te = find (world, classname, "player"); + while (te != world) + { + self = te; + if (!self.captain) + { + self.playerclass = 0; + playerSetTeam (-1); + self.current_menu = 0; + self.impulse = 0; + } else if (self.captain > 0 && self.captain < 5) { + //TeamFortress_TeamSet(self.team_no); + self.playerclass = 0; + playerSetTeam(self.team_no); + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s "); + bprint(2, self.netname); + bprint(2, " \bIs the captain for team\b "); + bprint(2, TeamToString(self.team_no)); + bprint(2, "\b.\b\n"); + } + te = find (te, classname, "player"); + } + self = temp; + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s All available players are now Observers\n"); + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + } + self = temp; + } + } + } else { + if (self.admin_use && f_tmp == ((self.current_menu_page - 1) * 7) + inp && self.captain == 1) { + temp = self; + self = self.admin_use; + self.captain = 10; + + playerSetTeam (temp.team_no); + + bprint(2, "\x10\bCaptain Mode\b\x11\b:\b "); + bprint(2, temp.netname); + bprint(2, " \bcalled\b "); + bprint(2, self.netname); + bprint(2, " \bfor team\b "); + bprint(2, TeamToString(self.team_no)); + bprint(2, "\b.\b\n"); + self = temp; + + nextCaptain(); + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + } + } + } + else if (self.current_menu_type == ADMIN_MENU_TYPE_QUADMODE) + { + switch (inp) + { + case 1: + // rounds + self.current_menu_type = ADMIN_MENU_TYPE_QUAD_ROUNDNUM; + self.current_menu_page = 1; + Menu_Admin(); + break; + case 2: + // round time limit + self.current_menu_type = ADMIN_MENU_TYPE_QUAD_ROUNDTL; + self.current_menu_page = 1; + Menu_Admin(); + break; + } + } + else if (self.current_menu_type == ADMIN_MENU_TYPE_QUAD_ROUNDNUM) + { + SetQuadRounds(inp); + self.current_menu_type = ADMIN_MENU_TYPE_QUADMODE; + self.current_menu_page = 1; + Menu_Admin(); + } + else if (self.current_menu_type == ADMIN_MENU_TYPE_QUAD_ROUNDTL) + { + local string d; + local float rt = (inp - 1) * 5; + switch(inp) { + case 1: + rt = 1; + break; + case 9: + rt = 45; + break; + case 10: + rt = 60; + break; + } + d = strcat("localinfo round_time ", ftos(rt), "\n"); + localcmd(d); + bprint(2, "Quad Round Timelimit changed to ", ftos(rt), "\n"); + self.current_menu_type = ADMIN_MENU_TYPE_QUADMODE; + self.current_menu_page = 1; + Menu_Admin(); + } + else if (self.current_menu_type == ADMIN_MENU_TYPE_CLASSES) + { + if (self.current_menu_page == 1) + { + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + switch (inp) + { + case 1: + override_mapclasses = !override_mapclasses; + localcmd("serverinfo omc "); + localcmd(ftos(override_mapclasses)); + localcmd("\n"); + bprint(PRINT_HIGH, self.netname, " has set server class overrides to ", ftos(override_mapclasses), "\n"); + Menu_Admin(); + return; + } + } + } + + //Previous page + if (inp == 8) { + if (self.current_menu_page > 1) { + self.current_menu_page = self.current_menu_page - 1; + Menu_Admin(); + } + } + //Next page + if (inp == 9) { + if (self.current_menu_page < 5) { + self.current_menu_page = self.current_menu_page + 1; + Menu_Admin(); + } + } + + //Closes menus, disables captain mode + if (inp == 10) { + if (self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMTWO || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINSELECT || self.current_menu_type == ADMIN_MENU_TYPE_CAPTAINTEAMONE) { + captainmode = 0; + bprint(2, "\x10\sCaptain Mode\s\x11\s:\s \scanceled by request of\s "); + bprint(2, self.netname); + bprint(2, ".\n"); + te = self; + temp = find (world, classname, "player"); + while (temp != world) { + self = temp; + temp.captain = 0; + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + temp = find (temp, classname, "player"); + } + self = te; + } + if (self.current_menu_type == ADMIN_MENU_TYPE_MAIN) { + Menu_Close(self); + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + } + else if (self.is_admin) { + self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + self.current_menu_page = 1; + Menu_Admin(); + } + } + self.impulse = 0; +}; diff --git a/misc.qc b/ssqc/misc.qc similarity index 64% rename from misc.qc rename to ssqc/misc.qc index 1d3703015..cd290ca25 100644 --- a/misc.qc +++ b/ssqc/misc.qc @@ -1,9 +1,26 @@ +float GetQ3State(string st); void () info_null = { dremove(self); }; void () info_notnull = { + self.state = GetQ3State(self.initialstate); + + for (int i = 1; i < (tokenize(__fullspawndata) - 1); i += 2) { + switch (argv(i)) { + case "flags": + string s = argv(i + 1); + if (s == "chargeable") + { + self.flagsq3 = s; + self.flags = FL_FINDABLE_NONSOLID; + } + break; + default: + break; + } + } }; void () light_use = { @@ -149,7 +166,7 @@ void () fire_fly = { newmis.velocity_y = random() * 100 - 50; newmis.velocity_z = self.speed + random() * 200; newmis.classname = "fireball"; - setmodel(newmis, "progs/lavaball.mdl"); + FO_SetModel(newmis, "progs/lavaball.mdl"); setsize(newmis, '0 0 0', '0 0 0'); setorigin(newmis, self.origin); newmis.nextthink = time + 5; @@ -189,7 +206,7 @@ void () misc_explobox = { self.solid = 2; self.movetype = 0; precache_model("maps/b_explob.bsp"); - setmodel(self, "maps/b_explob.bsp"); + FO_SetModel(self, "maps/b_explob.bsp"); setsize(self, '0 0 0', '32 32 64'); precache_sound("weapons/r_exp3.wav"); self.health = 20; @@ -217,7 +234,7 @@ void () misc_explobox2 = { self.solid = 2; self.movetype = 0; precache_model2("maps/b_exbox2.bsp"); - setmodel(self, "maps/b_exbox2.bsp"); + FO_SetModel(self, "maps/b_exbox2.bsp"); setsize(self, '0 0 0', '32 32 32'); precache_sound("weapons/r_exp3.wav"); self.health = 20; @@ -247,7 +264,7 @@ void () Laser_Touch = { dremove(self); return; } - sound(self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + FO_Sound(self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); org = self.origin - 8 * normalize(self.velocity); if (other.health) { SpawnBlood(org, 15); @@ -266,7 +283,7 @@ void () Laser_Touch = { void (vector org, vector vec) LaunchLaser = { if (self.classname == "monster_enforcer") - sound(self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); vec = normalize(vec); newmis = spawn(); @@ -274,7 +291,7 @@ void (vector org, vector vec) LaunchLaser = { newmis.movetype = MOVETYPE_FLY; newmis.solid = SOLID_BBOX; newmis.effects = EF_DIMLIGHT; - setmodel(newmis, "progs/laser.mdl"); + FO_SetModel(newmis, "progs/laser.mdl"); setsize(newmis, '0 0 0', '0 0 0'); setorigin(newmis, org); newmis.velocity = vec * 600; @@ -284,12 +301,34 @@ void (vector org, vector vec) LaunchLaser = { newmis.touch = Laser_Touch; }; +void (vector org, vector dir) launch_spike = { + newmis = spawn(); + newmis.voided = 0; + newmis.owner = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + + newmis.angles = vectoangles(dir); + newmis.touch = spike_touch; + newmis.weapon = DMSG_NAILGUN; + newmis.classname = "spike"; + newmis.think = SUB_Remove; + newmis.nextthink = time + 6; + fo_projectile* fdesc = FPP_Get(FPP_NAIL); + FO_SetModel(newmis, fdesc->model); + setsize(newmis, VEC_ORIGIN, VEC_ORIGIN); + setorigin(newmis, org); + + newmis.velocity = dir * fdesc->speed; +}; + + void () spikeshooter_use = { if (self.spawnflags & 2) { - sound(self, CHAN_VOICE, "enforcer/enfire.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "enforcer/enfire.wav", 1, ATTN_NORM); LaunchLaser(self.origin, self.movedir); } else { - sound(self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); launch_spike(self.origin, self.movedir); newmis.velocity = self.movedir * 500; @@ -348,7 +387,7 @@ void () air_bubbles = { void () make_bubbles = { newmis = spawn(); - setmodel(newmis, "progs/s_bubble.spr"); + FO_SetModel(newmis, "progs/s_bubble.spr"); setorigin(newmis, self.origin); newmis.movetype = MOVETYPE_NOCLIP; newmis.solid = SOLID_NOT; @@ -366,7 +405,7 @@ void () make_bubbles = { void () bubble_split = { newmis = spawn(); - setmodel(newmis, "progs/s_bubble.spr"); + FO_SetModel(newmis, "progs/s_bubble.spr"); setorigin(newmis, self.origin); newmis.movetype = MOVETYPE_NOCLIP; newmis.solid = SOLID_NOT; @@ -436,11 +475,33 @@ void () viewthing = { self.movetype = MOVETYPE_NONE; self.solid = SOLID_NOT; precache_model("progs/player.mdl"); - setmodel(self, "progs/player.mdl"); + FO_SetModel(self, "progs/player.mdl"); }; void () func_wall_use = { - self.frame = (1 - self.frame); + if (self.spawnflags & WALL_SOLID_NOT_ON_USE) + { + self.solid = self.solid ? SOLID_NOT : SOLID_BSP; + } + + if (self.spawnflags & WALL_HIDE_ON_USE) + { + if (self.dimension_seen != DMN_INVISIBLE) + { + self.old_dimension_seen = self.dimension_seen; + self.dimension_seen = DMN_INVISIBLE; + } + else + { + float val = self.dimension_seen; + self.dimension_seen = self.old_dimension_seen; + self.old_dimension_seen = val; + } + } + else + { + self.frame = (1 - self.frame); + } }; void () func_wall = { @@ -450,9 +511,44 @@ void () func_wall = { } self.angles = '0 0 0'; self.movetype = MOVETYPE_PUSH; - self.solid = SOLID_BSP; - self.use = func_wall_use; + self.use = func_wall_use; + + if (self.spawnflags & WALL_SOLID_NOT) + { + self.solid = SOLID_NOT; + } + else + { + self.solid = SOLID_BSP; + } + float team_bit = 0; + switch (self.team_no) + { + case TEAM_BLUE: + team_bit = DMN_TEAMBLUE; + break; + case TEAM_RED: + team_bit = DMN_TEAMRED; + break; + case TEAM_YELL: + team_bit = DMN_TEAMYELL; + break; + case TEAM_GREN: + team_bit = DMN_TEAMGREN; + break; + default: + break; + } + self.dimension_seen = self.dimension_seen | team_bit; + + if (self.dimension_seen & DMN_INVISIBLE) + self.old_dimension_seen = DMN_NOFLASH | DMN_FLASH | team_bit; + else + self.old_dimension_seen = self.dimension_seen | DMN_NOFLASH | DMN_FLASH; + + setmodel(self, self.model); + //self.mdl = self.model; }; void () func_illusionary = { @@ -581,13 +677,13 @@ void () ambient_swamp2 = { void () noise_think = { self.nextthink = time + 0.5; - sound(self, 1, "enforcer/enfire.wav", 1, ATTN_NORM); - sound(self, 2, "enforcer/enfstop.wav", 1, ATTN_NORM); - sound(self, 3, "enforcer/sight1.wav", 1, ATTN_NORM); - sound(self, 4, "enforcer/sight2.wav", 1, ATTN_NORM); - sound(self, 5, "enforcer/sight3.wav", 1, ATTN_NORM); - sound(self, 6, "enforcer/sight4.wav", 1, ATTN_NORM); - sound(self, 7, "enforcer/pain1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "enforcer/enfstop.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_ITEM, "enforcer/sight1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_BODY, "enforcer/sight2.wav", 1, ATTN_NORM); + FO_Sound(self, 5, "enforcer/sight3.wav", 1, ATTN_NORM); + FO_Sound(self, 6, "enforcer/sight4.wav", 1, ATTN_NORM); + FO_Sound(self, 7, "enforcer/pain1.wav", 1, ATTN_NORM); }; @@ -609,3 +705,203 @@ void () misc_noisemaker = { self.nextthink = time + 0.1 + random(); self.think = noise_think; }; + +void() blocker_use = +{ + if (!(self.state)) + { + self.state = 1; + setorigin(self, self.origin - '8000 8000 8000'); + sound(self, CHAN_VOICE, self.noise1, 1, 1); + } + else + { + self.state = 0; + setorigin(self, self.origin + '8000 8000 8000'); + sound(self, CHAN_VOICE, self.noise, 1, 1); + } +}; + +void(entity attacker, float damage) glass_pain = +{ + if (self.spawnflags & 8) + { + self.health = self.max_health; + } +}; + +void() glass_die = +{ + local entity new; + local vector tmpvec; + while (self.cnt2 > 0) + { + new = spawn(); + new.origin = self.origin; + if (random() < 0.5) + { + FO_SetModel(new, "progs/glass2.mdl"); + } + else + { + FO_SetModel(new, "progs/glass1.mdl"); + } + setsize(new, '0 0 0', '0 0 0'); + if (self.height != 100) + { + new.velocity_x = 70 * crandom(); + new.velocity_y = 70 * crandom(); + new.velocity_z = 140 + 70 * random(); + } + else + { + new.velocity_x = 400 * crandom(); + new.velocity_y = 400 * crandom(); + new.velocity_z = 140 + 70 * random(); + } + new.movetype = 10; + new.solid = 2; + new.avelocity_x = random() * 600; + new.avelocity_y = random() * 600; + new.avelocity_z = random() * 600; + new.nextthink = time + 2 + random() * 3; + new.think = SUB_Remove; + self.absmin = self.origin + self.mins; + self.absmax = self.origin + self.maxs; + tmpvec_x = self.absmin_x + random() * (self.absmax_x - self.absmin_x); + tmpvec_y = self.absmin_y + random() * (self.absmax_y - self.absmin_y); + tmpvec_z = self.absmin_z + random() * (self.absmax_z - self.absmin_z); + setorigin(new, tmpvec); + self.cnt2 = self.cnt2 - 1; + } + if (self.noise2) + { + if (pointcontents(self.origin) == -3) + { + sound(self, CHAN_VOICE, "effects/rcksplsh.wav", 1, 1); + } + else + { + sound(self, CHAN_VOICE, self.noise2, 1, 1); + } + } + remove(self); +}; + +void() func_glass = +{ + local vector tmpvec; + self.movetype = 7; + self.solid = 4; + self.mdl = self.model; + setmodel(self, self.model); + setsize(self, self.mins, self.maxs); + setorigin(self, self.origin); + self.model = string_null; + precache_sound("misc/null.wav"); + precache_sound("effects/rcksplsh.wav"); + precache_sound("effects/shatter.wav"); + precache_model("progs/glass2.mdl"); + precache_model("progs/glass1.mdl"); + + if (self.health > 0) + { + if (!(self.cnt2)) + { + tmpvec = self.maxs - self.mins; + tmpvec = tmpvec * 0.031; + if (tmpvec_x < 1) + { + tmpvec_x = 1; + } + if (tmpvec_y < 1) + { + tmpvec_y = 1; + } + if (tmpvec_z < 1) + { + tmpvec_z = 1; + } + self.cnt2 = tmpvec_x * tmpvec_y * tmpvec_z; + } + else + { + if (self.cnt2 == -1) + { + self.cnt2 = 0; + } + } + if (self.cnt2 > 16) + { + self.cnt2 = 16; + } + self.takedamage = 1; + self.max_health = self.health; + self.th_die = glass_die; + self.th_pain = glass_pain; + precache_model("progs/glass1.mdl"); + precache_model("progs/glass2.mdl"); + } + if (self.target) + { + if (!(self.speed)) + { + self.speed = 100; + } + if (!(self.dmg)) + { + self.dmg = 2; + } + if (self.sounds == 1) + { + if (!(self.noise)) + { + self.noise = "plats/train2.wav"; + } + if (!(self.noise1)) + { + self.noise1 = "plats/train1.wav"; + } + precache_sound(self.noise); + precache_sound(self.noise1); + } + self.cnt = 1; + self.blocked = train_blocked; + self.use = train_use; + self.classname = "train"; + self.think = func_train_find; + self.nextthink = self.ltime + 0.6; + } + else + { + self.use = blocker_use; + if (self.spawnflags & 4) + { + self.state = 0; + setorigin(self, self.origin + '8000 8000 8000'); + } + else + { + self.state = 1; + if (self.noise1) + { + sound(self, CHAN_VOICE, self.noise1, 1, 1); + } + } + } + if (!(self.noise)) + { + self.noise = "misc/null.wav"; + } + if (!(self.noise1)) + { + self.noise1 = "misc/null.wav"; + } + if (!(self.noise2)) + { + self.noise2 = "effects/shatter.wav"; + } + precache_sound(self.noise); + precache_sound(self.noise1); + precache_sound(self.noise2); +}; diff --git a/monsters.qc b/ssqc/monsters.qc similarity index 99% rename from monsters.qc rename to ssqc/monsters.qc index 0cbafdd1e..e871395e2 100644 --- a/monsters.qc +++ b/ssqc/monsters.qc @@ -111,7 +111,7 @@ void () t_movetarget = { } }; - +/* float (float v) anglemod = { while (v >= 360) v = v - 360; @@ -119,7 +119,7 @@ float (float v) anglemod = { v = v + 360; return (v); }; - +*/ float (entity targ) range = { local vector spot1; local vector spot2; diff --git a/ssqc/mvdsv.qc b/ssqc/mvdsv.qc new file mode 100644 index 000000000..11147cd95 --- /dev/null +++ b/ssqc/mvdsv.qc @@ -0,0 +1,138 @@ +float () UserCmd = +{ + float isProcessed; + string arg1, arg2, arg3, arg4; + arg1 = argv_mvdsv(0); + arg2 = argv_mvdsv(1); + arg3 = argv_mvdsv(2); + arg4 = argv_mvdsv(3); + + isProcessed = ParseCmds(arg1, arg2, arg3, arg4); + return (isProcessed); +}; + +float (string cmd) ConsoleCmd { + local float arg_num; + local string arg_val0, arg_val1, arg_val2; + local string tmp; + + if(cmd) { + //FTESV implementation + arg_num = tokenize(cmd); + arg_val0 = argv(0); + arg_val1 = argv(1); + arg_val2 = argv(2); + switch(argv(0)) { + case "vote_addmap": + AddVoteMap(argv(1),argv(2),argv(3),stof(argv(4)),stof(argv(5)),stof(argv(6))); + return TRUE; + case "vote_removemap": + RemoveVoteMap(argv(1)); + return TRUE; + case "nextmap": + if(FO_GetUserSetting (world, "nomapcycle", "nmc", "0")) { + bprint(PRINT_HIGH, "Tried setting next map to ", argv(1), ", but nomapcycle is set.\n"); + } else { + nextmap = argv(1); + bprint(PRINT_HIGH, "Next map set to ", nextmap, ". Enable nomapcycle to skip.\n"); + } + return TRUE; + } + } else { + //MVDSV implementation + arg_val0 = argv_mvdsv(0); + arg_val1 = argv_mvdsv(1); + arg_val2 = argv_mvdsv(2); + arg_num = argc(); + } + if (arg_num == 0) { + return (0); + } + if (arg_val0 == "prematch") { + if (arg_num == 2) { + localcmd("localinfo prematch "); + localcmd(arg_val1); + localcmd("\n"); + return (1); + } + if (arg_num == 1) { + tmp = infokey (world, arg_val0); + dprint("prematch is "); + dprint("\""); + dprint(tmp); + dprint("\"\n"); + return (1); + } + } + else if (arg_val0 == "autorecord") + { + if (arg_num == 2) { + localcmd("localinfo demo_auto_left "); + localcmd(arg_val1); + localcmd("\n"); + return (1); + } + if (arg_num == 1) { + tmp = infokey (world, "demo_auto_left"); + if ((stof(tmp) > 0)) { + dprint("Auto-Recording off\n"); + localcmd("localinfo demo_auto_left 0\n"); + return (1); + } + else { + dprint("Auto-Recording the next match\n"); + localcmd("localinfo demo_auto_left 1\n"); + return (1); + } + } + } + else if (arg_val0 == "clan") + { + ClanMode(); + return (1); + } + else if (arg_val0 == "quadmode") + { + QuadMode(); + return (1); + } + else if (arg_val0 == "duelmode") + { + DuelMode(); + return (1); + } + return 0; +} + +void (float duration) GE_PausedTic = { + local entity p; + if (unpause_requested && (unpause_countdown == 0)) { + unpause_countdown = duration + 5; + unpause_lastcountnumber = 5; + } + if (unpause_requested) { + if ((duration >= unpause_countdown)) { + is_paused = 0; + cs_paused = 0; + unpause_countdown = 0; + unpause_requested = 0; + unpause_lastcountnumber = 0; + setpause(0); + NotifyPauseUnpause(FALSE); + } else if (duration >= unpause_countdown - (unpause_lastcountnumber)) { + unpause_lastcountnumber--; + p = find (world, classname, "player"); + while (p) { + if (p.netname != "") + stuffcmd(p, "play buttons/switch04.wav\n"); + p = find(p, classname, "player"); + } + bprint(PRINT_HIGH, + sprintf("Unpausing in %d seconds\n", unpause_lastcountnumber)); + } + } +} + +void(float pauseduration) SV_PausedTic = { + GE_PausedTic(pauseduration); +} diff --git a/plats.qc b/ssqc/plats.qc similarity index 90% rename from plats.qc rename to ssqc/plats.qc index db5860234..d681cb57b 100644 --- a/plats.qc +++ b/ssqc/plats.qc @@ -5,6 +5,7 @@ void () plat_trigger_use; void () plat_go_up; void () plat_go_down; void () plat_crush; +void (entity pe_player, float dontstopdead) Spy_CheckForFuncTouch; void () plat_spawn_inside_trigger = { local entity trigger; @@ -15,6 +16,7 @@ void () plat_spawn_inside_trigger = { trigger.movetype = MOVETYPE_NONE; trigger.solid = SOLID_TRIGGER; trigger.enemy = self; + self.enemy = trigger; trigger.team_no = self.team_no; trigger.playerclass = self.playerclass; trigger.items_allowed = self.items_allowed; @@ -51,25 +53,25 @@ void () plat_spawn_inside_trigger = { }; void () plat_hit_top = { - sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); self.state = STATE_TOP; self.think = plat_go_down; self.nextthink = self.ltime + 3; }; void () plat_hit_bottom = { - sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); self.state = STATE_BOTTOM; }; void () plat_go_down = { - sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); self.state = STATE_DOWN; SUB_CalcMove(self.pos2, self.speed, plat_hit_bottom); }; void () plat_go_up = { - sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); self.state = STATE_UP; SUB_CalcMove(self.pos1, self.speed, plat_hit_top); }; @@ -80,9 +82,19 @@ void () plat_center_touch = { if (other.classname != "player") return; + if (self.team_no != 0) + { + if (other.team_no != self.team_no) + { + return; + } + } + if (other.playerclass == 0) return; + Spy_CheckForFuncTouch(other, 1); + if (!Activated(self, other)) { if (self.else_goal != 0) { te = Findgoal(self.else_goal); @@ -108,6 +120,8 @@ void () plat_outside_touch = { if (other.classname != "player") return; + Spy_CheckForFuncTouch(other, 0); + if (!Activated(self, other)) { if (self.else_goal != 0) { te = Findgoal(self.else_goal); @@ -133,7 +147,7 @@ void () plat_trigger_use = { void () plat_crush = { if (other.classname == "detpack") { sprint(other.owner, PRINT_HIGH, "Your detpack was squashed\n"); - if (other.weaponmode == 1) { + if (other.is_disarming) { TeamFortress_SetSpeed(other.enemy); dremove(other.observer_list); } @@ -193,6 +207,17 @@ void () func_plat = { setmodel(self, self.model); setsize(self, self.mins, self.maxs); + switch(self.allowteams) + { + case "blue": + self.team_no = 1; + break; + case "red": + self.team_no = 2; + break; + default: + } + self.blocked = plat_crush; if (!self.speed) @@ -237,7 +262,7 @@ void () train_use = { void () train_wait = { if (self.wait) { self.nextthink = self.ltime + self.wait; - sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); } else self.nextthink = self.ltime + 0.1; @@ -257,7 +282,7 @@ void () train_next = { else self.wait = 0; - sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait); }; diff --git a/ssqc/player.qc b/ssqc/player.qc new file mode 100644 index 000000000..e5e8762ab --- /dev/null +++ b/ssqc/player.qc @@ -0,0 +1,557 @@ +// name = [framenum, nexttime, nextthink] {code} +// expands to: +// name () +// { +// self.frame=framenum; +// self.nextthink = time + nexttime; +// self.think = nextthink; +// +// }; + +void () bubble_bob; +void () T_Dispenser; +void () Headless_Think; + +void () player_touch = { + local entity Bio; + local entity te; + local float found; + te = world; + + if ((invis_only == 0) && + ((self.playerclass == 8) || (other.playerclass == 8))) { + if (other.classname == "player") { + if ((self.undercover_team != 0) || (self.undercover_skin != 0)) { + if (((other.playerclass == PC_SPY) || + (other.playerclass == PC_SCOUT)) + && (other.team_no != self.team_no)) { + TF_AddFrags(other, 1); + bprint(PRINT_MEDIUM, other.netname, + " uncovered an enemy spy!\n"); + Spy_RemoveDisguise(self); + } + } + if ((other.undercover_team != 0) || + (other.undercover_skin != 0)) { + if (((self.playerclass == PC_SPY) || + (self.playerclass == PC_SCOUT)) + && (self.team_no != other.team_no)) { + TF_AddFrags(self, 1); + bprint(PRINT_MEDIUM, self.netname, + " uncovered an enemy spy!\n"); + Spy_RemoveDisguise(other); + } + } + } + } + if ((self.tfstate & TFSTATE_INFECTED) && (!cb_prematch)) { + if ((other.classname == "player") && (te.playerclass != 0)) { + if (!(other.tfstate & TFSTATE_INFECTED)) { + if (other.playerclass != 5) { + if (!((teamplay & 16) + && (self.owner.team_no == self.enemy.team_no) + && (self.owner.team_no != 0))) { + found = 0; + te = find(world, classname, "timer"); + while ((te != world) && (found == 0)) { + if ((te.owner == self) && + (te.think == BioInfection_Decay)) { + found = 1; + } else { + te = find(te, classname, "timer"); + } + } + Bio = spawn(); + Bio.nextthink = 2; + Bio.think = BioInfection_Decay; + Bio.owner = other; + Bio.classname = "timer"; + Bio.enemy = te.enemy; + other.tfstate = other.tfstate | TFSTATE_INFECTED; + other.infection_team_no = self.infection_team_no; + LogEventAffliction(te.enemy, other, TFSTATE_INFECTED); + LogEventAffliction(self, other, TFSTATE_INFECTED); + sprint(other, PRINT_MEDIUM, + "You have been infected by ", self.netname, + "\n"); + sprint(self, PRINT_MEDIUM, "You have infected ", + other.netname, "\n"); + } + } + } + } + } +}; + +void () player_light1 =[105, player_light2] { + muzzleflash(); + if (!self.button0 || intermission_running) { + player_run(); + return; + } + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) { + self.weaponframe = 1; + } + SuperDamageSound(); + W_FireLightning(); + Attack_Finished(0.2); +}; + +void () player_light2 =[106, player_light1] { + if (!self.button0 || intermission_running) { + player_run(); + return; + } + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) { + self.weaponframe = 1; + } + SuperDamageSound(); + W_FireLightning(); + Attack_Finished(0.2); +}; + +void (float num_bubbles) DeathBubbles; + +void () PainSound = { + local float rs; + + if (self.health < 0) { + return; + } + if (damage_attacker.classname == "teledeath") { + FO_Sound(self, CHAN_VOICE, "player/teledth1.wav", 1, 0); + return; + } + if ((self.watertype == -3) && (self.waterlevel == 3)) { + DeathBubbles(1); + if (random() > 0.5) { + FO_Sound(self, CHAN_VOICE, "player/drown1.wav", 1, 1); + } else { + FO_Sound(self, CHAN_VOICE, "player/drown2.wav", 1, 1); + } + return; + } + if (self.watertype == -4) { + if (random() > 0.5) { + FO_Sound(self, CHAN_VOICE, "player/lburn1.wav", 1, 1); + } else { + FO_Sound(self, CHAN_VOICE, "player/lburn2.wav", 1, 1); + } + return; + } + if (self.watertype == CONTENT_LAVA) { + if (random() > 0.5) { + FO_Sound(self, CHAN_VOICE, "player/lburn1.wav", 1, 1); + } else { + FO_Sound(self, CHAN_VOICE, "player/lburn2.wav", 1, 1); + } + return; + } + if (self.pain_finished > time) { + self.axhitme = 0; + return; + } + self.pain_finished = time + 0.5; + if (self.axhitme == 1) { + self.axhitme = 0; + FO_Sound(self, CHAN_VOICE, "player/axhit1.wav", 1, 1); + return; + } + rs = rint(((random() * 5) + 1)); + self.noise = ""; + if (rs == 1) { + self.noise = "player/pain1.wav"; + } else { + if (rs == 2) { + self.noise = "player/pain2.wav"; + } else { + if (rs == 3) { + self.noise = "player/pain3.wav"; + } else { + if (rs == 4) { + self.noise = "player/pain4.wav"; + } else { + if (rs == 5) { + self.noise = "player/pain5.wav"; + } else { + self.noise = "player/pain6.wav"; + } + } + } + } + } + FO_Sound(self, CHAN_VOICE, self.noise, 1, 1); + return; +}; + +void () player_pain1 =[35, player_pain2] { + PainSound(); + self.weaponframe = 0; +}; + +void () player_pain2 =[36, player_pain3] { +}; + +void () player_pain3 =[37, player_pain4] { +}; + +void () player_pain4 =[38, player_pain5] { +}; + +void () player_pain5 =[39, player_pain6] { +}; + +void () player_pain6 =[40, player_run] { +}; + +void () player_axpain1 =[29, player_axpain2] { + PainSound(); + self.weaponframe = 0; +}; + +void () player_axpain2 =[30, player_axpain3] { +}; + +void () player_axpain3 =[31, player_axpain4] { +}; + +void () player_axpain4 =[32, player_axpain5] { +}; + +void () player_axpain5 =[33, player_axpain6] { +}; + +void () player_axpain6 =[34, player_run] { +}; + +void (entity et, float f) player_pain = { + if (self.weaponframe) { + if (deathmsg == DMSG_TEAMKILL) + PainSound(); + return; + } + if (self.invisible_finished > time) + return; + + if (IsFeigned(self)) { + PainSound(); + return; + } + if (self.button0 && (FO_CurrentWeapon() == WEAP_INCENDIARY)) + return; + + if (FO_CurrentWeapon() <= WEAP_AXE) + player_axpain1(); + else + player_pain1(); +}; + +void () player_diea1; +void () player_dieb1; +void () player_diec1; +void () player_died1; +void () player_diee1; +void () player_die_ax1; + +void () DeathBubblesSpawn = { + if (self.owner.waterlevel != 3) + return; + + newmis = spawn(); + FO_SetModel(newmis, "progs/s_bubble.spr"); + setorigin(newmis, (self.owner.origin + '0 0 24')); + newmis.movetype = 8; + newmis.solid = 0; + newmis.velocity = '0 0 15'; + newmis.nextthink = time + 0.5; + newmis.think = bubble_bob; + newmis.classname = "bubble"; + newmis.frame = 0; + newmis.cnt = 0; + setsize(newmis, '-8 -8 -8', '8 8 8'); + self.nextthink = time + 0.1; + self.think = DeathBubblesSpawn; + self.air_finished = self.air_finished + 1; + if (self.air_finished >= self.bubble_count) + dremove(self); +}; + +void (float num_bubbles) DeathBubbles = { + local entity bubble_spawner; + + bubble_spawner = spawn(); + setorigin(bubble_spawner, self.origin); + bubble_spawner.movetype = MOVETYPE_NONE; + bubble_spawner.solid = SOLID_NOT; + bubble_spawner.nextthink = time + 0.1; + bubble_spawner.think = DeathBubblesSpawn; + bubble_spawner.air_finished = 0; + bubble_spawner.owner = self; + bubble_spawner.bubble_count = num_bubbles; + return; +}; + +void () DeathSound = { + local float rs; + + if (self.waterlevel == 3) { + if (IsFeigned(self)) + DeathBubbles(2); + else + DeathBubbles(10); + + FO_Sound(self, CHAN_VOICE, "player/h2odeath.wav", 1, 0); + return; + } + rs = rint(random() * 4 + 1); + if (rs == 1) + self.noise = "player/death1.wav"; + else if (rs == 2) + self.noise = "player/death2.wav"; + else if (rs == 3) + self.noise = "player/death3.wav"; + else if (rs == 4) + self.noise = "player/death4.wav"; + else + self.noise = "player/death5.wav"; + + FO_Sound(self, CHAN_VOICE, self.noise, 1, 0); + return; +}; + +void () PlayerDead = { + self.client_nextthink = -1; + self.nextthink = -1; + self.deadflag = DEAD_DEAD; +}; + +vector(float dm) VelocityForDamage = +{ + local vector v; + + v_x = 100 * crandom(); + v_y = 100 * crandom(); + v_z = 200 + 100 * random(); + if (dm > -50) + v = v * 0.7; + else if (dm > -200) + v = v * 2; + else + v = v * 10; + return (v); +}; + +void (string gibname, float dm) ThrowGib = { + newmis = spawn(); + newmis.origin = self.origin; + + FO_SetModel(newmis, gibname); + + setsize(newmis, '0 0 0', '0 0 0'); + newmis.velocity = VelocityForDamage(dm); + + newmis.movetype = MOVETYPE_BOUNCE; + newmis.solid = SOLID_NOT; + + newmis.avelocity_x = random() * 600; + newmis.avelocity_y = random() * 600; + newmis.avelocity_z = random() * 600; + + newmis.think = SUB_Remove; + newmis.ltime = time; + newmis.nextthink = time + 10 + random() * 10; + newmis.frame = 0; + newmis.flags = 0; +}; + +void (string gibname, float dm) ThrowHead = { + FO_SetModel(self, gibname); + self.skin = 0; + self.frame = 0; + self.nextthink = -1; + + self.movetype = MOVETYPE_BOUNCE; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + + self.view_ofs = '0 0 8'; + setsize(self, '-16 -16 0', '16 16 56'); + self.velocity = VelocityForDamage(dm); + self.origin_z = self.origin_z - 24; + self.flags = self.flags - (self.flags & 512); + self.avelocity = crandom() * '0 600 0'; +}; + +void (string gibname) HeadShotThrowHead = { + FO_SetModel(self, gibname); + self.frame = 0; + self.nextthink = -1; + + self.movetype = MOVETYPE_BOUNCE; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + + self.view_ofs = '0 0 8'; + setsize(self, '-16 -16 0', '16 16 56'); + self.velocity = normalize(self.head_shot_vector) * 600; + self.origin_z = self.origin_z + 24; + self.flags = self.flags - self.flags & 512; + self.avelocity = '0 0 0'; +}; + +void () KillPlayer = { + self.owner.deadflag = DEAD_DEAD; + dremove(self); +}; + +void () GibPlayer = { + ThrowHead("progs/h_player.mdl", self.health); + ThrowGib("progs/gib1.mdl", self.health); + ThrowGib("progs/gib2.mdl", self.health); + ThrowGib("progs/gib3.mdl", self.health); + if (deathmsg == DMSG_TRIGGER) { + newmis = spawn(); + newmis.owner = self; + newmis.think = KillPlayer; + newmis.nextthink = time + 1; + } else { + self.deadflag = DEAD_DEAD; + } + TeamFortress_SetupRespawn(0); + if (damage_attacker.classname == "teledeath") { + FO_Sound(self, CHAN_VOICE, "player/teledth1.wav", 1, 0); + self.respawn_time = (self.respawn_time + 2) + (random() * 2); + return; + } else if (damage_attacker.classname == "teledeath2") { + FO_Sound(self, CHAN_VOICE, "player/teledth1.wav", 1, 0); + self.respawn_time = (self.respawn_time + 2) + (random() * 2); + return; + } + if (random() < 0.5) { + FO_Sound(self, CHAN_VOICE, "player/gib.wav", 1, 0); + } else { + FO_Sound(self, CHAN_VOICE, "player/udeath.wav", 1, 0); + } + + FO_SetClientThink(think_nop, 0); +}; + +void () PlayerDie = { + UpdateClientPlayerDie(self); + + self.spawn_gen += 1; + self.last_death_ctime = self.client_time; + + local float i; + local entity te; + + self.items = self.items - (self.items & 524288); + self.invisible_finished = 0; + self.invincible_finished = 0; + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.modelindex = modelindex_player; + + if ((self.tfstate & 16) && (self == self.enemy)) { + te = find(world, classname, "timer"); + while (te) { + if ((te.owner == self) && (te.think == BioInfection_Decay)) { + logfrag(te.enemy, self); + TF_AddFrags(te.enemy, 1); + } + te = find(te, classname, "timer"); + } + } + TeamFortress_RemoveTimers(); + + if (deathmatch || coop) + DropBackpack(); + + self.tfstate |= TFSTATE_NO_WEAPON; + self.view_ofs = '0 0 -8'; + self.deadflag = DEAD_DYING; + self.solid = SOLID_NOT; + self.flags = self.flags - (self.flags & 512); + self.movetype = MOVETYPE_TOSS; + + if (self.velocity_z < 10) + self.velocity_z = self.velocity_z + random() * 300; + + if (self.health < -40) { + GibPlayer(); + return; + } + + DeathSound(); + self.angles_x = 0; + self.angles_z = 0; + if (FO_CurrentWeapon() <= WEAP_AXE) { + player_die_ax1(); + TeamFortress_SetupRespawn(0); + return; + } + i = 1 + floor(random() * 6); + if (i == 1) + player_diea1(); + else if (i == 2) + player_dieb1(); + else if (i == 3) + player_diec1(); + else if (i == 4) + player_died1(); + else + player_diee1(); + + TeamFortress_SetupRespawn(0); +}; + +void () set_suicide_frame = { + if (self.model != "progs/player.mdl") + return; + + setmodel(self, string_null); + setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); +}; + + +void player_die_extra() { + // Death animations are variable length, this always picks up last frame. + if (self.client_think == player_run) + PlayerDead(); +} + +anim_t anim_player_diea1 = { 11, 1, {50}, {0}, FALSE, FALSE }; +void player_dieaN() { client_anim_frames(player_dieaN, player_die_extra, &anim_player_diea1); } +void player_diea1() { *thinkindex() = 1; player_dieaN(); } + +anim_t anim_player_dieb1 = { 9, 1, {61}, {0}, FALSE, FALSE }; +void player_diebN() { client_anim_frames(player_diebN, player_die_extra, &anim_player_dieb1); } +void player_dieb1() { *thinkindex() = 1; player_diebN(); } + +anim_t anim_player_diec1 = { 15, 1, {70}, {0}, FALSE, FALSE }; +void player_diecN() { client_anim_frames(player_diecN, player_die_extra, &anim_player_diec1); } +void player_diec1() { *thinkindex() = 1; player_diecN(); } + +anim_t anim_player_died1 = { 9, 1, {85}, {0}, FALSE, FALSE }; +void player_diedN() { client_anim_frames(player_diedN, player_die_extra, &anim_player_died1); } +void player_died1() { *thinkindex() = 1; player_diedN(); } + +anim_t anim_player_diee1 = { 9, 1, {94}, {0}, FALSE, FALSE }; +void player_dieeN() { client_anim_frames(player_dieeN, player_die_extra, &anim_player_diee1); } +void player_diee1() { *thinkindex() = 1; player_dieeN(); } + +anim_t anim_player_die_ax = { 9, 1, {41}, {0}, FALSE, FALSE }; +void player_die_axN() { client_anim_frames(player_die_axN, player_die_extra, &anim_player_die_ax); } +void player_die_ax1() { *thinkindex() = 1; player_die_axN(); } + +void () Headless_Think = { + self.frame = self.frame + 1; + if ((self.frame == 7) || (self.frame == 18)) { + self.nextthink = time + 10 + random() * 10; + self.think = SUB_Remove; + return; + } + self.nextthink = time + 0.1; +}; diff --git a/ssqc/progs.src b/ssqc/progs.src new file mode 100644 index 000000000..f588d2f76 --- /dev/null +++ b/ssqc/progs.src @@ -0,0 +1,88 @@ +#pragma target fte_5768 +#pragma optimise 3 +#pragma optimise no-cf +#pragma flag enable subscope +#pragma flag enable iffloat +#pragma flag enable lo + +#pragma progs_dat "../qwprogs.dat" + +#define QWSSQC + +#includelist +../share/fteextensions.qc +../share/debug.qc +defs.qc +../share/commondefs.qc +../share/common_helpers.qc +qw.qc +debug.qc +time.qc +../share/physics.qc +../share/weapons.qc +../share/prediction.qc +../share/classes.qc +../share/animate.qc +../share/mcp_precache.qc +../share/engineer.qc +helpers.qc +events.qc +roles.qc +q3defs.qc +status.qc +teamplay.qc +functions.qc +menu.qc +csmenu.qc +../share/common_vote.qc +vote.qc +extraents.qc +help.qc +subs.qc +items.qc +locfiles.qc +rewind.qc +combat.qc +weapons.qc +world.qc +client.qc +player.qc +doors.qc +buttons.qc +triggers.qc +tforttm.qc +plats.qc +misc.qc +monsters.qc +flare.qc +sentry.qc +boss.qc +admin.qc +scout.qc +sniper.qc +tsoldier.qc +demoman.qc +medic.qc +hwguy.qc +pyro.qc +spy.qc +engineer.qc +camera.qc +clan.qc +quadmode.qc +tfort.qc +tforthlp.qc +tfortmap.qc +login.qc +commands.qc +ctf.qc +coop.qc +actions.qc +spect.qc +q3.qc +mvdsv.qc +rotate.qc +fo_logic.qc +fo_math.qc +fo_misc_info.qc +#endlist diff --git a/pyro.qc b/ssqc/pyro.qc similarity index 52% rename from pyro.qc rename to ssqc/pyro.qc index 7a046566c..e67e7b88e 100644 --- a/pyro.qc +++ b/ssqc/pyro.qc @@ -8,9 +8,18 @@ void (vector org, entity shooter) NapalmGrenadeLaunch; void () Napalm_touch; float (string id_flame) RemoveFlameFromQueue; +void (entity ent, float num) SetFlameCount = { + ent.numflames = num; + + if (num == 0) + ent.tfstate &= ~TFSTATE_BURNING; + else + ent.tfstate |= TFSTATE_BURNING; +}; + entity(string type, entity p_owner) FlameSpawn = { - if (cb_prematch_time > time) { + if (cb_prematch) { return (world); } if (type != "1") { @@ -28,30 +37,31 @@ entity(string type, entity p_owner) FlameSpawn = newmis.solid = SOLID_BBOX; newmis.effects = EF_DIMLIGHT; newmis.flame_id = "1"; - setmodel(newmis, "progs/flame2.mdl"); + FO_SetModel(newmis, "progs/flame2.mdl"); setsize(newmis, '0 0 0', '0 0 0'); } else if (type == "2") { newmis.movetype = MOVETYPE_BOUNCE; newmis.solid = SOLID_BBOX; newmis.flame_id = "2"; - setmodel(newmis, "progs/flame2.mdl"); + FO_SetModel(newmis, "progs/flame2.mdl"); newmis.frame = 1; setsize(newmis, '0 0 0', '0 0 0'); } else if (type == "3") { newmis.movetype = MOVETYPE_FLYMISSILE; newmis.solid = SOLID_BBOX; newmis.flame_id = "3"; - setmodel(newmis, "progs/flame2.mdl"); + FO_SetModel(newmis, "progs/flame2.mdl"); setsize(newmis, '0 0 0', '0 0 0'); } else if (type == "4") { newmis.movetype = MOVETYPE_FLYMISSILE; newmis.flame_id = "4"; newmis.frame = 1; newmis.solid = SOLID_BBOX; - setmodel(newmis, "progs/flame2.mdl"); + FO_SetModel(newmis, "progs/flame2.mdl"); setsize(newmis, '0 0 0', '0 0 0'); } newmis.owner = p_owner; + newmis.classname = "pyro_flame"; return (newmis); }; @@ -95,6 +105,8 @@ float (string id_flame) RemoveFlameFromQueue = { return (1); }; + + void () Remove = { FlameDestroy(self); }; @@ -102,7 +114,7 @@ void () Remove = { void () NapalmGrenadeFollow = { traceline(self.origin, self.origin, TRUE, self); if (trace_inwater == 1) { - sound(self, CHAN_VOICE, "misc/vapeur2.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "misc/vapeur2.wav", 1, ATTN_NORM); FlameDestroy(self); } if (self.velocity == '0 0 0') { @@ -112,88 +124,79 @@ void () NapalmGrenadeFollow = { }; void () NapalmGrenadeTouch = { - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); if (self.velocity == '0 0 0') self.avelocity = '0 0 0'; }; -void () NapalmGrenadeNetThink = { +void () NapalmGrenadeExplode2; + +// Explode1 is the initial "flare" before we start emitting dmg. +// Explode2 handles the subsequent explosions. +void () NapalmGrenadeExplode1 = { + local entity head; + + FO_Sound(self, CHAN_AUTO, "weapons/flmgrexp.wav", 1, ATTN_NORM); + traceline(self.origin, self.origin, 1, self); + if (trace_inwater != 1) + self.effects = self.effects | EF_DIMLIGHT; + self.think = NapalmGrenadeExplode2; + self.nextthink = time + 0.1; + self.heat = 0; +}; + +void () NapalmGrenadeExplode2 = { local entity head; local entity te; - if (self.heat == 0) { - self.owner.no_active_napalm_grens = - self.owner.no_active_napalm_grens + 1; - if (self.owner.no_active_napalm_grens > 2) { - te = find(world, classname, "grentimer"); - while (te) { - if ((te.owner == self.owner) && - (te.no_active_napalm_grens == 1)) { - te.weapon = DMSG_FLAME; - te.think = RemoveGrenade; - te.nextthink = time + 0.1; - } - te = find(te, classname, "grentimer"); - } - } - self.no_active_napalm_grens = self.owner.no_active_napalm_grens; - } + // TODO - make a settings struct and put these in on world spawn + float explodeDam = 0; + float maxExplosions = 0; + explodeDam = PC_PYRO_NAPALM_INIT_DAM; + maxExplosions = PC_PYRO_NAPALM_MAX_EXPLOSIONS; + self.nextthink = time + 1; - self.origin = self.enemy.origin; makevectors(self.v_angle); traceline(self.origin, self.origin, 1, self); - if (trace_inwater == 1) { - sound(self, CHAN_VOICE, "misc/vapeur2.wav", 1, ATTN_NORM); - RemoveGrenade(); - return; - } - head = findradius(self.origin, 180); - while (head) { - if (head.takedamage) { - deathmsg = DMSG_FLAME; - TF_T_Damage(head, self, self.owner, 20, TF_TD_NOTTEAM, - TF_TD_FIRE); - other = head; - Napalm_touch(); - if (other.classname == "player") { - stuffcmd(other, "bf\nbf\n"); + + local float ignited = self.effects & EF_DIMLIGHT; + if (trace_inwater == 1 && ignited) { + self.effects &= ~EF_DIMLIGHT; // Extinguish. + FO_Sound(self, CHAN_VOICE, "misc/vapeur2.wav", 1, ATTN_NORM); + } else if (ignited) { + head = findradius(self.origin, 180); + while (head) { + if (head.takedamage) { + deathmsg = DMSG_FLAME; + + float aflags = TF_TD_FIRE; + if (NewBalanceActive()) + aflags |= TF_TD_NOMOMENTUM; + + TF_T_Damage(head, self, self.owner, explodeDam, TF_TD_NOTTEAM, aflags); + other = head; + Napalm_touch(); + if (other.classname == "player") { + stuffcmd(other, "bf\nbf\n"); + } } + head = head.chain; } - head = head.chain; + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_EXPLOSION); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PHS); } - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); self.heat = self.heat + 1; - if (self.heat > 7) - RemoveGrenade(); -}; - -void () NapalmGrenadeExplode = { - local entity head; - sound(self, CHAN_AUTO, "weapons/flmgrexp.wav", 1, ATTN_NORM); - traceline(self.origin, self.origin, 1, self); - if (trace_inwater == 1) { + if (self.heat >= maxExplosions) dremove(self); - return; - } - self.effects = self.effects | EF_DIMLIGHT; - head = spawn(); - head.think = NapalmGrenadeNetThink; - head.classname = "grentimer"; - head.nextthink = time + 0.1; - head.heat = 0; - head.origin = self.origin; - head.owner = self.owner; - head.team_no = self.owner.team_no; - head.enemy = self; }; + void (vector org, entity shooter) NapalmGrenadeLaunch = { local float xdir; local float ydir; @@ -252,22 +255,21 @@ void () FlameFollow = { if (self.enemy.health < 1) { deathmsg = 15; T_RadiusDamage(self, self, 10, self); - self.enemy.numflames = 0; + SetFlameCount(self.enemy, 0); FlameDestroy(self); return; } - if ((self.enemy.armorclass & 16) && (self.enemy.armorvalue > 0)) { + if ((self.enemy.armorclass & AT_SAVEFIRE) && (self.enemy.armorvalue > 0)) { self.health = 0; } - if (self.enemy.tfstate & 131072) { + if (self.enemy.tfstate & TFSTATE_FLAMES_MAX) { self.health = 45; self.enemy.tfstate = - self.enemy.tfstate - (self.enemy.tfstate & 131072); + self.enemy.tfstate - (self.enemy.tfstate & TFSTATE_FLAMES_MAX); } if (self.health < 1) { if ((self.effects != 8) || (self.enemy.numflames <= 1)) { - self.enemy.numflames = self.enemy.numflames - 1; - self.enemy.numflames = 0; + SetFlameCount(self.enemy, (self.enemy.numflames - 1)); FlameDestroy(self); return; } @@ -281,7 +283,7 @@ void () FlameFollow = { setorigin(self, vtemp); if (self.model != "progs/flame2.mdl") { self.model = "progs/flame2.mdl"; - setmodel(self, self.model); + FO_SetModel(self, self.model); } } else { if (self.model == "progs/flame2.mdl") { @@ -290,17 +292,14 @@ void () FlameFollow = { } } if ((self.enemy.waterlevel > 1) || (self.enemy.classname != "player" && self.enemy.waterlevel > 0)) { - sound(self, 2, "misc/vapeur2.wav", 1, 1); - self.enemy.numflames = self.enemy.numflames - 1; + FO_Sound(self, CHAN_VOICE, "misc/vapeur2.wav", 1, 1); + SetFlameCount(self.enemy, self.enemy.numflames - 1); FlameDestroy(self); return; } self.nextthink = time + 0.1; if ((self.effects == 8) && (self.heat >= 3)) { - damage = self.enemy.numflames * 0.3 * 3; - if (damage < 1) { - damage = 1; - } + damage = min(self.enemy.numflames * PC_PYRO_BURN_MULTIPLIER * 3, 1); self.heat = 1; deathmsg = 15; TF_T_Damage(self.enemy, self, self.owner, damage, TF_TD_NOTTEAM, @@ -312,46 +311,65 @@ void () FlameFollow = { } }; -void () OnPlayerFlame_touch = { - local entity flame; - local vector vtemp; +void (entity player, entity fireowner, vector org + , float flamehealth) SetOnFire = { + entity flame; - if (((other != world) && (other.health > 0)) && (other != self.enemy)) { - if (cb_prematch_time > time) { + if (cb_prematch) + { + return; + } + + if (player.numflames >= 4) + { + player.tfstate = player.tfstate | TFSTATE_FLAMES_MAX; + return; + } + + if ((player.armorclass & AT_SAVEFIRE) && (player.armorvalue > 0)) + { + return; + } + + if (player.classname == "player") + { + if (((teamplay & 16) && (player.team_no > 0)) && + (player.team_no == fireowner.team_no)) { return; } - if (other.numflames >= 4) { - other.tfstate = other.tfstate | 131072; + CenterPrint(player, "You are on fire!\n"); + stuffcmd(player, "bf\n"); + } + + if (player.numflames < 1) + { + flame = FlameSpawn("1", player); + //FO_Sound(flame, CHAN_VOICE, "ambience/fire1.wav", 1, 1); // original tf code, but super annoying and it doesn't stop + } + else + { + flame = FlameSpawn("3", player); + if (flame == world) { return; } - if (other.classname == "player") { - if (((teamplay & 16) && (other.team_no > 0)) && - (other.team_no == self.owner.team_no)) { - return; - } - CenterPrint(other, "You are on fire!\n"); - stuffcmd(other, "bf\n"); - } - if (other.numflames < 1) { - flame = FlameSpawn("1", other); - sound(flame, 2, "ambience/fire1.wav", 1, 1); - } else { - flame = FlameSpawn("3", other); - if (flame == world) { - return; - } - } - flame.classname = "fire"; - flame.health = 45; - other.numflames = other.numflames + 1; - flame.velocity = other.velocity; - flame.enemy = other; - flame.touch = OnPlayerFlame_touch; - flame.owner = self.owner; - vtemp = self.origin; - setorigin(flame, vtemp); - flame.nextthink = time + 0.1; - flame.think = FlameFollow; + } + + flame.classname = "fire"; + flame.health = flamehealth; + SetFlameCount(player, (player.numflames + 1)); + flame.velocity = player.velocity; + flame.enemy = player; + flame.touch = OnPlayerFlame_touch; + flame.owner = fireowner; + setorigin(flame, org); + flame.nextthink = time + 0.1; + flame.think = FlameFollow; +}; + +void () OnPlayerFlame_touch = { + if (((other != world) && (other.health > 0)) && (other != self.enemy)) { + float flamehealth = 45; + SetOnFire(other, self.owner, self.origin, flamehealth); } }; @@ -364,9 +382,6 @@ void () QW_Flame_ResetTouch = { }; void () WorldFlame_touch = { - local entity flame; - local vector vtemp; - deathmsg = DMSG_FLAME; TF_T_Damage(other, self, self.enemy, 10, TF_TD_NOTTEAM, TF_TD_FIRE); self.touch = SUB_Null; @@ -375,48 +390,13 @@ void () WorldFlame_touch = { self.nextthink = time + 1; } if (((other != world) && (other.solid != 1)) && (other.health > 0)) { - if (cb_prematch_time > time) { - return; - } - if (other.numflames >= 4) { - other.tfstate = other.tfstate | 131072; - return; - } - if (other.classname == "player") { - if (((teamplay & 16) && (other.team_no > 0)) && - (other.team_no == self.owner.team_no)) { - return; - } - CenterPrint(other, "You are on fire!\n"); - stuffcmd(other, "bf\n"); - } - if (other.numflames < 1) { - flame = FlameSpawn("1", other); - sound(flame, 2, "ambience/fire1.wav", 1, 1); - } else { - flame = FlameSpawn("3", other); - if (flame == world) { - return; - } - } - flame.classname = "fire"; - flame.health = 0; - other.numflames = other.numflames + 1; - flame.velocity = other.velocity; - flame.enemy = other; - flame.touch = OnPlayerFlame_touch; - flame.owner = self.owner; - vtemp = self.origin + '0 0 10'; - setorigin(flame, vtemp); - flame.nextthink = time + 0.15; - flame.think = FlameFollow; + vector org = self.origin + '0 0 10'; + float flamehealth = 0; + SetOnFire(other, self.owner, org, flamehealth); } }; void () Flamer_stream_touch = { - local entity flame; - local vector vtemp; - if (other.classname == "fire") { return; } @@ -427,63 +407,29 @@ void () Flamer_stream_touch = { if (flame_knockback) { // knockback target if (other.classname != "building_sentrygun" && other.classname != "building_dispenser") { - other.velocity = other.velocity + self.velocity / 2; + other.velocity = other.velocity + self.velocity / 20; } } - TF_T_Damage(other, self, self.owner, 10, TF_TD_NOTTEAM, - TF_TD_FIRE); - if (cb_prematch_time > time) { - return; - } - if (other.numflames >= 4) { - other.tfstate = other.tfstate | 131072; - return; - } - if ((other.armorclass & 16) && (other.armorvalue > 0)) { - return; - } - if (other.classname == "player") { - if (((teamplay & 16) && (other.team_no > 0)) && - (other.team_no == self.owner.team_no)) { - return; - } - CenterPrint(other, "You are on fire!\n"); - stuffcmd(other, "bf\n"); - } - if (other.numflames < 1) { - flame = FlameSpawn("1", other); - sound(flame, CHAN_VOICE, "ambience/fire1.wav", 1, - ATTN_NORM); - } else { - flame = FlameSpawn("3", other); - if (flame == world) { - return; - } - } - flame.classname = "fire"; - flame.health = 45; - other.numflames = other.numflames + 1; - flame.velocity = other.velocity; - flame.enemy = other; - flame.touch = OnPlayerFlame_touch; - flame.owner = self.owner; - vtemp = self.origin; - setorigin(flame, vtemp); - flame.nextthink = time + 0.1; - flame.think = FlameFollow; + TF_T_Damage(other, self, self.owner, PC_PYRO_FLAMETHROWER_DAM, + TF_TD_NOTTEAM, TF_TD_FIRE); + + float flamehealth = 45; + SetOnFire(other, self.owner, self.origin, flamehealth); + dremove(self); } } else { if ((random() < 0.3) || - (pointcontents((self.origin + '0 0 1')) != -1)) { + (pointcontents((self.origin + '0 0 1')) != CONTENT_EMPTY)) { remove(self); return; } + entity flame; flame = FlameSpawn("4", other); if (flame != world) { flame.touch = WorldFlame_touch; flame.classname = "fire"; - vtemp = self.origin + '0 0 10'; + vector vtemp = self.origin + '0 0 10'; setorigin(flame, vtemp); flame.nextthink = time + 8; flame.heat = flame.nextthink; @@ -495,66 +441,31 @@ void () Flamer_stream_touch = { }; void () Napalm_touch = { - local entity flame; - local vector vtemp; - if (other.classname == "fire") { return; } if (other != world) { if ((other.takedamage == 2) && (other.health > 0)) { + float dam = PC_PYRO_INIT_BURN_DAM; + deathmsg = DMSG_FLAME; - TF_T_Damage(other, self, self.owner, 6, TF_TD_NOTTEAM, + TF_T_Damage(other, self, self.owner, dam, TF_TD_NOTTEAM, TF_TD_FIRE); - if (cb_prematch_time > time) { - return; - } - if (other.numflames >= 4) { - other.tfstate = other.tfstate | 131072; - return; - } - if ((other.armorclass & 16) && (other.armorvalue > 0)) { - return; - } - if (other.classname == "player") { - if (((teamplay & 16) && (other.team_no > 0)) && - (other.team_no == self.owner.team_no)) { - return; - } - sound(other, CHAN_VOICE, "ambience/fire1.wav", 1, ATTN_NORM); - CenterPrint(other, "You are on fire!\n"); - stuffcmd(other, "bf\n"); - } - if (other.numflames < 1) { - flame = FlameSpawn("1", other); - } else { - flame = FlameSpawn("3", other); - if (flame == world) { - return; - } - } - flame.classname = "fire"; - flame.health = 45; - other.numflames = other.numflames + 1; - flame.velocity = other.velocity; - flame.enemy = other; - flame.touch = OnPlayerFlame_touch; - flame.owner = self.owner; - vtemp = self.origin; - setorigin(flame, vtemp); - flame.nextthink = time + 0.1; - flame.think = FlameFollow; + + float flamehealth = 45; + SetOnFire(other, self.owner, self.origin, flamehealth); } } else { if (pointcontents((self.origin + '0 0 1')) != -1) { FlameDestroy(self); return; } + entity flame; flame = FlameSpawn("4", other); if (flame != world) { flame.touch = WorldFlame_touch; flame.classname = "fire"; - vtemp = self.origin + '0 0 10'; + vector vtemp = self.origin + '0 0 10'; setorigin(flame, vtemp); flame.nextthink = time + 20; flame.heat = flame.nextthink; @@ -584,7 +495,6 @@ void (float num_bubbles, vector bub_origin) NewBubbles = { }; void () W_FireFlame = { - local entity flame; local float rn; if (self.waterlevel > 2) { @@ -592,49 +502,32 @@ void () W_FireFlame = { NewBubbles(2, self.origin + v_forward * 64); rn = random(); if (rn < 0.5) { - sound(self, CHAN_WEAPON, "misc/water1.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "misc/water1.wav", 1, ATTN_NORM); } else { - sound(self, CHAN_WEAPON, "misc/water2.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "misc/water2.wav", 1, ATTN_NORM); } return; } self.ammo_cells = self.ammo_cells - 1; - self.currentammo = self.ammo_cells; - sound(self, CHAN_AUTO, "weapons/flmfire2.wav", 1, ATTN_NORM); - flame = spawn(); + entity flame = FOProj_Create(FPP_FLAMETHROWER); flame.owner = self; flame.movetype = MOVETYPE_FLYMISSILE; flame.solid = SOLID_BBOX; flame.classname = "flamerflame"; makevectors(self.v_angle); - flame.velocity = aim(self, 10000); - flame.velocity = flame.velocity * 600; + flame.velocity = aim(self, 10000) * FPP_Get(FPP_FLAMETHROWER)->speed; + flame.nextthink = time + 0.15; + flame.touch = Flamer_stream_touch; flame.think = s_explode1; - flame.nextthink = time + 0.15; - setmodel(flame, "progs/s_explod.spr"); - setsize(flame, '0 0 0', '0 0 0'); + setorigin(flame, self.origin + v_forward * 16 + '0 0 16'); -}; -void () T_IncendiaryTouch = { - local float damg; - local entity head; + FOProj_Finalize(flame); +}; - if (other == self.owner) { - return; - } - if (pointcontents(self.origin) == CONTENT_SKY) { - remove(self); - return; - } - self.effects = self.effects | 4; - damg = 30 + random() * 20; - if (other.health) { - deathmsg = DMSG_FLAME; - TF_T_Damage(other, self, self.owner, damg, TF_TD_NOTTEAM, - TF_TD_FIRE); - } +void () IncendiaryRadius = { + entity head; head = findradius(self.origin, 180); while (head) { if (head.takedamage) { @@ -661,54 +554,156 @@ void () T_IncendiaryTouch = { WriteCoord(MSG_MULTICAST, self.origin_y); WriteCoord(MSG_MULTICAST, self.origin_z); multicast(self.origin, MULTICAST_PHS); - dremove(self); + dremove_sent(self); }; -void () W_FireIncendiaryCannon = { - if (self.ammo_rockets < 3) { +void () T_IncendiaryTouch = { + local float damg; + + if (other == self.owner) { return; } - self.ammo_rockets = self.ammo_rockets - 3; - self.currentammo = self.ammo_rockets; - sound(self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); - KickPlayer(-3, self); - newmis = spawn(); - newmis.owner = self; - newmis.movetype = MOVETYPE_FLYMISSILE; - newmis.solid = SOLID_BBOX; - makevectors(self.v_angle); - newmis.velocity = aim(self, 1000); - newmis.velocity = newmis.velocity * 600; - newmis.angles = vectoangles(newmis.velocity); - newmis.touch = T_IncendiaryTouch; - newmis.nextthink = time + 5; - newmis.think = SUB_Remove; - newmis.weapon = DMSG_INCENDIARY; - setmodel(newmis, "progs/missile.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin + v_forward * 8 + '0 0 16'); -}; -void () TeamFortress_IncendiaryCannon = { - if (!(self.weapons_carried & WEAP_INCENDIARY)) { + if (self.voided) return; + self.voided = 1; + + self.effects |= EF_BRIGHTLIGHT; + damg = 42 + random() * 20; + + if (other.health) { + if (CF_GetSetting("pdf", "pyro_direct_fix", "on") && + other.solid != SOLID_BSP) + SetOnFire(other, self.owner, self.origin, 30); + deathmsg = DMSG_FLAME; + TF_T_Damage(other, self, self.owner, damg, TF_TD_NOTTEAM, + TF_TD_FIRE); } + + damg = 92; + deathmsg = 15; + + entity ignore_self = AntilagKnock(self, damg) ? self.owner : __NULL__; + T_RadiusDamage(self, self.owner, damg, other, ignore_self); + + self.origin = (self.origin - (8 * normalize (self.velocity))); + WriteByte(4, 23); + WriteByte(4, 3); + WriteCoord(4, self.origin_x); + WriteCoord(4, self.origin_y); + WriteCoord(4, self.origin_z); + multicast(self.origin, 1); + dremove_sent(self); +}; + +void W_FireIncendiaryCannon(vector org, vector v_ang, float use_ctime=0) { if (self.ammo_rockets < 3) { - sprint(self, PRINT_HIGH, "Not enough ammo\n"); return; } - self.current_weapon = WEAP_INCENDIARY; - W_SetCurrentAmmo(self); + KickPlayer(-3, self); + entity proj = FOProj_Create(FPP_INCENDIARY); + proj.owner = self; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.solid = SOLID_BBOX; + + makevectors(v_ang); + proj.velocity = aim(self, 1000) * FPP_Get(FPP_INCENDIARY)->speed; + proj.angles = vectoangles(proj.velocity); + proj.touch = T_IncendiaryTouch; + proj.think = SUB_Remove; + proj.nextthink = time + 5; + proj.weapon = DMSG_INCENDIARY; + proj.classname = "pyro_rocket"; + setorigin(proj, org + v_forward * 8 + '0 0 16'); + + FOProj_Finalize(proj, use_ctime); +}; + +void () AirBlastReloadFinished = { + self.owner.airblast_cooldown = 0; + sprint(self.owner, PRINT_HIGH, "Airblast ready\n"); + stuffcmd(self.owner, "play weapons/airblast.wav\n"); + dremove(self); }; -void () TeamFortress_FlameThrower = { - if (!(self.weapons_carried & WEAP_FLAMETHROWER)) { +void FO_Airblast() = { + if(no_fire_mode) { + sprint(self, PRINT_MEDIUM, "You cannot blast air right now\n"); return; } - if (self.ammo_cells < 1) { - sprint(self, PRINT_HIGH, "Not enough ammo\n"); + + if (time < self.special_next) return; + + self.airblast_cooldown = 1; + self.special_next = time + PC_PYRO_AIRBLAST_COOLDOWN; + entity timer = spawn(); + timer.owner = self; + timer.classname = "timer"; + timer.nextthink = time + PC_PYRO_AIRBLAST_COOLDOWN; + timer.think = AirBlastReloadFinished; + + pointparticles(particleeffectnum("te_teleportsplash"), + self.origin + v_forward * PC_PYRO_AIRBLAST_RANGE / 4, v_forward); + FO_Sound(self, CHAN_WEAPON, "weapons/airblastshoot.wav", 1, ATTN_NORM); + makevectors(self.v_angle); + + int count; + entity* list = findradius_list(self.origin, PC_PYRO_AIRBLAST_RANGE, count); + + for (int i = 0; i < count; i++) { + entity ent = list[i]; + + if (ent.movetype == MOVETYPE_NONE || ent == self || !infront(ent)) + continue; + + switch (ent.classname) { + case "player": + case "pyro_rocket": + case "flamerflame": + case "proj_tranq": + case "proj_bullet": + case "proj_rocket": + case "grenade": + case "spike": + case "pipebomb": + break; + + default: + continue; // Next in list. + } + + vector targorg; + targorg = ent.origin; + float pushval = 1000; + if (ent == self) { + pushval = 500; + traceline(self.origin, self.origin + v_forward * 200, MOVE_NOMONSTERS, self); + if (trace_fraction < 1) + targorg = self.origin - (v_forward * 200 * trace_fraction); + } + + vector delta = targorg - self.origin; + float dist = vlen(delta); + float percent = (PC_PYRO_AIRBLAST_RANGE - dist) / PC_PYRO_AIRBLAST_RANGE; + + if (ent.flags & FL_ONGROUND) { + if (ent.classname != "pipebomb" && ent != self) // leave them on ground, we don't want them blasted + setorigin(ent, ent.origin + '0 0 1'); + } + + delta = normalize(delta); + delta = delta * percent * pushval; + + if (ent.movetype == MOVETYPE_FLYMISSILE) { + float speed = vlen(ent.velocity); + ent.velocity = normalize(delta) * speed; + } else { + ent.velocity = ent.velocity + delta; + } + + if (ent.classname != "player") + ent.SendFlags |= FOPP_POS; } - self.current_weapon = WEAP_FLAMETHROWER; - W_SetCurrentAmmo(self); + Status_Refresh(self); }; diff --git a/ssqc/q3.qc b/ssqc/q3.qc new file mode 100644 index 000000000..13054704d --- /dev/null +++ b/ssqc/q3.qc @@ -0,0 +1,275 @@ +string prepq3fstring (string s) +{ + string ret; + + ret = strireplace("%n", "", s); + ret = strireplace("~", "", ret); + ret = strireplace("^*", "", ret); + + ret = strireplace("^4blue", Q"\sblue\s", ret); + ret = strireplace("^1red", Q"\sred\s", ret); + + ret = strcat(ret, "\n"); + return (ret); +} + +float GetQ3State(string st) +{ + float stf = 0; + st = strtrim(prepq3fstring(st)); + + switch (st) + { + case "disabled": + stf = STATE_DISABLED; + break; + case "inactive": + stf = STATE_INACTIVE; + break; + case "active": + stf = STATE_ACTIVE; + break; + case "invisible": + stf = STATE_INVISIBLE; + break; + } + return stf; +} + +void ActivateQ3Goal(entity trig, float active) +{ + string msg = ""; + string targets = ""; + if (active) + { + msg = prepq3fstring(trig.active_all_message); + targets = trig.activetarget; + } + else + { + //msg = prepq3fstring(trig.active_all_message); + targets = trig.failtarget; + } + if (msg != "") + bprint(PRINT_HIGH, msg, "\n"); + + float count = tokenizebyseparator(targets, ","); + for (float i = 0; i <= count; i++) + { + string s = argv(i); + float count2 = tokenizebyseparator(s, "="); + if (count2 >= 2) + { + string gname = argv(0); + float chkstate = GetQ3State(argv(1)); + entity targ = find(world, groupname, gname); + while (targ) + { + if (targ.state == STATE_INVISIBLE && targ.state != chkstate) + { + FO_SetModel(targ, targ.mdl); + } + targ.state = chkstate; + msg = ""; + switch(chkstate) + { + case STATE_INVISIBLE: + targ.mdl = targ.model; + setmodel(targ, ""); + break; + case STATE_DISABLED: + msg = prepq3fstring(targ.disabled_all_message); + break; + } + if (msg != "") + { + bprint(PRINT_HIGH, msg, "\n"); + } + targ = find(targ, groupname, gname); + } + } + tokenizebyseparator(targets, ","); + } +} + +void CheckStateQ3Goal(entity trig) +{ + float count = tokenizebyseparator(trig.checkstate, ","); + float triggered = TRUE; + for (float i = 0; i <= count; i++) + { + string s = argv(i); + float count2 = tokenizebyseparator(s, "="); + if (count2 >= 2) + { + string gname = argv(0); + float chkstate = GetQ3State(argv(1)); + + entity targ = find(world, groupname, gname); + if (!targ) + continue; + + if (targ.state != chkstate) + { + triggered = FALSE; + } + } + } + + ActivateQ3Goal(trig, triggered); +} + +void () target_location = { +} + +// backpacks, cap points etc +void () func_goalinfo = { + self.classname = "info_tfgoal"; + self.frags = self.teamscore; + self.count = self.teamscore; + self.mdl = self.model; + self.noise = self.active_all_sound; + self.armortype = 0.8; + self.goal_effects = 1; + self.goal_activation = 1; // touched by player + self.n_b = prepq3fstring(self.active_all_message); + + // probably have to tokenise this? + if (self.allowteams == "blue") + { + self.team_no = 1; + } + if (self.allowteams == "red") + { + self.team_no = 2; + } + + // note: need to see if this is groupname or builtin name in q3f + if (self.holding == "blueflag") + { + self.items_allowed = 1; + self.owned_by = 1; + self.axhitme = 1; + } + if (self.holding == "redflag") + { + self.items_allowed = 2; + self.owned_by = 2; + self.axhitme = 2; + } + + float count = tokenizebyseparator(self.give, ","); + + for (float i = 0; i <= count; i++) + { + string s, gtype, gop, gamt; + float loc, slen, val; + + s = argv(i); + slen = strlen(s); + loc = strstrofs(s, "="); + + gtype = substring(s, 0, loc); + if (gtype == "") + continue; + gop = substring(s, loc, loc + 1); + gamt = substring(s, loc + 1, slen); + if (gop == "-") { + val = stof(strcat("-", gamt)); + } else { + val = stof(gamt); + } + + switch (gtype) + { + case "health": + self.health = val; + break; + case "armor": + self.armorvalue = val; + break; + case "ammo_shells": + self.ammo_shells = val; + break; + case "ammo_nails": + self.ammo_nails = val; + break; + case "ammo_rockets": + self.ammo_rockets = val; + break; + case "ammo_cells": + self.ammo_cells = val; + break; + case "gren1": + self.no_grenades_1 = val; + break; + case "gren2": + self.no_grenades_2 = val; + break; + case "ammo_medikit": + self.ammo_medikit = val; + break; + default: + dprint("gtype not found, discarding\n"); + dprint("gtype: ", gtype, "\n"); + dprint("gop: ", gop, "\n"); + dprint("gamt: ", gamt, "\n"); + } + } + + info_tfgoal(); +} + +// flags +void () func_goalitem = { + self.classname = "item_tfgoal"; + self.mdl = self.model; + self.delay = self.wait; // is delay used anymore? + self.pausetime = self.wait; + self.netname = self.groupname; + self.g_a = 4789; + switch(self.allowteams) + { + case "blue": + self.team_no = 1; + self.owned_by = 2; + self.skin = 1; + self.goal_no = 2; + self.items = 262144; // used by hud? + break; + case "red": + self.team_no = 2; + self.owned_by = 1; + self.skin = 2; + self.goal_no = 1; + self.items = 131072; // used by hud? + break; + default: + } + string cam, iam; + cam = prepq3fstring(self.carried_all_message); + iam = prepq3fstring(self.inactive_all_message); + + self.team_broadcast = cam; + self.non_team_broadcast = cam; + self.message = prepq3fstring(self.carried_message); + self.noise = self.carried_all_sound; + self.noise3 = iam; + self.noise4 = iam; + self.n_b = cam; + self.d_n_n = prepq3fstring(self.active_all_message); + + item_tfgoal(); +} + +void () target_kill = { + self.classname = "info_tfgoal"; + self.health = -500; + self.deathtype = " died\n"; + self.goal_effects = 1; + + info_tfgoal(); +} + +void () target_position = { +}; \ No newline at end of file diff --git a/ssqc/q3defs.qc b/ssqc/q3defs.qc new file mode 100644 index 000000000..8c24f12dd --- /dev/null +++ b/ssqc/q3defs.qc @@ -0,0 +1,22 @@ +// q3 defs - enable q3f map ents +.string allowteams; +.string give; +.string active_all_sound; +.string model; +.float teamscore; +.string active_all_message; +.string holding; +.string groupname; +.string carried_all_message; +.string carried_message; +.string carried_all_sound; +.string inactive_all_message; +.string activetarget; +.string checkstate; +.float active; +.string initialstate; +.string flagsq3; // .float flags already exists and q3 uses it as a string +.string failtarget; +.string disabled_all_message; + +string __fullspawndata; // due to flags/flagsq3 \ No newline at end of file diff --git a/ssqc/quadmode.qc b/ssqc/quadmode.qc new file mode 100644 index 000000000..d222137a7 --- /dev/null +++ b/ssqc/quadmode.qc @@ -0,0 +1,844 @@ +void () info_player_teamspawn; + +void Activate2v2_Ruleset(); + +void () PostFOQuadStarted = { + if (!fo_login_required) + return; + + if !(TeamFortress_TeamGetNoPlayersExcludingAllTime(1) == TeamFortress_TeamGetNoPlayersExcludingAllTime(2)) { + bprint(PRINT_HIGH, "Uneven teams, match can't be recorded\n"); + fo_login_required = FALSE; + return; + } + + local string data = ""; + data = strcat(data, "{\n"); + data = strcat(data, " \"match\": {\n"); + + local float rated = CF_GetSetting("mra", "fo_matchrated", "2"); + if (rated == 2) { + if(TeamFortress_TeamGetNoPlayersExcludingAllTime(1) > 2) { + rated = 1; + } else { + rated = 0; + } + } + data = strcat(data, " \"rated\": \"", ftos(rated), "\",\n"); + + data = strcat(data, " \"round\": \"", ftos(rounds), "\",\n"); + data = strcat(data, " \"map\": \"", mapname, "\",\n"); + + local string game_filename_extension; + if (cvar("sv_demoAutoCompress")) { + game_filename_extension = ".mvd.gz"; + } else { + game_filename_extension = ".mvd"; + } + + local string demo_uri = strcat(demo_files_address, game_filename, game_filename_extension); + data = strcat(data, " \"demo_uri\": \"", demo_uri, "\",\n"); + + local string stats_uri = strcat(stats_files_address, game_filename, ".html"); + data = strcat(data, " \"stats_uri\": \"", stats_uri, "\",\n"); + + data = strcat(data, " \"server\": {\n"); + data = strcat(data, " \"name\": \"", infokey(world, "hostname"), "\",\n"); + data = strcat(data, " \"address\": \"", cvar_string("sv_serverip"), "\"\n"); + data = strcat(data, " },\n"); + + data = strcat(data, " \"discord_channel\": {\n"); + data = strcat(data, " \"channel_id\": \"", discord_channel_id, "\"\n"); + data = strcat(data, " },\n"); + + data = strcat(data, " \"teams\": {\n"); + + local float tn; + local float prefix_comma; + local entity p; + + for (tn = 1; tn <= number_of_teams; ++tn) { + data = strcat(data, " \"", ftos(tn), "\": {\n"); + data = strcat(data, " \"players\": [\n"); + + prefix_comma = FALSE; + + p = find(world, classname, "player"); + while (p != world) { + if(p.team_no == tn && p.all_time == ALL_TIME_COLOUR) { + if (prefix_comma) + data = strcat(data, ","); + + data = strcat(data, " {\n"); + data = strcat(data, " \"id\": \"", p.fo_login, "\",\n"); + data = strcat(data, " \"playerclass\": \"", ftos(p.playerclass), "\"\n"); + data = strcat(data, " }\n"); + prefix_comma = TRUE; + } + + p = find(p, classname, "player"); + } + + data = strcat(data, " ]\n"); + data = strcat(data, " }"); + + if (tn < number_of_teams) + data = strcat(data, ","); + + data = strcat(data, " \n"); + } + + data = strcat(data, " }\n"); + data = strcat(data, " }\n"); + data = strcat(data, "}\n"); + + local string uri = sprintf("%s/results/api/v1/matches", backend_address); + dprint("POST to ", uri, "\n"); + dprint(data); + uri_post(uri, FO_QUAD_STARTED_REQUEST, "application/json", data); +}; + +void () PostFOQuadFinalRoundStarted = { + if (!fo_login_required) + return; + + local string data = ""; + + data = strcat(data, "{\n"); + data = strcat(data, " \"match\": {\n"); + data = strcat(data, " \"id\": \"", match_id, "\",\n"); + data = strcat(data, " \"round\": \"", ftos(rounds), "\",\n"); + data = strcat(data, " \"teams\": {\n"); + + local float tn; + local float prefix_comma; + local entity p; + + for (tn = 1; tn <= number_of_teams; ++tn) { + local float score = TeamFortress_TeamGetScore(tn); + + data = strcat(data, " \"", ftos(tn), "\": {\n"); + data = strcat(data, " \"score\": \"", ftos(score), "\","); + data = strcat(data, " \"players\": [\n"); + + prefix_comma = FALSE; + + p = find(world, classname, "player"); + while (p != world) { + if(p.team_no == tn && p.all_time == ALL_TIME_COLOUR) { + if (prefix_comma) + data = strcat(data, ","); + + data = strcat(data, " {\n"); + data = strcat(data, " \"id\": \"", p.fo_login, "\",\n"); + data = strcat(data, " \"playerclass\": \"", ftos(p.playerclass), "\"\n"); + data = strcat(data, " }\n"); + prefix_comma = TRUE; + } + + p = find(p, classname, "player"); + } + + data = strcat(data, " ]\n"); + data = strcat(data, " }"); + + if (tn < number_of_teams) + data = strcat(data, ","); + + data = strcat(data, " \n"); + } + + data = strcat(data, " }\n"); + data = strcat(data, " }\n"); + data = strcat(data, "}\n"); + + local string uri = sprintf("%s/results/api/v1/matches/%s", backend_address, match_id); + dprint("POST to ", uri, "\n"); + dprint(data); + uri_post(uri, FO_QUAD_STARTED_REQUEST, "application/json", data); +}; + +void (float winner) PostFOQuadFinished = { + if (!fo_login_required) + return; + + if (!match_id) + return; + + local string data = ""; + + local entity t = find(world, classname, "round"); + local float timeleft = 0; + if (t != world) { + timeleft = t.cnt * 60 + t.cnt2; + } + + data = strcat(data, "{\n"); + data = strcat(data, " \"match\": {\n"); + data = strcat(data, " \"id\": \"", match_id, "\",\n"); + data = strcat(data, " \"winner\": \"", ftos(winner), "\",\n"); + data = strcat(data, " \"timeleft\": \"", ftos(timeleft), "\",\n"); + data = strcat(data, " \"teams\": {\n"); + + local float tn; + local float prefix_comma; + local entity p; + + for (tn = 1; tn <= number_of_teams; ++tn) { + local float score = TeamFortress_TeamGetScore(tn); + + data = strcat(data, " \"", ftos(tn), "\": {\n"); + data = strcat(data, " \"score\": \"", ftos(score), "\""); + data = strcat(data, " }"); + + if (tn < number_of_teams) + data = strcat(data, ","); + } + + data = strcat(data, " }\n"); + data = strcat(data, " }\n"); + data = strcat(data, "}\n"); + + local string uri = sprintf("%s/results/api/v1/matches/%s", backend_address, match_id); + dprint("POST to ", uri, "\n"); + dprint(data); + uri_post(uri, FO_QUAD_FINISHED_REQUEST, "application/json", data); +}; + +float () CheckWinningTeam = { + local float win_score = 0; + local float winning_team = 0; + + if (team1score > win_score) { + win_score = team1score; + winning_team = 1; + } + + if (team2score > win_score) { + win_score = team2score; + winning_team = 2; + } + else if (team2score == win_score) { + winning_team = 0; + } + + if (team3score > win_score) { + win_score = team3score; + winning_team = 3; + } + else if (team3score == win_score) { + winning_team = 0; + } + + if (team4score > win_score) { + win_score = team4score; + winning_team = 4; + } + else if (team4score == win_score) { + winning_team = 0; + } + return winning_team; +}; + +void (entity te) Quad_HideFlags = { + if(rounds % 2) { + if(te.team_no == 2 && te.owned_by == 1) { + Quad_UnHideFlag(te); + } else if(te.team_no == 1 && te.owned_by == 2) { + Quad_HideFlag(te); + } + } else { + if(te.team_no == 2 && te.owned_by == 1) { + Quad_HideFlag(te); + } else if(te.team_no == 1 && te.owned_by == 2) { + Quad_UnHideFlag(te); + } + } +}; + +void () FO_RoundChange = { + entity e = find(world, targetname, "fo_roundchange"); + entity oldself = self; + while (e) + { + // activate + self = e; + entity c; + // initial round, hide the restore items + if (rounds == CF_GetSetting("rounds","rounds","on")) + { + if (e.restore_group_no) + { + c = findfloat(world, group_no, e.restore_group_no); + while (c) + { + switch (c.classname) + { + case "item_tfgoal": + Quad_HideFlag(c); + break; + } + c = findfloat(c, group_no, e.restore_group_no); + } + } + } + else + { + self.use(); + //SUB_UseTargets(); + + // TF state of removed seems to be broken and ignored all + // throughout the codebase, so we will do the same + if (e.remove_group_no) + { + c = findfloat(world, group_no, e.remove_group_no); + while (c) + { + switch (c.classname) + { + case "item_tfgoal": + Quad_HideFlag(c); + break; + } + c = findfloat(c, group_no, e.remove_group_no); + } + } + + if (e.restore_group_no) + { + c = findfloat(world, group_no, e.restore_group_no); + while (c) + { + switch (c.classname) + { + case "item_tfgoal": + Quad_UnHideFlag(c); + break; + } + c = findfloat(c, group_no, e.restore_group_no); + } + } + + if (e.switch_team_group_no) + { + c = findfloat(world, group_no, e.switch_team_group_no); + while (c) + { + // this could be abused..? + if (c.group_no == e.switch_team_group_no) + { + switch (c.team_no) + { + // need another field to denote swaps + case TEAM_BLUE: + c.team_no = TEAM_RED; + break; + case TEAM_RED: + c.team_no = TEAM_BLUE; + break; + case TEAM_YELL: + c.team_no = TEAM_GREN; + break; + case TEAM_GREN: + c.team_no = TEAM_YELL; + break; + } + + switch (c.classname) + { + case "func_wall": + self = c; + func_wall(); + break; + case "info_player_teamspawn": + self = c; + info_player_teamspawn(); + break; + case "plat": + if (c.enemy) + c.enemy.team_no = c.team_no; + break; + + } + } + c = findfloat(c, group_no, e.switch_team_group_no); + } + } + } + + e = find(e, targetname, "fo_roundchange"); + } + self = oldself; +}; + +void () QuadRoundOver = { + round_over = 2; + + self.think = StartQuadRound; + self.nextthink = (time + 0.5); +}; + +void () QuadRoundThink = { + local string tmp; + local float fl; + + if (rounds < 2) { + if (CheckWinningTeam() != 0 && !play_to_completion) { + if (quad_winner != CheckWinningTeam()) { + quad_winner = CheckWinningTeam(); + self.think = QuadRoundOver; + self.nextthink = (time + 0.1); + return; + } + } + } + + if (self.cnt == -1) + return; + + if ( self.cnt == stof(infokey(world, "round_time")) - 1 && self.cnt2 == 59 ) { + localcmd("serverinfo status \""); + tmp = ftos (self.cnt + 1); + localcmd(tmp); + localcmd(" min left\"\n"); + } + + if (self.cnt2 == 1) { + localcmd("serverinfo status \""); + tmp = ftos (self.cnt); + localcmd(tmp); + localcmd(" min left\"\n"); + } + + if (self.cnt2 == -1) { + self.cnt2 = 59; + self.cnt = (self.cnt - 1); + } + + if (!cease_fire) + self.cnt2 = (self.cnt2 - 1); + + if (!self.cnt2) { + if ((self.cnt == 1) || (self.cnt == 5)) { + tmp = ftos (self.cnt); + bprint(2, "\s[\s", tmp, "\s]\s minute"); + if (self.cnt != 1) + bprint(2, "s"); + bprint(2, " remaining\n"); + } + if (!self.cnt) { + if (rounds > 1) + { + local string st; + st = ftos(round_delay_time); + round_end_time = time + round_delay_time + 1; + bprint(2, "ROUND TIME OVER\nNext round begins in ", st, " seconds\n"); + } + + self.think = QuadRoundOver; + self.nextthink = (time + 0.1); + return; + } + } + if (!self.cnt && (((self.cnt2 == 30) || (self.cnt2 == 15)) || (self.cnt2 <= 10))) { + fl = ceil (self.cnt2); + if (!(fl - self.cnt2)) { + tmp = ftos(self.cnt2); + bprint(2, "\s[\s", tmp, "\s]\s second"); + if ((self.cnt2 != 1)) + bprint(2, "s"); + bprint(2, " remaining\n"); + } + } + gametime++; + self.nextthink = (time + 1); +}; + +void () QuadRoundBegin = { + local entity te; + local entity oldself; + //local entity tfdet; + local float counter; + + local float round_time = stof(infokey(world, "round_time")); + localcmd(strcat(strcat("timelimit ", ftos(round_time)), "\n")); + + te = find(world, classname, "func_breakable"); + while (te) { + setmodel(te, te.mdl); + te.solid = SOLID_BSP; + te = find(te, classname, "func_breakable"); + } + cb_prematch = 0; + round_active = 1; + round_over = 0; + + if(quad_roles) { + team_no_attack = 1 + (rounds % 2); + //tfdet = find(world, classname, "info_tfdetect"); + if(rounds % 2) { + Team1_Role = &Role_Defence; + Team2_Role = &Role_Attack; + //te = Finditem(tfdet.display_item_status1); + //if(te && te.owned_by == 1) { + //Quad_HideFlag(te); + //} + //te = Finditem(tfdet.display_item_status2); + } else { + Team1_Role = &Role_Attack; + Team2_Role = &Role_Defence; + } + te = find(world, classname, "item_tfgoal"); + while (te) { + Quad_HideFlags(te); + te = find(te, classname, "item_tfgoal"); + } + } + + FO_RoundChange(); + + te = find(world, classname, "player"); + while (te != world) { + oldself = self; + self = te; + ResetAndRespawnPlayer(self); + self = oldself; + te = find (te, classname, "player"); + } + // lightstyle (0, "m"); + te = find(world, classname, "observer"); + while (te) { + if(infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(te, FALSE); + } + te = find(te, classname, "observer"); + } + + bprint(2, "QUAD ROUND BEGINS NOW\n"); + + if (rounds == 1) + PostFOQuadFinalRoundStarted(); + + round_start_time = time; + round_end_time = round_start_time + round_time * 60 + 1; + UpdateClientQuadRoundBegin(te, round_time); + + if (!self.cnt) { + self.cnt = stof(infokey (world, "round_time")) - 1; + self.cnt2 = 60; + } + else { + counter = floor(self.cnt); + if (counter < self.cnt) + self.cnt2 = ((self.cnt - counter) * 60); + else + self.cnt2 = 60; + if (self.cnt2 == 60) + self.cnt = (self.cnt - 1); + else + self.cnt = counter; + } + self.cnt2 = self.cnt2 + 1; + localcmd("serverinfo status \""); + local string tmp = ftos (self.cnt + 1); + localcmd(tmp); + localcmd(" min left\"\n"); + self.think = QuadRoundThink; + self.nextthink = (time + 0.001); +}; + +void () QuadRoundInit = { + local string num; + local entity p; + + if ((number_of_teams < 1) || cease_fire) { + self.nextthink = (time + 2); + if (self.cnt2 <= 5) + self.cnt2 = 10; + return; + } + self.cnt2 = (self.cnt2 - 1); + if (self.cnt2 == 2) + round_over = 2; + else { + if (!self.cnt2) { + localcmd("serverinfo status \"0 min left\"\n"); + self.nextthink = (time + 0.1); + self.think = QuadRoundBegin; + p = find(world, classname, "player"); + while (p != world) { + if (p.netname != "" && p.team_no && p.playerclass) { + p.takedamage = DAMAGE_AIM; + p.solid = SOLID_SLIDEBOX; + p.movetype = MOVETYPE_WALK; + } + p = find(p, classname, "player"); + } + return; + } + } + + num = ftos(self.cnt2); + p = find (world, classname, "player"); + while (p) { + if (p.netname != "") { + CenterPrint3(p, "Round begins in: ", num, " second(s).\nEnsure correct class is chosen!\n"); + + if (self.cnt2 <= 5) + { + cease_fire = 0; + cs_paused = 0; + stuffcmd(p, "play buttons/switch04.wav\n"); + } + } + p = find(p, classname, "player"); + } + self.nextthink = (time + 1); +}; + +void () StartQuadRound = +{ + local string st; + local float fl; + local entity te; + local entity te2; + local entity oldself; + local entity gren; + local entity p; + + lightstyle(0, "m"); + cease_fire = 0; + cs_paused = 0; + + p = find(world, classname, "player"); + while (p != world) { + if (p.netname != "" && p.team_no && p.playerclass) { + p.takedamage = DAMAGE_NO; + p.solid = SOLID_NOT; + p.movetype = MOVETYPE_NONE; + p.modelindex = 0; + p.model = string_null; + } + if(infokeyf(p, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(p, TRUE); + } + p = find(p, classname, "player"); + } + te = find(world, classname, "observer"); + while (te) { + if(infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(te, TRUE); + } + te = find(te, classname, "observer"); + } + + cb_prematch = 1; + round_over = 1; + + if (rounds == 1) { + while (p != world) { + if (p.netname != "" && p.team_no && p.playerclass) { + if(infokeyf(p, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(p, TRUE); + } + } + + p = find(p, classname, "player"); + } + + quad_winner = CheckWinningTeam(); + if (quad_winner == 0) + bprint (2, "Round Drawn!\n"); + else if (quad_winner == 1) + bprint(2, "Blue Team Wins!\n"); + else if (quad_winner == 2) + bprint(2, "Red Team Wins!\n"); + else if (quad_winner == 3) + bprint(2, "Yellow Team Wins!\n"); + else if (quad_winner == 4) + bprint(2, "Green Team Wins!\n"); + p = find (world, classname, "player"); + + // send result to server + PostFOQuadFinished(quad_winner); + + while (p != world) { + if (p.netname != "" && p.team_no && p.playerclass) { + p.takedamage = DAMAGE_NO; + p.solid = SOLID_NOT; + p.movetype = MOVETYPE_NONE; + p.modelindex = 0; + p.model = string_null; + } + if(infokeyf(p, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(p, TRUE); + } + p = find(p, classname, "player"); + } + te = find(world, classname, "observer"); + while (te) { + if(infokeyf(te, INFOKEY_P_CSQCACTIVE)) { + UpdateClientPrematch(te, TRUE); + } + te = find(te, classname, "observer"); + } + + bprint(2, "Rounds Over! Use \"cmd map \" to go to the nextmap\n"); + bprint(2, "map will auto-restart in ", ftos(map_restart_time)); + bprint(2, " seconds\n"); + if (!clan_scores_dumped) { + DumpClanScores(); + MapEndSequence(); + clan_scores_dumped = 1; + } + + localcmd("stop\n"); + Admin_UpdateServer(); + + return; + } + + if (rounds > 1){ + rounds = (rounds - 1); + + + p = find (world, classname, "player"); + + local float legit_players = 0; + while (p != world) { + if (p.team_no && p.playerclass) + legit_players++; + + p = find(p, classname, "player"); + } + + if (rounds == 2 && legit_players > 1) { + PostFOQuadStarted(); + if (legit_players == 4) + Activate2v2_Ruleset(); + } + + if (rounds == 1 /* final round */) { + te = find(world, classname, "player"); + while (te != world) { + if (te.all_time != ALL_TIME_COLOUR) { + sprint(te, PRINT_HIGH, "Swapping teams.\n"); + + switch(te.all_time) { + case ALL_TIME_ATTACK: + stuffcmd(te, "cmd changeteam attack\n"); + break; + case ALL_TIME_DEFENCE: + stuffcmd(te, "cmd changeteam defence\n"); + break; + } + } + + te = find(te, classname, "player"); + } + } + } + + if (intermission_running) + return; + + te = find(world, classname, "door"); + while (te != world) { + te2 = self; + self = te; + //door_go_down(); + door_go_down_silent(); + self.think = LinkDoors; + self = te2; + te = find(te, classname, "door"); + } + + te = find(world, classname, "item_tfgoal"); + while (te) { + if (te.origin != te.oldorigin) { + if (te.owner != world) + tfgoalitem_RemoveFromPlayer(te, te.owner, 1); + oldself = spawn(); + oldself.enemy = te; + oldself.weapon = 3; + oldself.nextthink = (time + 0.2); + oldself.think = ReturnItem; + } + te = find(te, classname, "item_tfgoal"); + } + + if (round_active) { + te = find(world, classname, "player"); + while (te != world) { + oldself = self; + self = te; + self.menu_count = 30; + self.current_menu = 1; + TeamFortress_ThrowGrenade(); + TeamFortress_RemoveTimers(); + if (self.playerclass == 9) { + Engineer_RemoveBuildings(self); + } + self = oldself; + te = find (te, classname, "player"); + } + round_active = 0; + } + gren = find(world, classname, "grenade"); + while (gren) { + gren.think = SUB_Remove; + gren.nextthink = (time + 0.1); + gren = find(gren, classname, "grenade"); + } + te = find(world, classname, "detpack"); + while (te){ + if (te.is_disarming == 1) { + TeamFortress_SetSpeed (te.enemy); + dremove(te.oldenemy); + dremove(te.observer_list); + } + dremove(te.linked_list); + dremove(te); + te = find (te, classname, "detpack"); + } + te = find(world, classname, "backpack"); + while (te) { + te.think = SUB_Remove; + te.nextthink = (time + 0.1); + te = find(te, classname, "backpack"); + } + te = find(world, classname, "ammobox"); + while (te) { + te.think = SUB_Remove; + te.nextthink = (time + 0.1); + te = find(te, classname, "ammobox"); + } + + te = find(world, classname, "round"); + st = infokey(world, "count"); + fl = stof(st); + if ((fl < 3) || (fl > 20)) { + fl = 10; + } + + local float rdt; + st = ftos(round_delay_time); + rdt = round_delay_time; + te.cnt2 = rdt; + st = infokey (world, "round_time"); + te.cnt = stof (st); + quad_winner = CheckWinningTeam(); + localcmd("serverinfo status Standby\n"); + if (rounds > 1) { + te.think = QuadRoundBegin; + te.nextthink = (time + 0.01); + } + else { + te.think = QuadRoundInit; + te.nextthink = (time + 1); + } +}; + +void Activate2v2_Ruleset() { + disable_resup_gren = 3; // Disable gren1 and gren2 pickups +} diff --git a/qw.qc b/ssqc/qw.qc similarity index 67% rename from qw.qc rename to ssqc/qw.qc index 9042e95d5..c3522e613 100644 --- a/qw.qc +++ b/ssqc/qw.qc @@ -1,7 +1,34 @@ -#include "defs.h" +#include "../share/defs.h" void (entity Goal, entity AP, entity ActivatingGoal) AttemptToActivate; -typedef void (float) f_void_float; +typedef void (float n) f_void_float; + +enum { + CT_NOEXTERNALEFFECT, // No external effects (e.g. reload, build, prime, etc) + CT_SLOW_PROJECTILE, // Slow proj => more judder (e.g. rocket) + CT_FAST_PROJECTILE, // Fast proj => less judder (e.g. heavy bullet) +}; + +.float last_death_ctime; +.float last_attack_ctime; + +.float client_time; // A stable/predictable client clock +.float client_ping; // ping used for prediction +.float last_remote_client_time; // For monoticity +.float client_lastupdate; + +.void() client_think; // client-time relative think +.float client_thinkindex; +.float client_nextthink; + +.int prng_base[PRNG_NUM_STATES]; + +// A monotonically non-decreasing, latency corrected notion of remote +// client-time. Monotonicity guarantees that for some offset `o`: +// at t1: remote_client_time() + o < at t2: lag_time() + o FOR t2 > t1 +// E.g. That you never have to worry about the correction reordering events +// that `time + o` would not. +float remote_client_time(); //=========================================================================== // TEAMFORTRESS Defs @@ -13,36 +40,24 @@ typedef void (float) f_void_float; .float armorclass; // Type of armor being worn .float tf_items; // Another flag for player items .float tf_items_flags; // Flags for the player items +.float primed_gren_type; // Type of (last) primed grenate +.float primed_gren_exp; // Expiry of (last) primed grenade .float no_grenades_1; // Number of grenades of type 1 being carried .float no_grenades_2; // Number of grenades of type 2 being carried -.float tp_grenades_1; // 1st type of grenades being carried -.float tp_grenades_2; // 2nd type of grenades being carried -.float tp_grenade_switch; // Set to 1 if +gren1 and +gren2 switch places - .float hook_out; // Dummy field for hook to silence error messages -.float got_aliases; // TRUE if the player has TeamFortress aliases .float cheat_check; // Time when we'll next check for team cheats .float is_removed; // TRUE if the entity has been removed .float is_undercover; // TRUE for a SPY if they're undercover -.float is_building; // TRUE for an ENGINEER if they're building something +.float is_building; // BUILD_DISPENSER, BUILD_SENTRYGUN or FALSE for an ENGINEER .float is_detpacking; // TRUE for a DEMOMAN if they're setting a detpack -.float is_feigning; // TRUE for a SPY if they're feigning death +.float is_button_feigning; // TRUE for a SPY if they're feigning death with +feign or +special +.float feign_next_damage; // TRUE for a SPY if they're going to feign death on next damage .float is_unabletospy; // TRUE for a SPY if they can't go undercover -.float is_zooming; // TRUE for a SNIPER if they're currently zoomed in -.float is_quickfiring; // TRUE for a player if they're using the quick slots -.float is_concussed; // TRUE for a player who is affected by concussion grenade -.float has_quickfired; // TRUE for a player that has stopped quick firing (until weapon is ready) .float has_throwngren; // TRUE for a player that has thrown a grenade (won't get a free suicide) .float has_changedteam; // TRUE for a player that has changed team .float has_changedclass; // TRUE for a player that has changed class .float has_disconnected; // TRUE if the player has disconnected .float has_menutimer; // TRUE if the player has an active menu timer -.float default_sensitivity; // Player's default (non-zoomed) sensitivity -.float default_fov; // Player's default (non-zoomed) fov -.float current_fov; // Player's current field of view -.float zoom_level; // Start zooming to this fov next frame -.float zoom_fov; // The default fov to zoom in to when pressing special button -.float zoom_last; // The last used zoom fov by the player - zoom to this when toggling zoom .float has_voted_map; // Map option that player voted for .float spawn_time; // Time when player spawned .float menu_time; // Time when vote map menu was first issued @@ -56,9 +71,10 @@ typedef void (float) f_void_float; .float vote_close; // TRUE if player has closed the map vote, resets on spawn .float detpack_left; // Seconds left to detpack explosion .float pipecooldown; // Time when detpipe command can be issued +.float detpipe_nesting; // Used to avoid recursive detpipes when detting self .float disguise_skin; // The skin the spy is changing to .float queue_skin; // The skin the spy will change to after team disguise is finished -.float last_skin; // The last skin the spy disguised as +.float last_selected_skin; // The last skin the spy selected to disguise as .float disguise_team; // The team the spy is changing to .float queue_team; // The team the spy will change to after skin disguise is finished .float last_team; // The last team the spy disguised as @@ -70,18 +86,21 @@ typedef void (float) f_void_float; .float regen_time; // Time when the medic last regenerated cells .float regen_cooldown; // Time to cooldown cell regeneration after using medikit heal .float saveme_time; // Time when player issued /saveme command -.float dash_cooldown; // Cooldown for Scout dash .float undercover_timer; // Seconds left until disguised .float power_time; // The time when sniper power in status bar was last updated -.float power_full; // Whether sniper power has reached full power or not .float sentry_ticks; // Used to indicate when engineer's sentry gun is finished building .float dispenser_ticks; // Used to indicate when engineer's dispenser is finished building .float building_percentage; // The building percentage shown in status bar .float fragstreak; // Frag streak .float caps; // Caps during this game +.float needs_skin_update; // Set true when per-player skins need updating + .entity nopickup; // Don't pick up this backpack/ammobox because it doesn't contain any of your ammo types +.entity disguised_as; // Used when cycling through disguises +.float pstate; // Player state flags .float tfstate; // State flags for TeamFortress +.float last_tfstate; // Internal cache of tfstate for detecting updates .entity linked_list; // Used just like chain. Has to be separate so // it doesn't interfere with chain. See T_RadiusScan @@ -105,17 +124,24 @@ typedef void (float) f_void_float; .float items_allowed; .float armor_allowed; .float maxarmor; -.float maxfbspeed; // Maximum forward/back speed -.float maxstrafespeed; // Maximum side speed +.float csqc_maxspeed; -.float weaponmode; // Used for multiple mode weapons -.float last_weaponmode; // Last weapon's weapon mode +.float is_disarming; .float motd; // Used to display MOTD +.float current_menu; +.float current_menu_type; +.float current_menu_page; +.float menu_count; +.float menu_displaytime; .f_void_float menu_input; +.float has_flag; + float toggleflags; // toggleable flags -float respawn_delay_time; +string nextmap; + +//float respawn_delay_time; // // FortressMap stuff @@ -149,9 +175,16 @@ float team2advantage; // stores the damage ratio players take/give float team3advantage; float team4advantage; +float round_winner_team; +entity round_winner; +float round_winner_print_health; + +float team_no_attack; .float team_no; // The team you belong to +.float all_time; // Automatically join attacking/defending team during quads .float lives; // The number of lives you have left + .float infection_team_no; // The team_no of the person who infected you // CTF stuff @@ -164,7 +197,7 @@ float coop; .entity real_owner; .float has_dispenser; // TRUE for an ENGINEER if he has a dispenser .float has_sentry; // TRUE for an ENGINEER if he has a sentry -.entity sentry_ent; // Contains sentry gun entity +.entity sentry_ent; // Contains sentry gun entity .float real_frags; // Used to store the players frags when TeamFrags is On @@ -174,23 +207,9 @@ float coop; /*==============================================*/ /* New Weapon variables */ /*==============================================*/ -.float weapons_carried; // the weapons the player is carrying -.float current_weapon; // the weapon the player is using -.float last_weapon; // the last weapon the player was using -.float current_weaponslot; // the currently equipped weaponslot -.float last_weaponslot; // the last equipped weaponslot -.float queue_weaponslot; // the weaponslot to switch to when possible -.float queue_weaponstate; // the weaponstate to load when possible -.float next_weapon; // used by weapon slots to communicate which weapon to select -.float next_weaponmode; // used by weapon slots to communicate which weapon mode to select - -// weapon states -.float weaponstate_current_weaponslot; -.float weaponstate_last_weaponslot; -.float weaponstate_current_weapon; -.float weaponstate_last_weapon; -.float weaponstate_weaponmode; -.float weaponstate_last_weaponmode; +.Slot current_slot; // the currently equipped (weapon) slot +.Slot last_slot; // the last slot the player was using +.Slot queue_slot; // the slot to switch to when possible .float ammo_medikit; // Ammo used for the medikit .float maxammo_medikit; @@ -199,15 +218,19 @@ float coop; .float noammo; // used for no ammo error messages // variables used for reloading +.float reload_started; // when the reload started +.float reload_finished; // when the reload will finish + +// Only use below for map ent compatibility. .float reload_shotgun; .float reload_super_shotgun; .float reload_sniper_rifle; .float reload_grenade_launcher; .float reload_rocket_launcher; -.float reload_tick; // how often the status bar should be refreshed during reload -.float reload_time; // when the reload will finish -.float reload_clipsize; // how much ammo is currently in the reloading clip -.float reload_sniper_ticks; // used to indicate when sniper rifle (with only 1 clip) will be reloaded +.float reload_assault_cannon; +// Only use above for map ent compatibility. + +.float clip_fired[TF_NUM_SLOTS]; // Assault Cannon .float heat; @@ -221,11 +244,9 @@ float coop; .float immune_to_check; // Make sure people don't do too many saveme sounds -.float last_saveme_sound; +.float next_saveme_sound; .float no_active_nail_grens; -.float no_active_napalm_grens; -.float no_active_gas_grens; /*======================================================================*/ /* TEAMFORTRESS GOALS */ @@ -235,6 +256,8 @@ float coop; .float group_no; // Goal group this goal is in .float goal_state; // State of this goal .float owned_by; // The team this goal/item/whatever belongs to +.float dropped_at; // Time when item was dropped +.entity dropped_by; // Entity that last held item // Goal Activation details .float goal_activation; // Bitfields. Determines how this goal is activated @@ -260,6 +283,7 @@ float coop; .float inactivate_group_no; .float remove_group_no; .float restore_group_no; +.float switch_team_group_no; .vector goal_min; .vector goal_max; @@ -327,6 +351,7 @@ float item_list_bit; // Global, used to determine what the bit of .float delay_time; // For delayed goal results .float dont_do_triggerwork; +.float track_goal; // Track this info_goal in the statusbar // Abbreviations for the above .float g_a; // goal_activation @@ -370,9 +395,11 @@ float item_list_bit; // Global, used to determine what the bit of //============================================================================ +// pyro float num_world_flames; .float numflames; // number of flames on entity .string flame_id; // has to be string so that the C function find() can be used +.float icdmgtime; // Spy variables .float undercover_team; @@ -380,6 +407,7 @@ float num_world_flames; .string undercover_name; .entity attacked_by; // Used by spy feign death to produce a fake death message .float feignmsg; // Stores the death message to be used when feigning +.float next_feign_time; // timestamp for when feign is next allowed float live_camera; .float camdist; @@ -389,7 +417,7 @@ float live_camera; float already_chosen_map; .float disptimer; -.float fire_held_down; +.float hit_in_current_animation; // Set on knife attack if a hit is recorded to prevent combo hits // sniper location damage stuff .vector head_shot_vector; @@ -399,11 +427,9 @@ float already_chosen_map; // flash grenade level .float FlashTime; -.float nailpos; // anti spam .float antispam_detpack; // don't spam detpack messages -.float antispam_feign; // don't spam feign deaths .float antispam_assault_cannon; // don't spam assault cannon messages .float antispam_cannon_air; // don't spam assault cannon air messages .float antispam_incendiary_cannon; // don't spam incendiary cannon messages @@ -419,20 +445,83 @@ float already_chosen_map; float clanbattle; float clan_scores_dumped; -float cb_prematch_time; -float cb_ceasefire_time; +float cb_prematch; +float disable_voting; +float org_game; +float v_break; +float v_ready; +.float allowvote; float game_locked; float team1frags; float team2frags; float team3frags; float team4frags; float last_id; +float quadmode; +float quad_winner; +float rounds; +float round_active; +float round_over; +float round_delay_time; +float round_start_time; +float round_end_time; +float map_restart_time; +float play_to_completion; +float gametime; +float is_countdown; +float duelmode; +float duel_reset_delay; +float duel_reset_timer; +float no_fire_mode; +float duel_all_grens; +float duel_no_packs; +float duel_spawn_guard; +.entity duel_guarded; +float duel_allow_draw; +float duel_tie_break; +float duel_draw_countdown; +float duel_autoprime; +float votemode; +float voting_started; +float voting_expires; +.entity vote_map; +float vote_anarchy_mode; +float vote_total_votes; +float vote_style; +float vote_threshold; +string game_filename; .float tf_id; float exec_map_cfgs; float spy_off; + +float spurs_scout; +float spurs_spy; +float spurs_engineer; +float spurs_duration; +float spurs_enabled; +float spurs_boost; +float spurs_consume; +float spurs_flag; + +float nailgren_type; +float lasergren_rotationcount; +float lasergren_rotationtime; +float lasergren_thinktime; +float lasergren_range; +float lasergren_damage; +float lasergren_reqthinks; +float lasergren_angleperthink; +float burstgren_count; +float burstgren_interval; +float burstgren_range; + +float medic_type; +float blastgren_velocity_multiplier; + float old_grens; +float fo_flash; float invis_only; float flagem_checked; float cease_fire; @@ -440,51 +529,117 @@ float drop_grenades; float drop_grenpack; float drop_gren1; float drop_gren2; -float grentimers; +float max_active_gren2_soldier; float id_extended; float remember_weapon; float discammo_pickup; float old_dropflag; -float reload_cliptick; float old_sniperrange; -float detpipe_limit; +//float detpipe_limit; float detpipe_limit_world; -float old_pipecooldown; +float pipecooldown_time; +float allpipes_cooldown; float medicaura; -float old_medikit; float old_biodamage; float flame_knockback; -float disp_explosion; float build_water; float cannon_lock; float cannon_air; float cannon_move; float cannon_movespin; -float cannon_conc; float cannon_accuracy; float feign_air; float feign_pack; float feign_msg; +float feign_rate_limit; float scoutdash; -float sniperreload; float spawnfull; float stockfull; +float stock_on_cap; +float cap_strip; +float stock_reload; float classtips; +float cussgrentime; +float medicnocuss; +float distance_based_cuss_duration; float sniperpower; -float sniperreloadpercent; float buildstatus; float server_default; float server_faithful; +float server_huetf; +float scoutscanspike; +float asscanrange; +float asscanrangedie; +float force_reload; float old_spanner; -float old_railgun; float old_dispenser; +float old_hp_armor; +float max_armor_hwguy; + +float ng_damage; +float sng_damage; +float superaxe; +float supermedikit; +float superspanner; +float superknife; +float superknife_multihit; +float impulse_queue; +float detpack_when_reloading; +float flag_follow; +float ceasefire_type; +float cs_paused; +float is_paused; +string pause_actor; +float unpause_requested; +float unpause_countdown; +float unpause_lastcountnumber; +float override_mapclasses; +float solid_detpack; +float walls_block_emp; +float new_emp; +float fo_sentry_targeting; // new improved targeting for sentry guns +float cb_keepteams; +float fo_flashtime; +float fo_repair_ratio; +float solid_nailgren; +float noreturn; +float nohitsounds; +float nohittext; +float zutmode; +float nokeepcells; +float allowpracspawns; +string discord_channel_id; +string demo_files_address; +string stats_files_address; +float splitbackpackmodels; +float standardizedeathammo; +float deathammo_shells; +float deathammo_nails; +float deathammo_rockets; +float deathammo_cells; +//float server_sbflaginfo; +float reverse_cap; +float engineer_move; +float grenade_lockout; + +float fo_projectiles; + +float fo_concuss; + +float numlocs; +typedef struct {vector pos; string desc;} loc_t; +loc_t *locs; .float teamkills; float autokick_time; float autokick_kills; +.float vote; +.float bvote; +.float stat_flags; + .float is_admin; .float admin_mode; .entity admin_use; @@ -496,6 +651,31 @@ float deathmsg; // Global, which is set before every T_Damage, t float intermission_running; float intermission_exittime; +// add clientkill ent defs +.float clientkillforce; +.float clientkillfree; + +// ability for doors to close instantly if they have a wait and are blocked +.float forcecloseonblock; + +// used to remember last spawn +.entity last_spawn_spot; +.float spawn_at_last_spawn_spot; + +.float spawn_gen; + +.float max_grenades_1; +.float max_grenades_2; + +.entity chain2; // allows for nesting of findradius (use in EMPGrenadeExplode) + +.entity vote_map_send; + +// global variables for voting +float vote1_cnt, vote2_cnt, vote3_cnt, vote4_cnt, vote5_cnt; +string vote1_map, vote2_map, vote3_map, vote4_map, vote5_map, vote_result; +float vote_started, vote_update, vote_winnercount, vote_decider, vote_abort; + //============================================================================ void (float psize, entity p) KickPlayer = { @@ -531,3 +711,75 @@ string (string s, float width) strpadr = return s; }; + +float captainmode; +.float captain; + +// fortressone.org API +.string fo_login; + +//Player ID +float loginRequired; +string loginUrl; +string webpageUrl; +.string login; +.float login_in_progress; + +//Logging fields +float gametime; +string gametimestamp; +float logfilehandle; +float canlog; +.float classtime; +.float goalrunningtime; + +// csqc scoreboard fields +.float caps; +.float touches; +.float kills; +.float sbteamkills; +.float deaths; +.float afflicted; +.float teamafflicted; +.float damagegiven; +.float damagetaken; + +// flash stuff +float FO_FlashDimension; +.float old_dimension_seen; + +// precise grenades +.entity grenade_timer; +.float last_throw; +.float last_prime; +.float conc_amp; +.float conc_finished; + +// allow for default goal state +.float default_group_no; +// classes to search for in goal remove, restore etc +string goal_class_names[7] = { + "info_tfgoal", + "trigger_teleport", + "door", + "func_wall", + "fo_misc_info", + "fo_logic", + "fo_math", +}; + +.string broadcast_high; + +.float special_next; +.float airblast_cooldown; +float disable_resup_gren; + +// backend +string match_id; +string backend_address; +float fo_login_required; +.entity filter_ent; + +var float PC_PYRO_AIRBLAST_COOLDOWN = 5; +var float PC_ENGINEER_GRENADE_TYPE_2_RANGE = 240; +.float detpack_last; diff --git a/ssqc/rewind.qc b/ssqc/rewind.qc new file mode 100644 index 000000000..0e5d6efa2 --- /dev/null +++ b/ssqc/rewind.qc @@ -0,0 +1,419 @@ +#define DEBUG_REWIND 0 + +#if DEBUG_REWIND +#define rw_printf(...) printf(__VA_ARGS__) +#define rw_printd(...) dprint(__VA_ARGS__) +#else +#define rw_printf(...) +#define rw_printd(...) +#endif + +#define MAX_SNAPSHOTS 50 + +inline int NextRewindIdx(int idx) { + return (idx + 1) % MAX_SNAPSHOTS; +} + +inline int PrevRewindIdx(int idx) { + return (idx - 1 + MAX_SNAPSHOTS) % MAX_SNAPSHOTS; +} + +struct RewindSnapshot { + float time; + vector origin; + vector velocity; +}; + +enum RewindStatus:float { + kPresent, + kRewound, +}; + +struct RewindState { + entity owner; + RewindSnapshot snapshot[MAX_SNAPSHOTS]; + int cur; + + RewindStatus rewound; + vector held_origin, held_velocity; + + RewindState* next; + RewindState* prev; +}; + +RewindState* rewind_players; + +static RewindState* AllocRewind(entity ent) { + RewindState* state = memalloc(sizeof(RewindState)); + state.rewound = kPresent; + state.owner = ent; + return state; +} + +static void FreeRewind(RewindState* h) { + memfree(h); +} + +static void RL_Insert(RewindState** head, RewindState* node) { + if (!*head) { + node->next = node->prev = __NULL__; + } else { + node->next = *head; + (*head)->prev = node; + } + *head = node; +} + +static void RL_Remove(RewindState** head, RewindState* node) { + if (*head == node) { + *head = __NULL__; + } else { + (node->next)->prev = node->prev; + (node->prev)->next = node->next; + } +} + +#define RL_FOR_EACH(_head, _var) \ + for (RewindState* _var = _head; _var; _var = _var->next) + +struct SeekResult { + RewindSnapshot* before; + RewindSnapshot* after; +}; + +static SeekResult RewindSeek(RewindState* rstate, float rtime) { + SeekResult r; + + r.before = r.after = __NULL__; + + int idx = rstate->cur; + for (int i = 0; i < MAX_SNAPSHOTS; i++) { + RewindSnapshot* rs = &rstate->snapshot[idx]; + + if (rs->time >= rtime) { + r.after = rs; + } else { + if (rs->time) + r.before = rs; + break; + } + + idx = PrevRewindIdx(idx); + } + + return r; +} + +static void DoubleCheckCollision(entity player, + RewindSnapshot* a, + RewindSnapshot* b) { + if (player.deadflag) + return; // Dead + if (vlen(b->origin - a->origin) > 48) + return; // Teleport + + const vector PLAYER_MINS = [-16, -16, -24], PLAYER_MAXS = [16, 16, 32]; + tracebox(a->origin, PLAYER_MINS, PLAYER_MAXS, b->origin, 0, player); + if (trace_fraction == 1) + return; + + entity hit = trace_ent; + + if (!hit.voided && hit.fpp.index && hit.touch) { + setorigin(player, trace_endpos); + + entity held_self = self; + self = hit; + other = player; + self.touch(); + self = held_self; + } +} + +RewindSnapshot* RewindLog(RewindState* target) { + if (target->owner != self) + error("Log mismatch\n"); + + target->owner->client_lastupdate = time; + + RewindSnapshot* rs = &target->snapshot[target->cur]; + RewindSnapshot* last = rs; + if (time > rs->time) { + if (rs->time) { + target->cur = NextRewindIdx(target->cur); + rs = &target->snapshot[target->cur]; + } + // Only set timestamp when we start the rewind entry. Subsequent + // overlapped updates will update the position but not the time (so that + // we can't drag it arbitrarily far forwards). + rs->time = time; + } + + rs->origin = target->owner.origin; + rs->velocity = target->owner.velocity; + + if (RewindFlagEnabled(REWIND_DOUBLE_COL) && rs != last) + DoubleCheckCollision(target->owner, last, rs); + + return rs; +} + +void DumpLog(RewindState* rs) { + for (float i = 0; i < MAX_SNAPSHOTS; i++) { + printf("%s%2d> t=%0.3f\n", i == rs->cur ? "*" : " ", i, rs->snapshot[i].time); + } +} + +static void RewindSave(RewindState* rs) { + rs->rewound = kRewound; + entity e = rs->owner; + + if (e.health <= 0) + return; + rs->held_origin = e.origin; + rs->held_velocity = e.velocity; +} + +static void RewindRestore(RewindState* rs, float type) { + ASSERTD_EQ(rs->rewound, kRewound); + + rs->rewound = type; + entity e = rs->owner; + + if (e.health <= 0) + return; + + setorigin(e, rs->held_origin); + // We restore origin, but preserve changes to velocity. +} + +static void RL_StashPositions(RewindState* head) { + RL_FOR_EACH(head, rs) + RewindSave(rs); +} + +static void RL_RestorePositions(RewindState* head) { + RL_FOR_EACH(head, rs) + RewindRestore(rs, kPresent); +} + +static void RewindTo(RewindState* rstate, float rtime) { + ASSERTD_EQ(rstate->rewound, kRewound); + entity e = rstate->owner; + + if (e.health <= 0 || rtime < e->spawn_time) + return; + + vector pos; + + if (rtime < e.client_lastupdate) { + SeekResult sr = RewindSeek(rstate, rtime); + RewindSnapshot* a = sr.after; + RewindSnapshot* b = sr.before; + + float a_time; + vector a_origin; + + if (a) { + a_time = a->time; + a_origin = a->origin; + } else { + a_time = time; + a_origin = rstate->held_origin; + } + + if (!b) { + pos = a_origin; // Should only happen if ran off the end of the + // array, this shouldn't occur at stock limits. + } else { + float frac = (rtime - b->time) / (a_time - b->time); + vector diff = a_origin - b->origin; + + if (vlen(diff) > 48) + frac = 1; // Most likely teleport. + + pos = b->origin + frac * diff; + } + } else { + pos = rstate->held_origin; + float max_xerp = CF_GetSetting("rwmx", "rewind_max_xerp", "0"); + if (max_xerp > 0) + pos += min(rtime - e.client_lastupdate, max_xerp) * rstate->held_velocity; + } + + setorigin(rstate->owner, pos); +} + +// TODO: Filter out observers, but no harm immediately. +static void RL_RewindTo(RewindState* head, entity exclude, float rtime) { + float show_rewind_points = + CF_GetSetting("rds", "rewind_debug_show", DEBUG_REWIND ? "on" : "off"); + + RL_FOR_EACH(head, rstate) { + entity e = rstate->owner; + if (e == exclude) + continue; + + RewindTo(rstate, rtime); + + if (show_rewind_points) + pointparticles(particleeffectnum("fo_airblast"), e->origin); + } +} + +class FOPlayer { + RewindState* rewind_; + + void() FOPlayer = { + rewind_ = AllocRewind(this); + RL_Insert(&rewind_players, rewind_); + }; + + virtual void() Destroy = { + RL_Remove(&rewind_players, rewind_); + FreeRewind(rewind_); + }; + + nonvirtual void() Respawn = { + }; + + nonvirtual void() RewindUpdate = { + if (rewind_) + RewindLog(rewind_); + }; + + nonvirtual void(float when) RewindExcept = { + RL_StashPositions(rewind_players); + RL_RewindTo(rewind_players, this, when); + }; + + nonvirtual vector() UnrewoundOrigin = { + if (rewind_->rewound == kRewound) { + return rewind_->held_origin; + } else { + return this->origin; + } + }; + + static void (float when) RewindAll = { + RL_StashPositions(rewind_players); + RL_RewindTo(rewind_players, world, when); + }; + + static void() RestoreAll { + RL_RestorePositions(rewind_players); + }; +}; + +// TODO: untangle and decide whether the class stuff is adding any value +void RW_RewindAll(float when) { + FOPlayer::RewindAll(when); +} + +void RW_StashAll() { + RL_StashPositions(rewind_players); +} + +void RW_RestoreAll() { + FOPlayer::RestoreAll(); + +} + +float (string ps_short, string ps_setting, string ps_default) CF_GetSetting; + +float RewindPlayersExceptSelf(float farthest_rewind_point) { + float rewind_max_offset = (MAX_SNAPSHOTS - 1) * SERVER_FRAME_DT; + farthest_rewind_point = max(farthest_rewind_point, + time - rewind_max_offset); + + // Det was pushed at remote_client_time(), let's see if we can get there. + float rewind_to = max(farthest_rewind_point, remote_time()); + + // Ignore for LAN pings. + if (time - rewind_to < SERVER_FRAME_DT) + return FALSE; + + FOPlayer fop = (FOPlayer)self; + fop.RewindExcept(rewind_to); + return TRUE; +} + +void ProjRewindForTravel(entity e, float target_time) { + RL_RewindTo(rewind_players, world, target_time); +} + +void ProjRewindForPhys(entity e, float target_time) { + RL_RewindTo(rewind_players, e.owner, target_time); +} + +void NoRewindSyncFn(entity e, float target_time) { + error("not set\n"); +} + +void Forward_Projectile(int fpp_type, entity proj, float use_ctime) { + float ping = proj.owner.client_ping; + if (use_ctime) { + // These can be pretty late with sendevent latency on top so let's be + // conservative. + ping = min((proj.owner.client_time - use_ctime) * 1000, + fo_config.max_rewind_ms - 25); + } + + ProjectResult offset = Forward_ProjectOffset(fpp_type, ping); + float static_dt = offset.static_ms / 1000.0; + float dynamic_dt = offset.dynamic_ms / 1000.0; + + + float rewind_hit = RewindFlagEnabled(REWIND_PROJ_FIRE) && + !(proj.fpp.flags & FPF_NO_REWIND) && + dynamic_dt > 0; + + float phys_flags = PHYSF_CONSUME_ALL; + float stime = time; + + if (rewind_hit) { + stime -= dynamic_dt; + phys_flags |= PHYSF_REWIND_PLAYERS; + RL_StashPositions(rewind_players); + RL_RewindTo(rewind_players, proj.owner, stime); + } + + // Static projection happens instantly. If rewind is active, we'll do it at + // a prior point in time, but we don't advance time while stepping. + float ft = Phys_Init(proj, stime, static_dt, PHYSF_CONSUME_ALL); + + // We initialize s_origin after Phys_Init, they are used when + // knockback forwarding is on to determine delay. + if (rewind_hit && RewindFlagEnabled(REWIND_FORWARD_PROJ_SELFKNOCK)) { + phys_flags |= PHYSF_FORWARD_KNOCK; + proj.s_origin = proj.origin; + } + + if (!proj.voided) { + RewindSyncTime = ProjRewindForPhys; + ft += Phys_Advance(proj, time, phys_flags); + RewindSyncTime = NoRewindSyncFn; + } + + if (rewind_hit) + RL_RestorePositions(rewind_players); + + proj.antilag_ms = ft * 1000; +} + +void Forward_OpenDoors(entity player) { + if (!RewindFlagEnabled(REWIND_FORWARD_DOORS)) + return; + + vector offset = min(550, vlen(player.velocity)) * normalize(player.velocity); + offset *= max(player.client_ping, 150) / 1000.0; + traceline(player.origin, player.origin + offset, MOVE_TRIGGERS, player); + + if (trace_fraction < 1 && trace_ent.solid == SOLID_TRIGGER) { + entity held_self = self; + other = player; + self = trace_ent; + trace_ent.touch(); + self = held_self; + } +} diff --git a/ssqc/roles.qc b/ssqc/roles.qc new file mode 100644 index 000000000..3dcff6a47 --- /dev/null +++ b/ssqc/roles.qc @@ -0,0 +1,156 @@ +#pragma target fte + +float (string ps_short, string ps_setting, string ps_default) CF_GetSetting; +string(float tno) GetTeamName; +void () item_tfgoal_touch; +void CenterPrint(entity, string); +float IsFeigned(entity ent); + + +float quad_roles; + +typedef struct { + string name; + string prefix; + float detpipe_limit; + float respawn_delay_time; + float gren1_limits[10]; + float gren2_limits[10]; + float class_limits[12]; +} Team_Role; + +var Team_Role Role_None = {"Default", "", 6}; +var Team_Role Role_Attack = {"Attack", "att_", 6}; +var Team_Role Role_Defence = {"Defence", "def_", 6}; +Team_Role * Team1_Role; +Team_Role * Team2_Role; +Team_Role * Team3_Role; +Team_Role * Team4_Role; + +const string CLASSES [12] = { + "observer", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian" +}; + +void (Team_Role * role) LoadRole = { + role.detpipe_limit = CF_GetSetting(strcat(role.prefix,"dl"), strcat(role.prefix,"detpipe_limit"), ftos(Role_None.detpipe_limit)); + role.respawn_delay_time = CF_GetSetting(strcat(role.prefix,"rd"), strcat(role.prefix,"respawn_delay"), ftos(Role_None.respawn_delay_time)); + role.gren1_limits[0] = 0; + role.gren2_limits[0] = 0; + role.class_limits[0] = 0; + for(float i = 1; i < 10; i++) { + role.gren1_limits[i] = CF_GetSetting(strcat(role.prefix,"mg1_",ftos(i)), strcat(role.prefix,"max_gren1_",CLASSES[i]), ftos(Role_None.gren1_limits[i])); + role.gren2_limits[i] = CF_GetSetting(strcat(role.prefix,"mg2_",ftos(i)), strcat(role.prefix,"max_gren2_",CLASSES[i]), ftos(Role_None.gren2_limits[i])); + role.class_limits[i] = CF_GetSetting(strcat(role.prefix,"cr_",CLASSES[i]), strcat(role.prefix,"cr_",CLASSES[i]), ftos(Role_None.class_limits[i])); + } +}; + +void (entity e) PrintRoleStatus = { + float current = CF_GetSetting("qr", "quad_roles", "0"); + sprint(e, PRINT_HIGH, "Roles are ", quad_roles?"\sENABLED\s":"\sDISABLED\s"); + if(!quadmode) { + sprint(e, PRINT_HIGH, ", however it requires \squadmode\s to work. Set it from the admin menu."); + } + if(current != quad_roles) { + sprint(e, PRINT_HIGH, "\nNote that the setting will be ", current?"\sENABLED\s":"\sDISABLED\s", " after map restart."); + } + sprint(e, PRINT_HIGH, "\n"); +}; + +void (entity e, Team_Role * role) PrintRole = { + sprint(e, PRINT_HIGH, "\sRole:\s ", role.name, "\n"); + sprint(e, PRINT_HIGH, "Detpipe Limit: ", ftos(role.detpipe_limit), "\n"); + sprint(e, PRINT_HIGH, "Respawn Delay: ", ftos(role.respawn_delay_time), "\n"); + for(float i = 1; i < 10; i++) { + sprint(e, PRINT_HIGH, CLASSES[i],": \n"); + sprint(e, PRINT_HIGH, "\tGren 1: ", ftos(role.gren1_limits[i]), "\n"); + sprint(e, PRINT_HIGH, "\tGren 2: ", ftos(role.gren2_limits[i]), "\n"); + sprint(e, PRINT_HIGH, "\tClass Limit: ", ftos(role.class_limits[i]), "\n"); + } + sprint(e, PRINT_HIGH, CLASSES[10],": \n"); + sprint(e, PRINT_HIGH, "\tClass Limit: ", ftos(role.class_limits[10]), "\n"); +} + +void () InitTeamRoles = { + Team1_Role = &Role_None; + Team2_Role = &Role_None; + Team3_Role = &Role_None; + Team4_Role = &Role_None; +}; + +void (entity e) PrintTeamRoles = { + sprint(e, PRINT_HIGH, "\sTeam Roles:\s\n"); + if(number_of_teams > 0) { + sprint(e, PRINT_HIGH, GetTeamName(1), ": ", Team1_Role.name, "\n"); + } + if(number_of_teams > 1) { + sprint(e, PRINT_HIGH, GetTeamName(2), ": ", Team2_Role.name, "\n"); + } + if(number_of_teams > 2) { + sprint(e, PRINT_HIGH, GetTeamName(3), ": ", Team3_Role.name, "\n"); + } + if(number_of_teams > 3) { + sprint(e, PRINT_HIGH, GetTeamName(4), ": ", Team4_Role.name, "\n"); + } +}; + +Team_Role* (float tno) GetTeamRole = { + switch (tno) { + case 1: + return Team1_Role; + case 2: + return Team2_Role; + case 3: + return Team3_Role; + case 4: + return Team4_Role; + } + return &Role_None; +}; + +void () item_tfgoal_hidden_touch = { + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (cb_prematch) + return; + if (IsFeigned(other)) + return; + if (other == self.owner) + return; + + // check if a wall or something is in the way of the flag + traceline(other.origin, self.origin, TRUE, world); + if (trace_fraction < 1) + return; + + /*if(self.owned_by == other.team_no) { + CenterPrint(other, "Unfortunately, your flag is in another castle\n\nYou are Attacking the other base\n"); + } else { + CenterPrint(other, "You are defending the wrong flag!\n\nGet back to your base quickly\n"); + }*/ +} + +void (entity flag) Quad_HideFlag = { + flag.touch = item_tfgoal_hidden_touch; + setmodel(flag, ""); + setsize(flag, flag.goal_min, flag.goal_max); +}; + +void (entity flag) Quad_UnHideFlag = { + flag.touch = item_tfgoal_touch; + setmodel(flag, flag.mdl); + setsize(flag, flag.goal_min, flag.goal_max); +}; diff --git a/ssqc/rotate.qc b/ssqc/rotate.qc new file mode 100644 index 000000000..a7add9a65 --- /dev/null +++ b/ssqc/rotate.qc @@ -0,0 +1,1197 @@ +/* Rotate QuickC program + By Jim Dose' 10/17/96 + Copyright (c)1996 Hipnotic Interactive, Inc. + All rights reserved. + Distributed (unsupported) on 3.12.97 +*/ + +float ROT_STATE_ACTIVE = 0; +float ROT_STATE_INACTIVE = 1; +float ROT_STATE_SPEEDINGUP = 2; +float ROT_STATE_SLOWINGDOWN = 3; + +float ROT_STATE_CLOSED = 4; +float ROT_STATE_OPEN = 5; +float ROT_STATE_OPENING = 6; +float ROT_STATE_CLOSING = 7; + +float ROT_STATE_WAIT = 0; +float ROT_STATE_MOVE = 1; +float ROT_STATE_STOP = 2; +float ROT_STATE_FIND = 3; +float ROT_STATE_NEXT = 4; + +float ROT_OBJECT_ROTATE = 0; +float ROT_OBJECT_MOVEWALL = 1; +float ROT_OBJECT_SETORIGIN = 2; + +// spawnflags for func_rotate_entity +float ROT_ROTATE_ENTITY_TOGGLE = 1; +float ROT_ROTATE_ENTITY_START_ON = 2; + +// spawnflags for path_rotate +float ROT_PATH_ROTATE_ROTATION = 1; +float ROT_PATH_ROTATE_ANGLES = 2; +float ROT_PATH_ROTATE_STOP = 4; +float ROT_PATH_ROTATE_NO_ROTATE = 8; +float ROT_PATH_ROTATE_DAMAGE = 16; +float ROT_PATH_ROTATE_MOVETIME = 32; +float ROT_PATH_ROTATE_SET_DAMAGE = 64; + +// spawnflags for func_rotate_door +float ROT_ROTATE_DOOR_STAYOPEN = 1; + +// spawnflags for func_movewall +float ROT_MOVEWALL_VISIBLE = 1; +float ROT_MOVEWALL_TOUCH = 2; +float ROT_MOVEWALL_NONBLOCKING = 4; + +.float rotate_type; +.vector neworigin; +.vector rotate; +.float endtime; +.float duration; +.vector finalangle; +.string path; +.string group; +.string event; + + +//========================= +// +// SUB_NormalizeAngles +// +//========================= + +vector (vector ang) SUB_NormalizeAngles = +{ + while( ang_x > 360 ) + { + ang_x = ang_x - 360; + } + while( ang_x < 0 ) + { + ang_x = ang_x + 360; + } + + while( ang_y > 360 ) + { + ang_y = ang_y - 360; + } + while( ang_y < 0 ) + { + ang_y = ang_y + 360; + } + + while( ang_z > 360 ) + { + ang_z = ang_z - 360; + } + while( ang_z < 0 ) + { + ang_z = ang_z + 360; + } + + return ang; +}; + +/*QUAKED info_rotate (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as the point of rotation for rotatable objects. +*/ +void() info_rotate = +{ +// remove self after a little while, to make sure that entities that +// have targeted it have had a chance to spawn + self.nextthink = time + 2; + self.think = SUB_Remove; +}; + +void() RotateTargets = +{ + local entity ent; + local vector vx; + local vector vy; + local vector vz; + local vector org; + + makevectors (self.angles); + + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.rotate_type == ROT_OBJECT_SETORIGIN ) + { + org = ent.oldorigin; + vx = ( v_forward * org_x ); + vy = ( v_right * org_y ); + vy = vy * -1; + vz = ( v_up * org_z ); + ent.neworigin = vx + vy + vz; + setorigin( ent, ent.neworigin + self.origin ); + } + else if ( ent.rotate_type == ROT_OBJECT_ROTATE ) + { + ent.angles = self.angles; + org = ent.oldorigin; + vx = ( v_forward * org_x ); + vy = ( v_right * org_y ); + vy = vy * -1; + vz = ( v_up * org_z ); + ent.neworigin = vx + vy + vz; + setorigin( ent, ent.neworigin + self.origin ); + } + else + { + org = ent.oldorigin; + vx = ( v_forward * org_x ); + vy = ( v_right * org_y ); + vy = vy * -1; + vz = ( v_up * org_z ); + ent.neworigin = vx + vy + vz; + ent.neworigin = self.origin - self.oldorigin + (ent.neworigin - ent.oldorigin); + ent.velocity = (ent.neworigin-ent.origin)*25; + } + ent = find( ent, targetname, self.target); + } +}; + +void() RotateTargetsFinal = + { + local entity ent; + + ent = find( world, targetname, self.target); + while( ent ) + { + ent.velocity = '0 0 0'; + if ( ent.rotate_type == ROT_OBJECT_ROTATE ) + { + ent.angles = self.angles; + } + ent = find( ent, targetname, self.target); + } + }; + +void() SetTargetOrigin = + { + local entity ent; + + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.rotate_type == ROT_OBJECT_MOVEWALL ) + { + setorigin( ent, self.origin - self.oldorigin + + (ent.neworigin - ent.oldorigin) ); + } + else + { + setorigin( ent, ent.neworigin + self.origin ); + } + ent = find( ent, targetname, self.target); + } + }; + +void() LinkRotateTargets = + { + local entity ent; + local vector tempvec; + + self.oldorigin = self.origin; + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.classname == "rotate_object" ) + { + ent.rotate_type = ROT_OBJECT_ROTATE; + ent.oldorigin = ent.origin - self.oldorigin; + ent.neworigin = ent.origin - self.oldorigin; + ent.owner = self; + } + else if ( ent.classname == "func_movewall" ) + { + ent.rotate_type = ROT_OBJECT_MOVEWALL; + tempvec = ( ent.absmin + ent.absmax ) * 0.5; + ent.oldorigin = tempvec - self.oldorigin; + ent.neworigin = ent.oldorigin; + ent.owner = self; + } + else + { + ent.rotate_type = ROT_OBJECT_SETORIGIN; + ent.oldorigin = ent.origin - self.oldorigin; + ent.neworigin = ent.origin - self.oldorigin; + } + ent = find (ent, targetname, self.target); + } + }; + +void( entity ent, float amount ) hurt_setdamage = + { + ent.dmg = amount; + if ( !amount ) + { + ent.solid = SOLID_NOT; + } + else + { + ent.solid = SOLID_TRIGGER; + } + ent.nextthink = -1; + }; + +void( float amount ) SetDamageOnTargets = + { + local entity ent; + + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.classname == "trigger_hurt" ) + { + hurt_setdamage( ent, amount ); + } + else if ( ent.classname == "func_movewall" ) + { + ent.dmg = amount; + } + ent = find( ent, targetname, self.target); + } + }; + + +//************************************************ +// +// Simple continual rotatation +// +//************************************************ + +void() rotate_entity_think = + { + local float t; + + t = time - self.ltime; + self.ltime = time; + + if ( self.state == ROT_STATE_SPEEDINGUP ) + { + self.count = self.count + self.cnt * t; + if ( self.count > 1 ) + { + self.count = 1; + } + + // get rate of rotation + t = t * self.count; + } + else if ( self.state == ROT_STATE_SLOWINGDOWN ) + { + self.count = self.count - self.cnt * t; + if ( self.count < 0 ) + { + RotateTargetsFinal(); + self.state = ROT_STATE_INACTIVE; + self.think = SUB_Null; + return; + } + + // get rate of rotation + t = t * self.count; + } + + self.angles = self.angles + ( self.rotate * t ); + self.angles = SUB_NormalizeAngles( self.angles ); + RotateTargets(); + self.nextthink = time + 0.02; + }; + +void() rotate_entity_use = + { + // change to alternate textures + self.frame = 1 - self.frame; + + if ( self.state == ROT_STATE_ACTIVE ) + { + if ( self.spawnflags & ROT_ROTATE_ENTITY_TOGGLE ) + { + if ( self.speed ) + { + self.count = 1; + self.state = ROT_STATE_SLOWINGDOWN; + } + else + { + self.state = ROT_STATE_INACTIVE; + self.think = SUB_Null; + } + } + } + else if ( self.state == ROT_STATE_INACTIVE ) + { + self.think = rotate_entity_think; + self.nextthink = time + 0.02; + self.ltime = time; + if ( self.speed ) + { + self.count = 0; + self.state = ROT_STATE_SPEEDINGUP; + } + else + { + self.state = ROT_STATE_ACTIVE; + } + } + else if ( self.state == ROT_STATE_SPEEDINGUP ) + { + if ( self.spawnflags & ROT_ROTATE_ENTITY_TOGGLE ) + { + self.state = ROT_STATE_SLOWINGDOWN; + } + } + else + { + self.state = ROT_STATE_SPEEDINGUP; + } + }; + +void() rotate_entity_firstthink = + { + LinkRotateTargets(); + if ( self.spawnflags & ROT_ROTATE_ENTITY_START_ON ) + { + self.state = ROT_STATE_ACTIVE; + self.think = rotate_entity_think; + self.nextthink = time + 0.02; + self.ltime = time; + } + else + { + self.state = ROT_STATE_INACTIVE; + self.think = SUB_Null; + } + self.use = rotate_entity_use; + }; + +/*QUAKED func_rotate_entity (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE START_ON +Creates an entity that continually rotates. Can be toggled on and +off if targeted. + +TOGGLE = allows the rotation to be toggled on/off + +START_ON = wether the entity is spinning when spawned. If TOGGLE is 0, entity can be turned on, but not off. + +If "deathtype" is set with a string, this is the message that will appear when a player is killed by the train. + +"rotate" is the rate to rotate. +"target" is the center of rotation. +"speed" is how long the entity takes to go from standing still to full speed and vice-versa. +*/ + +void() func_rotate_entity = +{ + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + + setmodel (self, self.model); + setsize( self, self.mins, self.maxs ); + + if ( self.speed != 0 ) + { + self.cnt = 1 / self.speed; + } + + self.think = rotate_entity_firstthink; + self.nextthink = time + 0.1; + self.ltime = time; +}; + +//************************************************ +// +// Train with rotation functionality +// +//************************************************ + +/*QUAKED path_rotate (0.5 0.3 0) (-8 -8 -8) (8 8 8) ROTATION ANGLES STOP NO_ROTATE DAMAGE MOVETIME SET_DAMAGE + Path for rotate_train. + + ROTATION tells train to rotate at rate specified by "rotate". Use '0 0 0' to stop rotation. + + ANGLES tells train to rotate to the angles specified by "angles" while traveling to this path_rotate. Use values < 0 or > 360 to guarantee that it turns in a certain direction. Having this flag set automatically clears any rotation. + + STOP tells the train to stop and wait to be retriggered. + + NO_ROTATE tells the train to stop rotating when waiting to be triggered. + + DAMAGE tells the train to cause damage based on "dmg". + + MOVETIME tells the train to interpret "speed" as the length of time to take moving from one corner to another. + + SET_DAMAGE tells the train to set all targets damage to "dmg" + + "noise" contains the name of the sound to play when train stops. + "noise1" contains the name of the sound to play when train moves. + "event" is a target to trigger when train arrives at path_rotate. +*/ +void() path_rotate = +{ + if ( self.noise != "" ) + { + precache_sound( self.noise ); + } + if ( self.noise1 != "" ) + { + precache_sound( self.noise1 ); + } +}; + + +void() rotate_train; +void() rotate_train_next; +void() rotate_train_find; + +void() rotate_train_think = + { + local float t; + local float timeelapsed; + + t = time - self.ltime; + self.ltime = time; + + if ( ( self.endtime ) && ( time >= self.endtime ) ) + { + self.endtime = 0; + if ( self.state == ROT_STATE_MOVE ) + { + setorigin(self, self.finaldest); + self.velocity = '0 0 0'; + } + + if (self.think1) + self.think1(); + } + else + { + timeelapsed = (time - self.cnt) * self.duration; + if ( timeelapsed > 1 ) + timeelapsed = 1; + setorigin( self, self.dest1 + ( self.dest2 * timeelapsed ) ); + } + + self.angles = self.angles + ( self.rotate * t ); + self.angles = SUB_NormalizeAngles( self.angles ); + RotateTargets(); + + self.nextthink = time + 0.02; + }; + +void() rotate_train_use = + { + if (self.think1 != rotate_train_find) + { + if ( self.velocity != '0 0 0' ) + return; // already activated + if ( self.think1 ) + { + self.think1(); + } + } + }; + +void() rotate_train_wait = + { + self.state = ROT_STATE_WAIT; + + if ( self.goalentity.noise != "" ) + { + sound (self, CHAN_VOICE, self.goalentity.noise, 1, ATTN_NORM); + } + else + { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + if ( self.goalentity.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.rotate = '0 0 0'; + self.angles = self.finalangle; + } + if ( self.goalentity.spawnflags & ROT_PATH_ROTATE_NO_ROTATE ) + { + self.rotate = '0 0 0'; + } + self.endtime = self.ltime + self.goalentity.wait; + self.think1 = rotate_train_next; + }; + +void() rotate_train_stop = + { + self.state = ROT_STATE_STOP; + + if ( self.goalentity.noise != "" ) + { + sound (self, CHAN_VOICE, self.goalentity.noise, 1, ATTN_NORM); + } + else + { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + if ( self.goalentity.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.rotate = '0 0 0'; + self.angles = self.finalangle; + } + if ( self.goalentity.spawnflags & ROT_PATH_ROTATE_NO_ROTATE ) + { + self.rotate = '0 0 0'; + } + + self.dmg = 0; + self.think1 = rotate_train_next; + }; + +void() rotate_train_next = +{ + local entity targ; + local entity current; + local vector vdestdelta; + local float len, traveltime, div; + local string temp; + + self.state = ROT_STATE_NEXT; + + current = self.goalentity; + targ = find (world, targetname, self.path); + if ( targ.classname != "path_rotate" ) + objerror( "Next target is not path_rotate" ); + + if ( self.goalentity.noise1 != "" ) + { + self.noise1 = self.goalentity.noise1; + } + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + + self.goalentity = targ; + self.path = targ.target; + if (!self.path ) + objerror ("rotate_train_next: no next target"); + + if ( targ.spawnflags & ROT_PATH_ROTATE_STOP ) + { + self.think1 = rotate_train_stop; + } + else if (targ.wait) + { + self.think1 = rotate_train_wait; + } + else + { + self.think1 = rotate_train_next; + } + + if ( current.event != "" ) + { + // Trigger any events that should happen at the corner. + temp = self.target; + self.target = current.event; + self.message = current.message; + SUB_UseTargets(); + self.target = temp; + self.message = string_null; + } + + if ( current.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.rotate = '0 0 0'; + self.angles = self.finalangle; + } + + if ( current.spawnflags & ROT_PATH_ROTATE_ROTATION ) + { + self.rotate = current.rotate; + } + + if ( current.spawnflags & ROT_PATH_ROTATE_DAMAGE ) + { + self.dmg = current.dmg; + } + + if ( current.spawnflags & ROT_PATH_ROTATE_SET_DAMAGE ) + { + SetDamageOnTargets( current.dmg ); + } + + if ( current.speed == -1 ) + { + // Warp to the next path_corner + setorigin( self, targ.origin ); + self.endtime = self.ltime + 0.01; + SetTargetOrigin(); + + if ( targ.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.angles = targ.angles; + } + + self.duration = 1; // 1 / duration + self.cnt = time; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + self.finaldest = self.origin; + } + else + { + self.state = ROT_STATE_MOVE; + + self.finaldest = targ.origin; + if (self.finaldest == self.origin) + { + self.velocity = '0 0 0'; + self.endtime = self.ltime + 0.1; + + self.duration = 1; // 1 / duration + self.cnt = time; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + self.finaldest = self.origin; + return; + } + // set destdelta to the vector needed to move + vdestdelta = self.finaldest - self.origin; + + // calculate length of vector + len = vlen (vdestdelta); + + if ( current.spawnflags & ROT_PATH_ROTATE_MOVETIME ) + { + traveltime = current.speed; + } + else + { + // check if there's a speed change + if (current.speed>0) + self.speed = current.speed; + + if (!self.speed) + objerror("No speed is defined!"); + + // divide by speed to get time to reach dest + traveltime = len / self.speed; + } + + if (traveltime < 0.1) + { + self.velocity = '0 0 0'; + self.endtime = self.ltime + 0.1; + if ( targ.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.angles = targ.angles; + } + return; + } + + // qcc won't take vec/float + div = 1 / traveltime; + + if ( targ.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.finalangle = SUB_NormalizeAngles( targ.angles ); + self.rotate = ( targ.angles - self.angles ) * div; + } + + // set endtime to trigger a think when dest is reached + self.endtime = self.ltime + traveltime; + + // scale the destdelta vector by the time spent traveling to get velocity + self.velocity = vdestdelta * div; + + self.duration = div; // 1 / duration + self.cnt = time; // start time + self.dest2 = vdestdelta; // delta + self.dest1 = self.origin; // original position + } + }; + +void() rotate_train_find = + { + local entity targ; + + self.state = ROT_STATE_FIND; + + LinkRotateTargets(); + + // the first target is the point of rotation. + // the second target is the path. + targ = find ( world, targetname, self.path); + if ( targ.classname != "path_rotate" ) + objerror( "Next target is not path_rotate" ); + + // Save the current entity + self.goalentity = targ; + + if ( targ.spawnflags & ROT_PATH_ROTATE_ANGLES ) + { + self.angles = targ.angles; + self.finalangle = SUB_NormalizeAngles( targ.angles ); + } + + self.path = targ.target; + setorigin (self, targ.origin ); + SetTargetOrigin(); + RotateTargetsFinal(); + self.think1 = rotate_train_next; + if (!self.targetname) + { + // not triggered, so start immediately + self.endtime = self.ltime + 0.1; + } + else + { + self.endtime = 0; + } + + self.duration = 1; // 1 / duration + self.cnt = time; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + }; + +/*QUAKED func_rotate_train (0 .5 .8) (-8 -8 -8) (8 8 8) +In path_rotate, set speed to be the new speed of the train after it reaches +the path change. If speed is -1, the train will warp directly to the next +path change after the specified wait time. If MOVETIME is set on the +path_rotate, the train to interprets "speed" as the length of time to +take moving from one corner to another. + +"noise" contains the name of the sound to play when train stops. +"noise1" contains the name of the sound to play when train moves. +Both "noise" and "noise1" defaults depend upon "sounds" variable and +can be overridden by the "noise" and "noise1" variable in path_rotate. + +Also in path_rotate, if STOP is set, the train will wait until it is +retriggered before moving on to the next goal. + +Trains are moving platforms that players can ride. +"path" specifies the first path_rotate and is the starting position. +If the train is the target of a button or trigger, it will not begin moving until activated. +The func_rotate_train entity is the center of rotation of all objects targeted by it. + +If "deathtype" is set with a string, this is the message that will appear when a player is killed by the train. + +speed default 100 +dmg default 0 +sounds +1) ratchet metal +*/ + +void() rotate_train = + { + objerror ("rotate_train entities should be changed to rotate_object with\nfunc_rotate_train controllers\n"); + }; + +void() func_rotate_train = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("rotate_train without a target"); + + if ( !self.noise ) + { + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav"); + } + } + if ( !self.noise1 ) + { + if (self.sounds == 0) + { + self.noise1 = ("misc/null.wav"); + } + if (self.sounds == 1) + { + self.noise1 = ("plats/train1.wav"); + } + } + + precache_sound( self.noise ); + precache_sound( self.noise1 ); + + self.cnt = 1; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_STEP; + self.use = rotate_train_use; + + setmodel (self, self.model); + setsize (self, self.mins, self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.ltime = time; + self.nextthink = self.ltime + 0.1; + self.endtime = self.ltime + 0.1; + self.think = rotate_train_think; + self.think1 = rotate_train_find; + self.state = ROT_STATE_FIND; + + self.duration = 1; // 1 / duration + self.cnt = 0.1; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + + + self.flags = self.flags | FL_ONGROUND; +}; + +//************************************************ +// +// Moving clip walls +// +//************************************************ + +void() rotate_door_reversedirection; +void() rotate_door_group_reversedirection; + +void() movewall_touch = + { + if (time < self.owner.attack_finished) + return; + + if ( self.dmg ) + { + T_Damage (other, self, self.owner, self.dmg); + self.owner.attack_finished = time + 0.5; + } + else if ( self.owner.dmg ) + { + T_Damage (other, self, self.owner, self.owner.dmg); + self.owner.attack_finished = time + 0.5; + } + }; + +void() movewall_blocked = + { + local entity temp; + + if (time < self.owner.attack_finished) + return; + + self.owner.attack_finished = time + 0.5; + + if ( self.owner.classname == "func_rotate_door" ) + { + temp = self; + self = self.owner; + rotate_door_group_reversedirection(); + self = temp; + } + + if ( self.dmg ) + { + T_Damage (other, self, self.owner, self.dmg); + self.owner.attack_finished = time + 0.5; + } + else if ( self.owner.dmg ) + { + T_Damage (other, self, self.owner, self.owner.dmg); + self.owner.attack_finished = time + 0.5; + } + }; + +void() movewall_think = + { + self.ltime = time; + self.nextthink = time + 0.02; + }; + +/*QUAKED func_movewall (0 .5 .8) ? VISIBLE TOUCH NONBLOCKING +Used to emulate collision on rotating objects. + +VISIBLE causes brush to be displayed. + +TOUCH specifies whether to cause damage when touched by player. + +NONBLOCKING makes the brush non-solid. This is useless if VISIBLE is set. + +"dmg" specifies the damage to cause when touched or blocked. +*/ +void() func_movewall = +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; + if ( self.spawnflags & ROT_MOVEWALL_NONBLOCKING ) + { + self.solid = SOLID_NOT; + } + else + { + self.solid = SOLID_BSP; + self.blocked = movewall_blocked; + } + if ( self.spawnflags & ROT_MOVEWALL_TOUCH ) + { + self.touch = movewall_touch; + } + setmodel (self,self.model); + if ( !( self.spawnflags & ROT_MOVEWALL_VISIBLE ) ) + { + self.model = string_null; + } + self.think = movewall_think; + self.nextthink = time + 0.02; + self.ltime = time; +}; + +/*QUAKED rotate_object (0 .5 .8) ? +This defines an object to be rotated. Used as the target of func_rotate_door. +*/ +void() rotate_object = +{ + self.classname = "rotate_object"; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + setmodel (self,self.model); + setsize( self, self.mins, self.maxs ); + self.think = SUB_Null; +}; + +//************************************************ +// +// Rotating doors +// +//************************************************ + +void() rotate_door_think2 = + { + local float t; + + t = time - self.ltime; + self.ltime = time; + + // change to alternate textures + self.frame = 1 - self.frame; + + self.angles = self.dest; + + if ( self.state == ROT_STATE_OPENING ) + { + self.state = ROT_STATE_OPEN; + } + else + { + if ( self.spawnflags & ROT_ROTATE_DOOR_STAYOPEN ) + { + rotate_door_group_reversedirection(); + return; + } + self.state = ROT_STATE_CLOSED; + } + + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + self.think = SUB_Null; + + RotateTargetsFinal(); + }; + +void() rotate_door_think = + { + local float t; + + t = time - self.ltime; + self.ltime = time; + + if ( time < self.endtime ) + { + self.angles = self.angles + ( self.rotate * t ); + RotateTargets(); + } + else + { + self.angles = self.dest; + RotateTargets(); + self.think = rotate_door_think2; + } + + self.nextthink = time + 0.01; + }; + +void() rotate_door_reversedirection = + { + local vector start; + + // change to alternate textures + self.frame = 1 - self.frame; + + if ( self.state == ROT_STATE_CLOSING ) + { + start = self.dest1; + self.dest = self.dest2; + self.state = ROT_STATE_OPENING; + } + else + { + start = self.dest2; + self.dest = self.dest1; + self.state = ROT_STATE_CLOSING; + } + + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + + self.rotate = ( self.dest - start ) * ( 1 / self.speed ); + self.think = rotate_door_think; + self.nextthink = time + 0.02; + self.endtime = time + self.speed - ( self.endtime - time ); + self.ltime = time; + }; + +void() rotate_door_group_reversedirection = + { + local string name; + + // tell all associated rotaters to reverse direction + if ( self.group != "" ) + { + name = self.group; + self = find( world, group, name); + while( self ) + { + rotate_door_reversedirection(); + self = find( self, group, name); + } + } + else + { + rotate_door_reversedirection(); + } + }; + +void() rotate_door_use = + { + local vector start; + + if ( ( self.state != ROT_STATE_OPEN ) && ( self.state != ROT_STATE_CLOSED ) ) + return; + + if ( !self.cnt ) + { + self.cnt = 1; + LinkRotateTargets(); + } + + // change to alternate textures + self.frame = 1 - self.frame; + + if ( self.state == ROT_STATE_CLOSED ) + { + start = self.dest1; + self.dest = self.dest2; + self.state = ROT_STATE_OPENING; + } + else + { + start = self.dest2; + self.dest = self.dest1; + self.state = ROT_STATE_CLOSING; + } + + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + + self.rotate = ( self.dest - start ) * ( 1 / self.speed ); + self.think = rotate_door_think; + self.nextthink = time + 0.01; + self.endtime = time + self.speed; + self.ltime = time; + }; + + +/*QUAKED func_rotate_door (0 .5 .8) (-8 -8 -8) (8 8 8) STAYOPEN +Creates a door that rotates between two positions around a point of +rotation each time it's triggered. + +STAYOPEN tells the door to reopen after closing. This prevents a trigger- +once door from closing again when it's blocked. + +"dmg" specifies the damage to cause when blocked. Defaults to 2. Negative numbers indicate no damage. +"speed" specifies how the time it takes to rotate + +"sounds" +1) medieval (default) +2) metal +3) base +*/ + +void() func_rotate_door = // added slinet option - sounds 4 -- dumptruck_ds +{ + if (!self.target) + { + objerror( "rotate_door without target." ); + } + + self.dest1 = '0 0 0'; + self.dest2 = self.angles; + self.angles = self.dest1; + + // default to 2 seconds + if ( !self.speed ) + { + self.speed = 2; + } + + self.cnt = 0; + + if (!self.dmg) + self.dmg = 2; + else if ( self.dmg < 0 ) + { + self.dmg = 0; + } + + if (self.sounds == 0) + self.sounds = 1; + if (self.sounds == 1) + { + precache_sound ("doors/latch2.wav"); + precache_sound ("doors/winch2.wav"); + precache_sound ("doors/drclos4.wav"); + self.noise1 = "doors/latch2.wav"; + self.noise2 = "doors/winch2.wav"; + self.noise3 = "doors/drclos4.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/airdoor1.wav"); + precache_sound ("doors/airdoor2.wav"); + self.noise2 = "doors/airdoor1.wav"; + self.noise1 = "doors/airdoor2.wav"; + self.noise3 = "doors/airdoor2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/basesec1.wav"); + precache_sound ("doors/basesec2.wav"); + self.noise2 = "doors/basesec1.wav"; + self.noise1 = "doors/basesec2.wav"; + self.noise3 = "doors/basesec2.wav"; + } + if (self.sounds == 4) + { + precache_sound ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise2 = "misc/null.wav"; + self.noise1 = "misc/null.wav"; + self.noise3 = "misc/null.wav"; + } + + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + setmodel (self, self.model); + setorigin( self, self.origin ); + setsize( self, self.mins, self.maxs ); + self.state = ROT_STATE_CLOSED; + self.use = rotate_door_use; + self.think = SUB_Null; +}; \ No newline at end of file diff --git a/ssqc/scout.qc b/ssqc/scout.qc new file mode 100644 index 000000000..94b7c5a19 --- /dev/null +++ b/ssqc/scout.qc @@ -0,0 +1,1031 @@ +//======================================================== +// Functions for the SCOUT class and associated weaponry +//======================================================== + +void () CaltropTouch; +void () CaltropScatterThink; +void () ScatterCaltrops; +void () FlashGrenadeTouch; +void () FlashTimer; +void () FlashGrenadeExplode; +void () ConcussionGrenadeTouch; +float (string ps_short, string ps_setting, string ps_default) CF_GetSetting; +float IsFeigned(entity ent); + +void (entity inflictor, entity attacker, float bounce, + entity ignore) T_RadiusBounce; +void (entity inflictor, entity attacker, float bounce, + entity ignore) T_RadiusBounceBlast; +entity(entity scanner, float scanrange, float enemies, + float friends) T_RadiusScan; + +void OldConcussionGrenadeTimer(); +void FoConcussionGrenadeTimer(); + +void ConcThink() { + switch (GetConcMode()) { + case CONC_IDLESCALE: return OldConcussionGrenadeTimer(); + case CONC_AIM: return FoConcussionGrenadeTimer(); + } +} + +entity FindConcTimer(entity player) { + int count; + entity* timers = find_list(classname, "timer", EV_STRING, count); + + for (int i = 0; i < count; i++) + if (timers[i].owner == player && timers[i].think == ConcThink) + return timers[i]; + + return __NULL__; +} + +void RemoveConc(entity player, float remove_timer = TRUE) { + if (player.tfstate & TFSTATE_CONC == 0) + return; + + player.tfstate &= ~TFSTATE_CONC; + player.conc_state.mag = 0; + player.conc_finished = 0; + + if (GetConcMode() == CONC_IDLESCALE) + stuffcmd(player, "v_idlescale 0\nfov 90\n"); + + if (remove_timer) { + entity timer = FindConcTimer(player); + if (timer != __NULL__) + dremove(timer); + } +} + +void () CF_Scout_Dash = { + if (self.playerclass != PC_SCOUT) + return; + + // check if dash is allowed in rules and if cooldown has ended + if (!scoutdash || time < self.special_next) + return; + + self.special_next = time + 1; + makevectors(self.angles); + self.velocity = v_forward * 540; + self.velocity_z = 181; + FO_Sound(self, CHAN_BODY, "dash.wav", 1, ATTN_NORM); +} + +void () CanisterTouch = +{ + FO_Sound(self, CHAN_WEAPON, "weapons/tink1.wav", 1, 1); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () CaltropTouch = { + if ((other.classname != "player") || !(other.flags & FL_ONGROUND) || + other.deadflag) + return; + + if ((teamplay & TEAMPLAY_NOEXPLOSIVE) && (other != self.owner) + && (other.team_no == self.owner.team_no) && + (self.owner.team_no != 0)) + return; + + sprint(other, PRINT_HIGH, "Ow, ow, ow! Caltrops!\n"); + other.leg_damage = other.leg_damage + 2; + TeamFortress_SetSpeed(other); + deathmsg = DMSG_GREN_CALTROP; + T_Damage(other, self, self.owner, 10); + dremove(self); +}; + +void () CaltropScatterThink = { + self.nextthink = time + 0.2; + if (self.velocity == '0 0 0') { + if (self.flags & FL_ONGROUND) { + self.nextthink = time + 10 + random() * 5; + self.think = SUB_Remove; + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_TOSS; + self.touch = CaltropTouch; + self.angles = '90 90 90'; + FO_Sound(self, CHAN_AUTO, "weapons/tink1.wav", 1, ATTN_NORM); + setorigin(self, self.origin); + return; + } else { + self.nextthink = time + 10 + random() * 5; + self.think = SUB_Remove; + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_TOSS; + self.touch = CanisterTouch; + setorigin(self, self.origin); + return; + } + } + self.heat = self.heat + 1; + if (self.heat > 50) { + remove(self); + return; + } + traceline(self.movedir, self.origin, 1, self); + if (trace_fraction == 1) { + self.movedir = self.origin; + return; + } + self.velocity = self.velocity * -1; +}; + +void () ScatterCaltrops = { + local float num; + local entity e; + + num = 6; + while (num > 0) { + e = spawn(); + e.classname = "grenade"; + e.fpp.gren_type = GREN_CALTROP; + e.weapon = 10; + e.owner = self.owner; + e.team_no = self.owner.team_no; + FO_SetModel(e, "progs/caltrop.mdl"); + e.mins = '-4 -4 -8'; + e.maxs = '4 4 4'; + e.angles = '0 0 0'; + e.angles_x = random() * 360; + e.velocity_x = crandom() * 100; + e.velocity_y = crandom() * 100; + e.velocity_z = 200 + random() * 100; + e.avelocity_x = crandom() * 400; + e.avelocity_y = crandom() * 400; + e.avelocity_z = crandom() * 400; + setorigin(e, self.owner.origin); + e.movedir = e.origin; + e.heat = 0; + e.movetype = 10; + e.solid = 0; + e.nextthink = time + 0.2; + e.think = CaltropScatterThink; + num = num - 1; + } + dremove(self); +}; + +void () FlashGrenadeTouch = { + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void StuffFlash(entity te, float amt) +{ + string st = ftos(amt); + string cmd = strcat("v_cshift ", st, " ", st); + cmd = strcat(cmd, " ", st, " ", st, "\n"); + stuffcmd(te, cmd); +} + +void () FlashTimer = { + local entity te; + + te = self.owner; + te.FlashTime = te.FlashTime - 0.6; + if (te.FlashTime < 5) { + te.FlashTime = 1; + stuffcmd(te, "v_cshift; wait; bf\n"); + remove(self); + SetDimensions(TRUE); + return; + } + + StuffFlash(te, (te.FlashTime*10)); + + self.nextthink = time + 0.6; +}; + +float FO_CalcFlash(entity te) +{ + float timeleft = te.FlashTime; + float flashAmtMax = 240; + float flashAmtMin = 100; + float flashAmtMaxMult = stof(infokey(te, "maxflash")); + float flashAmtMinMult = stof(infokey(te, "minflash")); + float flashAmt; + + + flashAmt = (flashAmtMaxMult > 0) ? flashAmtMax * flashAmtMaxMult : flashAmtMax; + flashAmtMin = (flashAmtMinMult > 0) ? flashAmtMin * flashAmtMinMult : flashAmtMin; + + float perc = timeleft / fo_flashtime; + + flashAmt = flashAmt * perc; + flashAmt = flashAmt < flashAmtMin ? flashAmtMin : flashAmt; + return flashAmt; +} + +void FO_FlashTimer() +{ + local entity te; + + te = self.owner; + te.FlashTime = te.FlashTime - .1; + if (te.FlashTime <= 0) { + sprint(te, PRINT_HIGH, "Flash has worn off\n"); + stuffcmd(te, "v_cshift; wait; bf\n"); + remove(self); + self = te; + SetDimensions(TRUE); + return; + } + + float flashamount = FO_CalcFlash(te); + StuffFlash(te, flashamount); + + self.nextthink = time + .1; +} + +void SetFlashDimension(entity te) +{ + FO_FlashDimension = ((FO_FlashDimension - DMN_FLASH + 1) % 7) + DMN_FLASH; + + float playerdimension = 1< 0) { + entity ft; + ft = find(world, classname, "flashtimer"); + float found; + found = FALSE; + + while (ft) + { + if (ft.owner == te) + { + found = TRUE; + ft.nextthink = time + .1; + } + ft = find(ft, classname, "flashtimer"); + } + + if (!found) + { + newmis = spawn(); + newmis.classname = "timer"; + newmis.netname = "flashtimer"; + newmis.team_no = self.owner.team_no; + newmis.owner = te; + newmis.think = FO_FlashTimer; + newmis.nextthink = time + .1; + } + float ftime = fo_flashtime; + ftime = ftime * (frac + frac * 0.5); // fall off is awful, let's half it + te.FlashTime = ftime; + + float flashamount = FO_CalcFlash(te); + + StuffFlash(te, flashamount); + + SetFlashDimension(te); + } + } + } + te = te.chain; + } +} + +void () FlashGrenadeExplode = { + local entity te; + + self.effects = self.effects | EF_BRIGHTLIGHT; + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_TAREXPLOSION); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PHS); + + if (fo_flash) + { + FO_FlashExplode(); + } + else + { + te = findradius(self.origin, 300); + while (te) { + if (te.classname == "player") { + traceline(self.origin, te.origin, 1, self); + if (trace_fraction == 1) { + // LogEventAffliction(self.owner, te, TFSTATE_FLASHED); + if (vlen(self.origin - te.origin) <= 200) { + deathmsg = DMSG_GREN_FLASH; + TF_T_Damage(te, self, self.owner, 60, 2, 16 | 4); + } + if (te.health > 0) { + entity ft; + ft = find(world, classname, "flashtimer"); + float found; + found = FALSE; + + while (ft) + { + if (ft.owner == te) + { + found = TRUE; + ft.nextthink = time + 1; + } + ft = find(ft, classname, "flashtimer"); + } + + if (!found) + { + newmis = spawn(); + newmis.classname = "timer"; + newmis.netname = "flashtimer"; + newmis.team_no = self.owner.team_no; + newmis.owner = te; + newmis.think = FlashTimer; + newmis.nextthink = time + 1; + } + + if (te != self.owner) + te.FlashTime = 16; + else + te.FlashTime = 24; + + StuffFlash(te, (te.FlashTime * 10)); + + SetFlashDimension(te); + } + } + } + te = te.chain; + } + } + + dremove(self); +}; + +void () ConcussionGrenadeTouch = { + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () BlastGrenadeTouch = { + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () BlastGrenadeExplode = { + T_RadiusBounceBlast(self, self.owner, 240, world); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_EXPLOSION); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PHS); + dremove(self); +}; + +void () OldConcussionGrenadeTimer = { + local string st; + + if (self.owner.invincible_finished > time) { + stuffcmd(self.owner, "v_idlescale 0; wait; fov 90\n"); + dremove(self); + return; + } + newmis = spawn(); + FO_SetModel(newmis, "progs/s_bubble.spr"); + setorigin(newmis, self.owner.origin); + newmis.movetype = 8; + newmis.solid = 0; + newmis.velocity = '0 0 15'; + newmis.nextthink = time + 0.5; + newmis.think = bubble_bob; + newmis.touch = bubble_remove; + newmis.classname = "bubble"; + newmis.frame = 0; + newmis.cnt = 0; + setsize(newmis, '-8 -8 -8', '8 8 8'); + + self.health = self.health - 20; + if (self.owner.playerclass == 5) + self.health = self.health - 20; + if (self.health < 0) + self.health = 0; + + self.nextthink = time + 5; + stuffcmd(self.owner, "v_iroll_cycle 0.5\n"); + stuffcmd(self.owner, "v_ipitch_cycle 1\n"); + stuffcmd(self.owner, "v_iyaw_cycle 2\n"); + + st = ftos(self.health); + stuffcmd(self.owner, "v_idlescale "); + stuffcmd(self.owner, st); + stuffcmd(self.owner, "\n"); + + st = ftos(90 + self.health / 2); + stuffcmd(self.owner, "fov "); + stuffcmd(self.owner, st); + stuffcmd(self.owner, "\n"); + if (self.health == 0) { + RemoveConc(self.owner, FALSE); + dremove(self); + } +}; + +void SpawnBubble(vector origin) { + newmis = spawn(); + FO_SetModel(newmis, "progs/s_bubble.spr"); + setorigin(newmis, origin); + newmis.movetype = MOVETYPE_NOCLIP; + newmis.solid = SOLID_NOT; + newmis.velocity = '0 0 15'; + newmis.nextthink = time + 0.5; + newmis.think = bubble_bob; + newmis.touch = bubble_remove; + newmis.classname = "bubble"; + newmis.frame = 0; + newmis.cnt = 0; + setsize(newmis, '-8 -8 -8', '8 8 8'); +} + +void FoConcussionGrenadeTimer() { + const float bubble_int = 1; + float finished = self.owner.conc_finished; + + self.nextthink = min(time + bubble_int, finished); + + if (time < finished) { + SpawnBubble(self.owner.origin); + } else { + sprint(self.owner, PRINT_HIGH, "Your head feels better now\n"); + RemoveConc(self.owner, FALSE); + dremove(self); + } +} + +void () ScannerSwitch = { + local entity te; + + if (self.ScannerOn != 1) { + te = spawn(); + te.nextthink = time + 2; + te.think = TeamFortress_Scan; + te.owner = self; + te.classname = "timer"; + te.netname = "scanner"; + sprint(self, PRINT_HIGH, "Scanner on\n"); + self.ScannerOn = 1; + + if (!(self.tf_items_flags & NIT_SCANNER_ENEMY)) + self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_ENEMY; + } else { + te = find(world, netname, "scanner"); + while (te) { + if (te.owner == self) + dremove(te); + + te = find(te, netname, "scanner"); + } + sprint(self, PRINT_HIGH, "Scanner off\n"); + self.ScannerOn = 0; + } + + Status_Refresh(self); +}; + +void () TeamFortress_Scan = { + local entity list; + local float scancost; + local float scanrange; + local float scen; + local float scfr; + local float num; + local vector scanvec; + local vector lightningvec; + local float enemy_detected; + local float any_detected; + list = world; + + scanrange = 100; + self.owner.impulse = 0; + self.owner.last_impulse = 0; + if (self.owner.classname == "player") { + if (!(self.owner.tf_items & 1)) { + return; + } + scancost = 2; + if (self.owner.ammo_cells <= 0) { + sprint(self.owner, PRINT_HIGH, + "Not enough cells to run scanner\n"); + self.owner.ammo_cells = 0; + self.owner.ScannerOn = 0; + dremove(self); + return; + } + if (scancost > self.owner.ammo_cells) { + scanrange = self.owner.ammo_cells * 20; + scancost = self.owner.ammo_cells; + } + scen = 0; + scfr = 0; + if (self.owner.tf_items_flags & 1) { + scen = 1; + } + if (self.owner.tf_items_flags & 2) { + scfr = 1; + } + if ((scen == 0) && (scfr == 0)) { + sprint(self.owner, PRINT_HIGH, "No target specified\n"); + self.owner.ScannerOn = 0; + dremove(self); + return; + } + self.owner.ammo_cells = self.owner.ammo_cells - 2; + if (self.owner.ammo_cells < 0) { + self.owner.ammo_cells = 0; + } + scanrange = scanrange * 25; + list = T_RadiusScan(self.owner, scanrange, scen, scfr); + } + scen = 0; + scfr = 0; + makevectors(self.owner.v_angle); + if (list != world) { + any_detected = 1; + if ((((list.team_no > 0) && (self.owner.team_no > 0)) && + (list.team_no == self.owner.team_no)) && + ((list.classname == "player") || + (list.classname == "building_sentrygun"))) { + scfr = scfr + 1; + enemy_detected = 0; + } else { + if ((((list.goal_no > 0) && (self.owner.team_no > 0)) && + (list.goal_no == self.owner.team_no)) && + (list.classname == "item_tfgoal")) { + scfr = scfr + 1; + enemy_detected = 0; + } else { + scen = scen + 1; + enemy_detected = 1; + } + } + if (any_detected) { + lightningvec = normalize((list.origin - self.owner.origin)); + lightningvec = + lightningvec * (vlen((list.origin - self.owner.origin)) / + 5); + lightningvec = lightningvec + self.owner.origin; + msg_entity = self.owner; + + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_LIGHTNING1); + WriteEntity(MSG_ONE, self.owner); + WriteCoord(MSG_ONE, self.owner.origin_x); + WriteCoord(MSG_ONE, self.owner.origin_y); + WriteCoord(MSG_ONE, self.owner.origin_z + 8); + WriteCoord(MSG_ONE, lightningvec_x); + WriteCoord(MSG_ONE, lightningvec_y); + WriteCoord(MSG_ONE, lightningvec_z + 8); + + if (scoutscanspike) { + scanvec = normalize(list.origin - self.owner.origin); + newmis = spawn(); + newmis.owner = self.owner; + newmis.enemy = self.owner; + newmis.movetype = MOVETYPE_NOCLIP; + newmis.solid = SOLID_NOT; + + FO_SetModel(newmis, "progs/spike.mdl"); + newmis.traileffectnum = particleeffectnum("spike_scan"); + + setsize(newmis, '0 0 0', '0 0 0'); + setorigin(newmis, self.owner.origin + v_forward * 8); + + switch (self.owner.team_no) + { + case TEAM_BLUE: + newmis.dimension_seen = DMN_TEAMBLUE; + break; + case TEAM_RED: + newmis.dimension_seen = DMN_TEAMRED; + break; + case TEAM_YELL: + newmis.dimension_seen = DMN_TEAMYELL; + break; + case TEAM_GREN: + newmis.dimension_seen = DMN_TEAMGREN; + break; + default: + newmis.dimension_seen = DMN_NOFLASH; + break; + } + + newmis.velocity = scanvec * 2000; + newmis.angles = vectoangles(newmis.velocity); + newmis.oldorigin = newmis.velocity; + + //Remove the scan spike when it reaches list.origin + newmis.think = SUB_Remove; + num = vlen(list.origin - self.owner.origin); + num = num / 1000; + newmis.nextthink = time + num; + + newmis.touch = SUB_Null; + newmis.classname = "scanspike"; + } + + if (self.owner.tf_items_flags & 4) { + stuffcmd(self.owner, "play misc/basekey.wav\n"); + } + num = vlen(list.origin - self.owner.origin); + num = num / 10; + num = num / 3; + num = rint(num); + self.health = num; + if (list.classname == "player") { + if ((list.playerclass == 8) && + (list.team_no != self.owner.team_no)) { + if (list.undercover_skin != 0) { + self.playerclass = list.undercover_skin; + } else { + self.playerclass = list.playerclass; + } + if (list.undercover_team != 0) { + self.team_no = list.undercover_team; + } else { + self.team_no = list.team_no; + } + } else { + self.playerclass = list.playerclass; + self.team_no = list.team_no; + } + } else { + if (list.classname == "building_sentrygun") { + self.playerclass = 13; + self.team_no = list.team_no; + } else { + if (list.classname == "item_tfgoal") { + self.playerclass = 14; + self.team_no = list.goal_no; + } + } + } + Status_Refresh(self.owner); + } + } + if ((scen == 0) && (scfr == 0)) { + self.health = 0; + Status_Refresh(self.owner); + self.nextthink = time + 2; + return; + } + self.nextthink = time + 2; + return; +}; + +void CussSpeedBump() +{ + entity p = self.owner; + self.nextthink = time + .3; + + if (!self.cnt) { + makevectors(p.v_angle); + + // if medic within x units of ground, slow + traceline(p.origin, p.origin - 100 * v_up, MOVE_NOMONSTERS, p); + + if (trace_fraction < 1) // Found ground + self.cnt = TRUE; + } else { + self.cnt2++; + if (vlen(p.velocity) > 1000) + p.velocity *= 0.9; + } + + if (self.cnt2 > 9) + dremove(self); +} + +void (entity inflictor, entity attacker, float bounce, + entity ignore) T_RadiusBounceBlast = { + local float points; + local entity head; + local vector org; + + head = findradius(inflictor.origin, bounce + 40); + while (head) { + if (head != ignore) { + if (head.takedamage) { + org = head.origin + (head.mins + head.maxs) * 0.5; + points = 0.5 * vlen(org - inflictor.origin); + if (points < 0) + points = 0; + points = bounce - points; + + if ((head.classname != "building_dispenser") + && (head.classname != "building_sentrygun") + && (head.classname != "building_sentrygun_base") + && (points > 0)) { + head.velocity = org - inflictor.origin; + head.velocity = head.velocity * (points / 20) * blastgren_velocity_multiplier; + if (head.classname != "player") { + if (head.flags & FL_ONGROUND) + head.flags = head.flags - FL_ONGROUND; + } + } + } + } + head = head.chain; + } +}; + + +struct ClassConc { + float amp_mult; + float duration_mult; +}; + +static ClassConc class_mod[] = { + { 1.0, 1.0 }, // observer + { 1.5, 1.0 }, // scout + { 1.0, 1.0 }, // sniper + { 1.0, 1.0 }, // soldier + { 1.0, 1.0 }, // demoman + { 1.0, 0.5 }, // medic + { 0.75, 1.0 }, // hwguy + { 1.0, 1.0 }, // pyro + { 1.0, 1.0 }, // spy + { 1.0, 1.0 }, // engineer + { 1.0, 1.0 }, // randompc + { 1.0, 1.0 }, // civilian +}; + +static ClassConc get_class_mod(float playerclass) { + string short_a = sprintf("foc_a%d", playerclass); + string long_a = sprintf("fo_concuss_amp%d", playerclass); + string short_d = sprintf("foc_d%d", playerclass); + string long_d = sprintf("fo_concuss_dur%d", playerclass); + + ClassConc r = class_mod[playerclass]; + + float amp_m = CF_GetSetting(short_a, long_a, "off"); + if (amp_m) + r.amp_mult = amp_m; + + float dur = CF_GetSetting(short_d, long_d, "off"); + if (dur) + r.duration_mult = dur; + + return r; +} + +static void UpdateClientConcussion(entity player, float duration) { + float base_amp = CF_GetSetting("foca", "foc_amp", "110"); + + // Once non debug, these should be once per map. + ClassConc r = get_class_mod(player.playerclass); + + // networked via prediction ent + player.conc_amp = base_amp * r.amp_mult; + player.conc_finished = time + duration * r.duration_mult; +} + +void (entity inflictor, entity attacker, float bounce, + entity ignore) T_RadiusBounce = { + local float actual_cuss_time = cussgrentime; + local float distance; + local float points; + local entity head; + local entity te; + local vector org; + + head = findradius(inflictor.origin, bounce + 40); + while (head) { + if (head != ignore) { + if (head.takedamage) { + org = head.origin + (head.mins + head.maxs) * 0.5; + distance = vlen(org - inflictor.origin); + points = 0.5 * distance; + if (points < 0) + points = 0; + points = bounce - points; + + if (distance_based_cuss_duration) { + // Actual cuss time based on distance from max explosion radius + local float fractional_distance = (1 - (distance / (bounce + 40))); + if (fractional_distance <= 1 && fractional_distance > 0.80) { + actual_cuss_time = cussgrentime; + } else if (fractional_distance <= 0.8 && fractional_distance > 0.2) { + actual_cuss_time = rint((1 - fractional_distance) * cussgrentime); + } else { + actual_cuss_time = rint(actual_cuss_time * 0.05); + } + } + + if ((head.classname != "building_dispenser") + && (head.classname != "building_sentrygun") + && (head.classname != "building_sentrygun_base") + && (points > 0)) { + head.velocity = org - inflictor.origin; + head.velocity = head.velocity * (points / 20); + Predict_AddFilterEnt(head, inflictor); + + NB_ConcCapAction(head, head.playerclass, &head.tfstate, + time, &head.conc_cap_time, kLaunch); + + if (head.classname != "player") { + if (head.flags & FL_ONGROUND) + head.flags = head.flags - FL_ONGROUND; + } else { + if (head.playerclass == PC_MEDIC) + { + if ((NewBalanceActive() && NB_UseNewConc()) || NB_NoCap()) { + head = head.chain; + continue; + } + + if (medicnocuss && (medic_type != MEDIC_TYPE_BLAST)) + { + entity speedbump; + speedbump = spawn(); + speedbump.think = CussSpeedBump; + speedbump.nextthink = time + 1; + speedbump.owner = head; + + head = head.chain; + continue; + } + } + + if (distance_based_cuss_duration + && ((head.playerclass == PC_MEDIC) || (head.playerclass == PC_SCOUT))) { + entity speedbump; + speedbump = spawn(); + speedbump.think = CussSpeedBump; + speedbump.nextthink = time + 1; + speedbump.owner = head; + } + + head.tfstate |= TFSTATE_CONC; + + switch (GetConcMode()) { + case CONC_AIM: + UpdateClientConcussion(head, actual_cuss_time); + break; + case CONC_CLASSIC: + // Activate and/or extend + head.conc_state.next = time + CONC_P; + head.conc_state.mag = ceil(1/CONC_P) * actual_cuss_time; + break; + } + + if (GetConcMode() != CONC_CLASSIC) { + entity te = FindConcTimer(head); + if (te != world) { + if (GetConcMode() == CONC_IDLESCALE) { + stuffcmd(head, "v_idlescale 100\n"); + stuffcmd(head, "fov 130\n"); + te.health = 100; + te.nextthink = time + 5; + } else { + te.health = 40 * cussgrentime; + te.nextthink = time + 0.25; + } + } else { + // LogEventAffliction(attacker, head, TFSTATE_CONCUSSED); + if (GetConcMode() == CONC_IDLESCALE) { + stuffcmd(head, "v_idlescale 100\n"); + stuffcmd(head, "fov 130\n"); + stuffcmd(head, "bf\n"); + te = spawn(); + te.nextthink = time + 5; + te.think = ConcThink; + te.team_no = attacker.team_no; + te.classname = "timer"; + te.owner = head; + te.health = 100; + } else { + te = spawn(); + te.nextthink = time + 0.25; + te.think = ConcThink; + te.team_no = attacker.team_no; + te.classname = "timer"; + te.owner = head; + te.health = 40 * actual_cuss_time; + } + } + } + } + } + } + } + head = head.chain; + } +}; + +entity(entity scanner, float scanrange, float enemies, + float friends) T_RadiusScan = +{ + local entity head; + local float rangedist; + + rangedist = 0; + head = world; + while (rangedist <= scanrange) { + if (rangedist <= 0) { + rangedist = 1; + } + head = findradius(scanner.origin, rangedist); + while (head) { + if (head != scanner) { + if (((head.takedamage != 0) && (head.health > 0)) || + (head.classname == "item_tfgoal")) { + if (((head.classname == "player") + || (head.classname == "building_sentrygun")) + && (friends || enemies)) { + if (teamplay) { + if (((friends != 0) && (head.team_no > 0)) && + (scanner.team_no > 0)) { + if ((head.playerclass == 8) && + (head.team_no != scanner.team_no)) { + if (!IsFeigned(head) && (invis_only != 1)) { + if (head.undercover_team == + scanner.team_no) { + return (head); + } + } + } else if (head.team_no == scanner.team_no) { + return (head); + } + } + if (((enemies != 0) && (head.team_no > 0)) && + (scanner.team_no > 0)) { + if ((head.playerclass == 8) && + (head.team_no != scanner.team_no)) { + if (!IsFeigned(head) && (invis_only != 1)) { + if (head.undercover_team != + scanner.team_no) { + return (head); + } + } + } else if (head.team_no != scanner.team_no) { + return (head); + } + } + } else { + return (head); + } + } else if ((head.classname == "item_tfgoal") && + (friends || enemies)) { + if (teamplay) { + if ((friends != 0) && (head.goal_no > 0) + && (scanner.team_no > 0) && + (head.goal_no == scanner.team_no)) { + return (head); + } + if ((enemies != 0) && (head.team_no > 0) + && (scanner.team_no > 0) && + (head.goal_no != scanner.team_no)) { + return (head); + } + } + } + } + } + head = head.chain; + } + rangedist = rangedist + 100; + } + return (world); +}; diff --git a/sentry.qc b/ssqc/sentry.qc similarity index 68% rename from sentry.qc rename to ssqc/sentry.qc index cf1797bdc..c1498741e 100644 --- a/sentry.qc +++ b/ssqc/sentry.qc @@ -6,10 +6,9 @@ void (entity bld) CheckBelowBuilding; void (entity gunhead) CheckSentry; void () Sentry_Rotate; -float () Sentry_FindTarget; void () Sentry_FoundTarget; void () Sentry_HuntTarget; -void (entity, float) Sentry_Pain; +void (entity e, float f) Sentry_Pain; void () Sentry_Die; float () Sentry_Fire; void () Sentry_MuzzleFlash; @@ -29,7 +28,7 @@ void () lvl1_sentry_stand =[0, lvl1_sentry_stand] { void () lvl1_sentry_atk1 =[1, lvl1_sentry_atk3] { ai_face(); - if ((self.enemy == world) || self.enemy.is_feigning || + if ((self.enemy == world) || IsFeigned(self.enemy) || (self.enemy.health <= 0) || !visible(self.enemy) || (self.enemy.has_disconnected == 1)) @@ -58,13 +57,11 @@ void () lvl2_sentry_stand =[3, lvl2_sentry_stand] { void () lvl2_sentry_atk1 =[4, lvl2_sentry_atk2] { ai_face(); - if (((((self.enemy == world) || self.enemy.is_feigning) || + if (((((self.enemy == world) || IsFeigned(self.enemy)) || (self.enemy.health <= 0)) || !visible(self.enemy)) || - (self.enemy.has_disconnected == 1)) - + (self.enemy.has_disconnected == 1)) lvl2_sentry_stand(); - - else if (self.ammo_shells <= 0) +else if (self.ammo_shells <= 0) lvl2_sentry_stand(); else if (Sentry_Fire() == 0) lvl2_sentry_atk3(); @@ -89,7 +86,7 @@ void () lvl3_sentry_stand =[6, lvl3_sentry_stand] { void () lvl3_sentry_atk1 =[7, lvl3_sentry_atk2] { ai_face(); - if (((((self.enemy == world) || self.enemy.is_feigning) || + if (((((self.enemy == world) || IsFeigned(self.enemy)) || (self.enemy.health <= 0)) || !visible(self.enemy)) || (self.enemy.has_disconnected == 1)) @@ -112,38 +109,6 @@ void () lvl3_sentry_atk3 =[6, lvl3_sentry_atk1] { }; //=========================== -void () Sentry_Rotate = { - local float ay; - - self.effects = self.effects - (self.effects & EF_DIMLIGHT); - CheckSentry(self); - if (Sentry_FindTarget()) - return; - - if (self.heat == 0) { - self.ideal_yaw = self.waitmin; - ChangeYaw(); - ay = anglemod(self.angles_y); - ay = rint(ay); - if (ay == rint(self.waitmin)) { - CheckBelowBuilding(self.trigger_field); - self.heat = 1; - if (random() < 0.1) - sound(self, CHAN_ITEM, "weapons/turridle.wav", 1, - ATTN_NORM); - } - } else { - self.ideal_yaw = self.waitmax; - ChangeYaw(); - ay = anglemod(self.angles_y); - ay = rint(ay); - if (ay == rint(self.waitmax)) { - CheckBelowBuilding(self.trigger_field); - self.heat = 0; - } - } -}; - float () Sentry_FindTarget = { local entity client; local float r; @@ -171,7 +136,7 @@ float () Sentry_FindTarget = { (self.team_no != 0)) gotone = 0; } - if (client.is_feigning) + if (IsFeigned(client)) gotone = 0; if ((client.flags & FL_NOTARGET) || (client.items & IT_INVISIBILITY)) @@ -202,10 +167,155 @@ float () Sentry_FindTarget = { return (1); }; +float (entity p) FO_Sentry_ValidTarget = { + float valid = TRUE; + + if (!p) + return FALSE; + + if ((p == self.real_owner) || !p.takedamage) + valid = FALSE; + if (p.has_disconnected == 1) + valid = FALSE; + + if (teamplay) { + if ((p.team_no == self.team_no) && (self.team_no != 0)) + valid = FALSE; + if ((p.undercover_team == self.team_no) && + (self.team_no != 0)) + valid = FALSE; + } + if (IsFeigned(p)) + valid = FALSE; + if ((p.flags & FL_NOTARGET) || + (p.items & IT_INVISIBILITY)) + valid = FALSE; + if (!visible(p)) + valid = FALSE; + + float r = range(p); + if (r == RANGE_FAR) + valid = FALSE; + else if ((r == RANGE_MID) && !infront(p)) + valid = FALSE; + + return valid; +}; + + +float () FO_Sentry_FindTarget = { + + // check for current valid target + if (FO_Sentry_ValidTarget(self.enemy)) + { + if (self.cnt <= time) + { + Sentry_FoundTarget(); + return TRUE; + } + return FALSE; + } + + entity p = find(world, classname, "player"); + float foundTarg = FALSE; + float dist = -1; + entity targ = world; + + while (p) + { + // do checks + if (FO_Sentry_ValidTarget(p)) + { + // do vlen check + if (dist == -1) + { + dist = vlen(self.origin - p.origin); + targ = p; + } + else if (dist > vlen(self.origin - p.origin)) + { + dist = vlen(self.origin - p.origin); + targ = p; + } + } + + p = find(p, classname, "player"); + } + + if (!targ) + { + self.enemy = world; + return FALSE; + } + + self.enemy = targ; + if (self.enemy.classname != "player") { + self.enemy = self.enemy.enemy; + if (self.enemy.classname != "player") { + self.enemy = world; + return FALSE; + } + } + + vector spot1 = self.origin + self.view_ofs; + vector spot2 = self.enemy.origin + self.enemy.view_ofs; + + float r = vlen(spot1 - spot2); + float delay = 0.25; + if (r >= 500) // RANGE_MID, 500 is halfway of sentry targeting distance + { + delay = 0.45; + } + self.cnt = time + delay; // delay + + return TRUE; +}; + +void () Sentry_Rotate = { + local float ay; + + self.effects = self.effects - (self.effects & EF_DIMLIGHT); + CheckSentry(self); + + if (fo_sentry_targeting) + { + if (FO_Sentry_FindTarget()) + return; + } + else + { + if (Sentry_FindTarget()) + return; + } + + if (self.heat == 0) { + self.ideal_yaw = self.waitmin; + ChangeYaw(); + ay = anglemod(self.angles_y); + ay = rint(ay); + if (ay == rint(self.waitmin)) { + CheckBelowBuilding(self.trigger_field); + self.heat = 1; + if (random() < 0.1) + FO_Sound(self, CHAN_ITEM, "weapons/turridle.wav", 1, + ATTN_NORM); + } + } else { + self.ideal_yaw = self.waitmax; + ChangeYaw(); + ay = anglemod(self.angles_y); + ay = rint(ay); + if (ay == rint(self.waitmax)) { + CheckBelowBuilding(self.trigger_field); + self.heat = 0; + } + } +}; + void () Sentry_FoundTarget = { if ((self.ammo_shells > 0) || ((self.ammo_rockets > 0) && (self.weapon == 3))) - sound(self, CHAN_VOICE, "weapons/turrspot.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, "weapons/turrspot.wav", 1, ATTN_NORM); Sentry_HuntTarget(); @@ -214,6 +324,9 @@ void () Sentry_FoundTarget = { }; void () Sentry_HuntTarget = { + if (self.tfstate & TFSTATE_RESPAWN_READY) + return; + self.goalentity = self.enemy; if (self.weapon == 1) @@ -238,14 +351,14 @@ void () Sentry_Explode = { ThrowGib("progs/tgib3.mdl", -70); if (self.real_owner.has_disconnected != 1) { - deathmsg = 38; + deathmsg = DMSG_SENTRYGUN_EXPLODE; T_RadiusDamage(self, self.real_owner, 75 + self.ammo_rockets * 8, self); } if (self.classname == "building_sentrygun_base") { if (self.oldenemy) dremove(self.oldenemy); - } else + } else if (self.trigger_field != world) dremove(self.trigger_field); WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); @@ -259,30 +372,39 @@ void () Sentry_Explode = { }; void () Sentry_Die = { - sprint(self.real_owner, PRINT_HIGH, - "Your sentry gun was destroyed\n"); - self.real_owner.has_sentry = 0; + sprint(self.real_owner, PRINT_HIGH, "Your sentry gun was destroyed\n"); + stuffcmd(self.real_owner, "play misc/chink.wav\n"); + local entity ro = self.real_owner; + ro.has_sentry = 0; + + self.tfstate = self.tfstate | TFSTATE_RESPAWN_READY; self.think = Sentry_Explode; self.nextthink = time + 0.1; + if (ro.is_building) { + self = ro; + TeamFortress_EngineerBuildStop(); + } }; float () Sentry_Fire = { - local vector dir; + local vector dir, org; self.effects = self.effects - (self.effects & EF_DIMLIGHT); + org = self.origin; + org_z = org_z + 20; + dir = self.enemy.origin - org; - dir = self.enemy.origin - self.origin; if (((self.ideal_yaw - anglemod(self.angles_y)) < -10) || ((self.ideal_yaw - anglemod(self.angles_y)) > 10)) - return (0); + return 0; - if (self.enemy.is_feigning == 1) - return (0); + if (IsFeigned(self.enemy)) + return 0; if ((self.weapon == 3) && (self.ammo_rockets > 0) && (self.super_damage_finished < time)) { Sentry_MuzzleFlash(); - sound(self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); newmis = spawn(); newmis.owner = self; @@ -297,7 +419,7 @@ float () Sentry_Fire = { newmis.nextthink = time + 5; newmis.think = SUB_Remove; - setmodel(newmis, "progs/missile.mdl"); + FO_SetModel(newmis, "progs/missile.mdl"); setsize(newmis, '0 0 0', '0 0 0'); setorigin(newmis, self.origin + v_forward * 8 + '0 0 16'); @@ -314,7 +436,7 @@ float () Sentry_Fire = { return (0); } Sentry_MuzzleFlash(); - sound(self, CHAN_WEAPON, "weapons/sniper.wav", 1, ATTN_NORM); + FO_Sound(self, CHAN_WEAPON, "weapons/sniper.wav", 1, ATTN_NORM); deathmsg = DMSG_SENTRYGUN_BULLET; diff --git a/ssqc/sniper.qc b/ssqc/sniper.qc new file mode 100644 index 000000000..c1a3dbf7d --- /dev/null +++ b/ssqc/sniper.qc @@ -0,0 +1,64 @@ +//======================================================== +// Functions for the SNIPER class and associated weaponry +//======================================================== + +void () SniperSight_Update = { + local vector org; + + if (!(self.owner.tfstate & TFSTATE_AIMING) || + (FO_PlayerCurrentWeapon(self.owner) != WEAP_SNIPER_RIFLE)) { + + self.owner.tfstate &= ~TFSTATE_AIMING; + self.owner.heat = 0; + dremove(self); + return; + } + + makevectors(self.owner.v_angle); + org = self.owner.origin + v_forward * 10; + org_z = self.owner.absmin_z + self.owner.size_z * 0.7; + + traceline(org, org + v_forward * 9192, FALSE, self); + + if (trace_fraction == 1) { + setorigin(self, self.owner.origin); + return; + } + self.angles = vectoangles(v_forward); + setorigin(self, trace_endpos); + self.nextthink = time + 0.1; +}; + +static float sight_send_filter() { + if (other == self.owner) + return FALSE; + + return TRUE; +} + +void () SniperSight_Create = { + local entity sight; + + if (self.has_disconnected == TRUE || (self.tfstate & TFSTATE_RELOADING)) + return; + + self.tfstate = self.tfstate | TFSTATE_AIMING; + + sight = spawn(); + sight.owner = self; + sight.movetype = MOVETYPE_NOCLIP; + sight.solid = SOLID_NOT; + + FO_SetModel(sight, "progs/sight.spr"); + + sight.classname = "timer"; + + setorigin(sight, self.origin); + + sight.think = SniperSight_Update; + sight.nextthink = time + 0.05; +#pragma warning disable F326 + if (ClientPred_Enabled(self, CSQC_SNIPER_SIGHT)) + sight.customizeentityforclient = sight_send_filter; +#pragma warning enable F326 +}; diff --git a/spect.qc b/ssqc/spect.qc similarity index 51% rename from spect.qc rename to ssqc/spect.qc index 8f455eb31..d6e72f4b2 100644 --- a/spect.qc +++ b/ssqc/spect.qc @@ -12,38 +12,32 @@ void () SpectatorDisconnect; void () SpectatorImpulseCommand; void () SpectatorThink; +void () Admin_Aliases; void () SpectatorConnect = { + SetDimensions(TRUE); + local string st; - local string st2; self.playerclass = PC_UNDEFINED; self.classname = "observer"; self.flags = 8; - st2 = infokey(world, "apw"); - if (st2 == string_null) - st2 = infokey(world, "adminpwd"); + if (infokey(self,"*login")) + self.login = infokey(self,"*login"); + if (infokey(self,"*admin")) + self.is_admin = stof(infokey(self, "*admin")); st = infokey(self, "apw"); if (st == string_null) st = infokey(self, "adminpwd"); - - if (((st2 != string_null) && (st != string_null)) && (st == st2)) { - self.is_admin = TRUE; - stuffcmd(self, "setinfo apw \""); - stuffcmd(self, "\"\n"); - stuffcmd(self, "setinfo adminpwd \""); - stuffcmd(self, "\"\n"); - TeamFortress_Alias("countplayers", 192, 0); - TeamFortress_Alias("deal", 189, 0); - TeamFortress_Alias("kick", 190, 0); - TeamFortress_Alias("ban", 191, 0); - TeamFortress_Alias("next", 195, 0); - TeamFortress_Alias("ceasefire", 193, 0); - TeamFortress_Alias("listips", 198, 0); - } else - self.is_admin = FALSE; + if (st) { + Admin_Check(st); + if (self.is_admin) + Admin_Aliases(); + else + self.is_admin = FALSE; + } st = infokey(self, "em"); if (st == string_null) @@ -57,9 +51,21 @@ void () SpectatorConnect = { stuffcmd(self, ".cfg\n"); } self.motd = 0; + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { + InitAllStatuses(self); + UpdateClientMOTD(self); + UpdateClientTeamScores(self); + UpdateClientPrematch(self, !cb_prematch); + UpdateClient_VoteMap_AddAll(self); + } + + TeamFortress_StartTimers(); + TeamFortress_RemovePracticeSpawn(self); + TeamFortress_SetSpeed(self); }; void () SpectatorDisconnect = { + RemoveAutoIdTimer(); }; void () SpectatorImpulseCommand = { @@ -90,35 +96,71 @@ void () SpectatorImpulseCommand = { TeamFortress_TeamShowScores(0); + if(self.menu_input == Vote_Menu_Map_Input && self.impulse >= 1 && self.impulse <= 10) { + Menu_Input(self.impulse); + } + if (!self.is_admin) { self.impulse = 0; return; } - if (self.impulse == 193) + if (self.impulse == TF_ADMIN_CEASEFIRE) Admin_CeaseFire(); - else if (self.impulse == 192) + else if (self.impulse == TF_ADMIN_COUNTPLAYERS) Admin_CountPlayers(); - else if (self.impulse == 189) + else if (self.impulse == TF_ADMIN_CYCLEDEAL) Admin_CycleDeal(); - else if ((self.impulse == 190) && (self.admin_mode == 1)) + else if ((self.impulse == TF_ADMIN_KICK) && (self.admin_mode == 1)) Admin_DoKick(); - else if ((self.impulse == 191) && (self.admin_mode == 1)) + else if ((self.impulse == TF_ADMIN_BAN) && (self.admin_mode == 1)) Admin_DoBan(); - else if ((self.impulse == 195) && (self.admin_mode == 1)) + else if ((self.impulse == TF_ADMIN_NEXT) && (self.admin_mode == 1)) Admin_CycleDeal(); - else if (self.impulse == 198) + else if (self.impulse == TF_ADMIN_LISTIPS) Admin_ListIPs(); - + else if (self.impulse == TF_ADMIN_CLANMODE) + ClanMode(); + else if (self.impulse == TF_ADMIN_QUADMODE) + QuadMode(); + else if (self.impulse == TF_ADMIN_DUELMODE) + DuelMode(); + else if (self.impulse == TF_ADMIN_ADMINMENU) { + self.current_menu_page = 1; + Menu_Admin(); + } + else if (self.impulse == TF_ADMIN_FORCESTARTMATCH) + StartTimer(); + else if (self.impulse == TF_ADMIN_READYSTATUS) + Broadcast_Players_NotReady(); + + else if (self.impulse > 0 && self.impulse <= 10) { + Menu_Input(self.impulse); + } + self.impulse = 0; }; void () SpectatorThink = { + + self.playerclass = 0; + self.afflicted = 0; + self.teamafflicted = 0; + self.damagegiven = 0; + self.damagetaken = 0; + self.kills = 0; + self.deaths = 0; + self.caps = 0; + self.touches = 0; + self.team_no = 0; + UpdateScoreboardInfo(self); + if (self.impulse) SpectatorImpulseCommand(); - if (self.motd <= 400) - TeamFortress_MOTD(); - if (time >= self.StatusRefreshTime) + if (time >= self.StatusRefreshTime) { RefreshStatusBar(self); + } + + Predict_Update(FALSE); }; diff --git a/spy.qc b/ssqc/spy.qc similarity index 52% rename from spy.qc rename to ssqc/spy.qc index 1688c31f1..22ad025c7 100644 --- a/spy.qc +++ b/ssqc/spy.qc @@ -1,357 +1,65 @@ void (entity Item, entity AP, float method) tfgoalitem_RemoveFromPlayer; void (entity spy) TeamFortress_SpyCalcName; void () CF_Spy_UndercoverThink; -void () GasGrenadeMakeGas; void () T_TranqDartTouch; void () Spy_DropBackpack; +void (entity targ, entity attacker) KillSound; -void () spy_diea1 =[50, spy_diea2] { -}; - -void () spy_diea2 =[51, spy_diea3] { -}; - -void () spy_diea3 =[52, spy_diea4] { -}; - -void () spy_diea4 =[53, spy_diea5] { -}; - -void () spy_diea5 =[54, spy_diea6] { -}; - -void () spy_diea6 =[55, spy_diea7] { -}; - -void () spy_diea7 =[56, spy_diea8] { -}; - -void () spy_diea8 =[57, spy_diea9] { -}; - -void () spy_diea9 =[58, spy_diea10] { -}; - -void () spy_diea10 =[59, spy_diea11] { -}; - -void () spy_diea11 =[60, spy_diea11] { -}; - -void () spy_dieb1 =[61, spy_dieb2] { -}; - -void () spy_dieb2 =[62, spy_dieb3] { -}; - -void () spy_dieb3 =[63, spy_dieb4] { -}; - -void () spy_dieb4 =[64, spy_dieb5] { -}; - -void () spy_dieb5 =[65, spy_dieb6] { -}; - -void () spy_dieb6 =[66, spy_dieb7] { -}; - -void () spy_dieb7 =[67, spy_dieb8] { -}; - -void () spy_dieb8 =[68, spy_dieb9] { -}; - -void () spy_dieb9 =[69, spy_dieb9] { -}; - -void () spy_diec1 =[70, spy_diec2] { -}; - -void () spy_diec2 =[71, spy_diec3] { -}; - -void () spy_diec3 =[72, spy_diec4] { -}; - -void () spy_diec4 =[73, spy_diec5] { -}; - -void () spy_diec5 =[74, spy_diec6] { -}; - -void () spy_diec6 =[75, spy_diec7] { -}; - -void () spy_diec7 =[76, spy_diec8] { -}; - -void () spy_diec8 =[77, spy_diec9] { -}; - -void () spy_diec9 =[78, spy_diec10] { -}; - -void () spy_diec10 =[79, spy_diec11] { -}; - -void () spy_diec11 =[80, spy_diec12] { -}; - -void () spy_diec12 =[81, spy_diec13] { -}; - -void () spy_diec13 =[82, spy_diec14] { -}; - -void () spy_diec14 =[83, spy_diec15] { -}; - -void () spy_diec15 =[84, spy_diec15] { -}; - -void () spy_died1 =[85, spy_died2] { -}; - -void () spy_died2 =[86, spy_died3] { -}; - -void () spy_died3 =[87, spy_died4] { -}; - -void () spy_died4 =[88, spy_died5] { -}; - -void () spy_died5 =[89, spy_died6] { -}; - -void () spy_died6 =[90, spy_died7] { -}; - -void () spy_died7 =[91, spy_died8] { -}; - -void () spy_died8 =[92, spy_died9] { -}; - -void () spy_died9 =[93, spy_died9] { -}; - -void () spy_diee1 =[94, spy_diee2] { -}; - -void () spy_diee2 =[95, spy_diee3] { -}; - -void () spy_diee3 =[96, spy_diee4] { -}; - -void () spy_diee4 =[97, spy_diee5] { -}; - -void () spy_diee5 =[98, spy_diee6] { -}; - -void () spy_diee6 =[99, spy_diee7] { -}; - -void () spy_diee7 =[100, spy_diee8] { -}; - -void () spy_diee8 =[101, spy_diee9] { -}; - -void () spy_diee9 =[102, spy_diee9] { -}; - -void () spy_die_ax1 =[41, spy_die_ax2] { -}; - -void () spy_die_ax2 =[42, spy_die_ax3] { -}; - -void () spy_die_ax3 =[43, spy_die_ax4] { -}; - -void () spy_die_ax4 =[44, spy_die_ax5] { -}; - -void () spy_die_ax5 =[45, spy_die_ax6] { -}; - -void () spy_die_ax6 =[46, spy_die_ax7] { -}; - -void () spy_die_ax7 =[47, spy_die_ax8] { -}; - -void () spy_die_ax8 =[48, spy_die_ax9] { -}; - -void () spy_die_ax9 =[49, spy_die_ax9] { -}; - -void () spy_upb1 =[69, spy_upb2] { -}; - -void () spy_upb2 =[68, spy_upb3] { -}; - -void () spy_upb3 =[67, spy_upb4] { -}; - -void () spy_upb4 =[66, spy_upb5] { -}; - -void () spy_upb5 =[65, spy_upb6] { -}; - -void () spy_upb6 =[64, spy_upb7] { -}; - -void () spy_upb7 =[63, spy_upb8] { -}; - -void () spy_upb8 =[62, spy_upb9] { -}; - -void () spy_upb9 =[61, spy_upb9] { - player_stand1(); -}; - -void () spy_upc1 =[84, spy_upc2] { -}; - -void () spy_upc2 =[83, spy_upc3] { -}; - -void () spy_upc3 =[82, spy_upc4] { -}; - -void () spy_upc4 =[81, spy_upc5] { -}; - -void () spy_upc5 =[80, spy_upc6] { -}; - -void () spy_upc6 =[79, spy_upc7] { -}; - -void () spy_upc7 =[78, spy_upc8] { -}; - -void () spy_upc8 =[77, spy_upc9] { -}; - -void () spy_upc9 =[76, spy_upc10] { -}; - -void () spy_upc10 =[75, spy_upc11] { -}; - -void () spy_upc11 =[74, spy_upc12] { -}; - -void () spy_upc12 =[73, spy_upc13] { -}; - -void () spy_upc13 =[72, spy_upc14] { -}; - -void () spy_upc14 =[71, spy_upc15] { -}; - -void () spy_upc15 =[70, spy_upc15] { - player_stand1(); -}; - -void () spy_upd1 =[93, spy_upd2] { -}; - -void () spy_upd2 =[92, spy_upd3] { -}; - -void () spy_upd3 =[91, spy_upd4] { -}; - -void () spy_upd4 =[90, spy_upd5] { -}; - -void () spy_upd5 =[89, spy_upd6] { -}; - -void () spy_upd6 =[88, spy_upd7] { -}; - -void () spy_upd7 =[87, spy_upd8] { -}; - -void () spy_upd8 =[86, spy_upd9] { -}; - -void () spy_upd9 =[85, spy_upd9] { - player_stand1(); -}; - -void () spy_upe1 =[94, spy_upe9] { -}; - -void () spy_upe9; -void () spy_upe2 =[95, spy_upe8] { -}; - -void () spy_upe8; -void () spy_upe3 =[96, spy_upe7] { -}; - -void () spy_upe7; -void () spy_upe4 =[97, spy_upe6] { -}; - -void () spy_upe6; -void () spy_upe5 =[98, spy_upe5] { -}; - -void () spy_upe6 =[99, spy_upe4] { -}; +float IsFeigned(entity ent) { + return ent.tfstate & TFSTATE_FEIGNED; +} -void () spy_upe7 =[100, spy_upe3] { -}; +anim_t anim_spy_diea1 = { 11, 1, {50}, {0}, FALSE, FALSE, TRUE }; +void spy_dieaN() { client_anim_frames(spy_dieaN, think_nop, &anim_spy_diea1); } +void spy_diea1() { *thinkindex() = 1; spy_dieaN(); } -void () spy_upe8 =[101, spy_upe2] { -}; +anim_t anim_spy_dieb1 = { 9, 1, {61}, {0}, FALSE, FALSE, TRUE }; +void spy_diebN() { client_anim_frames(spy_diebN, think_nop, &anim_spy_dieb1); } +void spy_dieb1() { *thinkindex() = 1; spy_diebN(); } -void () spy_upe9 =[102, spy_upe1] { - player_stand1(); -}; +anim_t anim_spy_diec1 = { 15, 1, {70}, {0}, FALSE, FALSE, TRUE }; +void spy_diecN() { client_anim_frames(spy_diecN, think_nop, &anim_spy_diec1); } +void spy_diec1() { *thinkindex() = 1; spy_diecN(); } -void () spy_upaxe1 =[49, spy_upaxe2] { -}; +anim_t anim_spy_died1 = { 9, 1, {85}, {0}, FALSE, FALSE, TRUE }; +void spy_diedN() { client_anim_frames(spy_diedN, think_nop, &anim_spy_died1); } +void spy_died1() { *thinkindex() = 1; spy_diedN(); } -void () spy_upaxe2 =[48, spy_upaxe3] { -}; +anim_t anim_spy_diee1 = { 9, 1, {94}, {0}, FALSE, FALSE, TRUE }; +void spy_dieeN() { client_anim_frames(spy_dieeN, think_nop, &anim_spy_diee1); } +void spy_diee1() { *thinkindex() = 1; spy_dieeN(); } -void () spy_upaxe3 =[47, spy_upaxe4] { -}; +anim_t anim_spy_die_ax1 = { 9, 1, {41}, {0}, FALSE, FALSE, TRUE }; +void spy_die_axN() { client_anim_frames(spy_die_axN, think_nop, &anim_spy_die_ax1); } +void spy_die_ax1() { *thinkindex() = 1; spy_die_axN(); } -void () spy_upaxe4 =[46, spy_upaxe5] { -}; +void spy_up_extra() { + // Death animations are variable length, this always picks up last frame. + // Animation transition to stand would take an extra 100ms. + if (self.client_think == player_run) + player_stand1(); +} -void () spy_upaxe5 =[45, spy_upaxe6] { -}; +anim_t anim_spy_upb1 = { 9, 1, {69, 68, 67, 66, 65, 64, 63, 62, 61}, {0}, FALSE, FALSE }; +void spy_upbN() { client_anim_frames(spy_upbN, spy_up_extra, &anim_spy_upb1); } +void spy_upb1() { *thinkindex() = 1; spy_upbN(); } -void () spy_upaxe6 =[44, spy_upaxe7] { -}; +anim_t anim_spy_upc1 = + { 15, 1, {84,83,82,81,80,79,78,77,76,75,74,73,72,71,70}, {0}, FALSE, FALSE }; +void spy_upcN() { client_anim_frames(spy_upcN, spy_up_extra, &anim_spy_upc1); } +void spy_upc1() { *thinkindex() = 1; spy_upcN(); } -void () spy_upaxe7 =[43, spy_upaxe8] { -}; +anim_t anim_spy_upd1 = { 9, 1, {93, 92, 91, 90, 89, 88, 87, 86, 85}, {0}, FALSE, FALSE }; +void spy_updN() { client_anim_frames(spy_updN, spy_up_extra, &anim_spy_upd1); } +void spy_upd1() { *thinkindex() = 1; spy_updN(); } -void () spy_upaxe8 =[42, spy_upaxe9] { -}; +anim_t anim_spy_upe1 = { 9, 1, {102, 101, 100, 99, 98, 97, 96, 95, 94}, {0}, FALSE, FALSE }; +void spy_upeN() { client_anim_frames(spy_upeN, spy_up_extra, &anim_spy_upe1); } +void spy_upe1() { *thinkindex() = 1; spy_upeN(); } -void () spy_upaxe9 =[41, spy_upaxe9] { - player_stand1(); -}; +anim_t anim_spy_upaxe1 = { 9, 1, {49, 48, 47, 46, 45, 44, 43, 32, 41}, {0}, FALSE, FALSE }; +void spy_upaxeN() { client_anim_frames(spy_upaxeN, spy_up_extra, &anim_spy_upaxe1); } +void spy_upaxe1() { *thinkindex() = 1; spy_upaxeN(); } float (entity pe_player) Spy_CheckArea = { local entity at_spot = findradius(pe_player.origin, 64); @@ -362,9 +70,9 @@ float (entity pe_player) Spy_CheckArea = { || at_spot.mdl == "progs/disp.mdl") return 1; else if (at_spot.classname == "player" && pe_player != at_spot) { - if (!at_spot.is_feigning && at_spot.deadflag == 0) + if (!IsFeigned(at_spot) && at_spot.deadflag == 0) return 2; - else if (at_spot.is_feigning) + else if (IsFeigned(at_spot)) return 3; } at_spot = at_spot.chain; @@ -372,203 +80,258 @@ float (entity pe_player) Spy_CheckArea = { return 0; }; +void (entity pe_player, float dontstopdead) Spy_CheckForFuncTouch = { + if (pe_player.classname == "player" && pe_player.playerclass == PC_SPY) { + if (IsFeigned(pe_player)) { + pe_player.velocity_x = 0; + pe_player.velocity_y = 0; + pe_player.movetype = MOVETYPE_TOSS; + //This is needed for plats that have a virtual presence and this causes you to fall slowly when inside it + if(!dontstopdead) + pe_player.tfstate |= TFSTATE_CANT_MOVE; + } + } +}; + void () CF_Spy_AirThink = { local float area_check = 0; - if ((self.owner.deadflag >= 2) || (self.owner.playerclass != PC_SPY) || (!self.owner.is_feigning)) { + if(self.owner.deadflag >= DEAD_DEAD) { + // reset the movetype just in case you die while in the TOSS or the WALK states (eg mbasesr lift squish) + self.owner.movetype = MOVETYPE_NONE; + self.owner.tfstate |= TFSTATE_CANT_MOVE; + dremove(self); + return; + } + + if ((self.owner.playerclass != PC_SPY) || (!IsFeigned(self.owner))) { dremove(self); return; } // only do stuff when spy is no longer moving if (!self.owner.velocity) { - // if spy is on ground and an entity is not nearby, set movetype to none (so he can't move) if (self.owner.flags & FL_ONGROUND) { area_check = Spy_CheckArea(self.owner); if (area_check == 0) { - self.owner.movetype = MOVETYPE_NONE; - self.owner.tfstate = self.owner.tfstate | TFSTATE_CANT_MOVE; + //This checks if something is trying to squish you, fixing some situations where the door or whatever would just go through you. + traceline(self.owner.origin, self.owner.origin, 0, self.owner); + if(trace_ent) { + if(trace_ent.classname == "door" || trace_ent.classname == "plat" || trace_ent.classname == "train") { + self.owner.movetype = MOVETYPE_TOSS; + } + } else { + self.owner.movetype = MOVETYPE_NONE; + } + self.owner.tfstate |= TFSTATE_CANT_MOVE; } } - // if spy is in the air, set movetype to walk (so he falls to ground) + // if spy is in the air, set movetype to walk^H^H^H^H toss (so he falls to ground) else { - self.owner.movetype = MOVETYPE_WALK; + self.owner.movetype = MOVETYPE_TOSS; } - - TeamFortress_SetSpeed(self.owner); - } self.nextthink = time + 0.1; }; -void (float issilent) CF_Spy_FeignDeath = { - local string deathstring; - local float area_check; - local float i; - local entity te, spy; - - if (self.is_feigning) { - - // check area for obstructing entities - area_check = Spy_CheckArea(self); - - // nothing on top => unfeign - if (!area_check) { +void (float issilent) FO_Spy_ToggleFeign = { + if (IsFeigned(self) || self.feign_next_damage) { + FO_Spy_Unfeign(); + } else { + FO_Spy_FeignOnNextDamage(); + } +} - // set size of player model - setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); +void (float issilent, float force) FO_Spy_Feign = { + if (IsFeigned(self)) + return; - // set view height - self.view_ofs = '0 0 22'; + if (!force && time <= self.next_feign_time) { + sprint(self, PRINT_HIGH, sprintf( + "%0.1f seconds until you can feign again!\n", + self.next_feign_time - time)); + return; + } - // unset feign variables - self.is_feigning = 0; - self.feign_areachecked = 0; + // don't allow feign if spy is in air and air feigning is disallowed + if (!feign_air && !(self.flags & FL_ONGROUND)) { + return; + } - // load saved weapon state and set current ammo - W_WeaponState_Load(self, 0); - W_SetCurrentAmmo(self); + // check area for feigned spy on the ground + local float area_check; + area_check = Spy_CheckArea(self); + if (area_check == 3) { + return; + } - // allow spy to move again - self.movetype = MOVETYPE_WALK; - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_CANT_MOVE); - TeamFortress_SetSpeed(self); + // Success. We will feign. + if (feign_rate_limit) + self.next_feign_time = time + feign_rate_limit; + else + self.next_feign_time = 0; - // set revive animation - i = 1 + floor(random() * 5); - if (i == 1) { - spy_upb1(); - } else if (i == 2) { - spy_upc1(); - } else if (i == 3) { - spy_upd1(); - } else { - spy_upe1(); - } + // don't check for team color cheat for 5 seconds + self.immune_to_check = time + 5; - // something is on top of spy - } else if (area_check == 1) { - sprint(self, PRINT_HIGH, "You cannot get up with something on top of you\n"); - } else if (area_check == 2) { - sprint(self, PRINT_HIGH, "You cannot get up while someone is standing on you\n"); - } - } else { + // set movetype to toss + self.movetype = MOVETYPE_TOSS; - // don't feign spam timer is active - if (time < self.antispam_feign) { - sprint(self, PRINT_HIGH, "You cannot feign right now\n"); - return; - } + // this timer will make sure the spy falls + // to the ground when possible + local entity spy; + spy = spawn(); + spy.classname = "airtimer"; + spy.owner = self; + spy.think = CF_Spy_AirThink; + spy.nextthink = time + 0.1; - // don't allow feign if spy is in air and air feigning is disallowed - if (!feign_air && !(self.flags & FL_ONGROUND)) { - sprint(self, PRINT_HIGH, "You cannot feign while you are airborne\n"); - return; - } + // set spy feign variables + self.tfstate |= TFSTATE_FEIGNED; + self.is_button_feigning = 1; - // check area for feigned spy on the ground - area_check = Spy_CheckArea(self); - if (area_check == 3) { - sprint(self, PRINT_HIGH, "You cannot feign on top of another spy\n"); - return; - } + Attack_Finished(0.8); + self.invisible_finished = 0; - // stop spy from attempting feign for 3 seconds - self.antispam_feign = time + 3; + // set precached model index + self.modelindex = modelindex_player; - // don't check for team color cheat for 5 seconds - self.immune_to_check = time + 5; + // set size of player model + setsize(self, '-16 -16 -24', '16 16 -16'); - // set movetype to toss - self.movetype = MOVETYPE_TOSS; + // set weapon state and remove weapon viewmodel + self.tfstate |= TFSTATE_NO_WEAPON; - // this timer will make sure the spy falls - // to the ground when possible - spy = spawn(); - spy.classname = "airtimer"; - spy.owner = self; - spy.think = CF_Spy_AirThink; - spy.nextthink = time + 0.1; + // set view height to ground + self.view_ofs = '0 0 4'; - // set spy feign variables - self.is_feigning = 1; + // do extra stuff if feign is not silent feign + if (issilent == 0) { + // make a death sound + DeathSound(); - Attack_Finished(0.8); - self.invisible_finished = 0; + // drop an empty backpack (if this is not disabled in settings) + if (feign_pack) + Spy_DropBackpack(); - // set precached model index - self.modelindex = modelindex_player; + // print feign message (if this is not disabled in settings) + if (feign_msg) { + local string deathstring; + deathstring = GetDeathMessage(self, self.attacked_by, self.feignmsg); + bprint(PRINT_MEDIUM, deathstring); + KillSound(self, self.attacked_by); + } - // set size of player model - setsize(self, '-16 -16 -24', '16 16 -16'); + // set movement speed to 0 if currently in the air to disable manipulation of trajectory + if (!(self.flags & FL_ONGROUND)) + self.maxspeed = 0; + } - // set weapon state and remove weapon viewmodel - W_WeaponState_Save(self); - self.weaponmodel = ""; - self.weaponframe = 0; + // drop flag if spy is carrying it + local entity te; + te = find(world, classname, "item_tfgoal"); + while (te) { + if (te.owner == self) { + if (!(te.goal_activation & TFGI_KEEP) || self.has_disconnected == 1) + tfgoalitem_RemoveFromPlayer(te, self, 0); + if (CTF_Map == 1) { + if (te.goal_no == 1) + bprint(PRINT_HIGH, self.netname, Q" \slost\s the \sblue\s flag!\n"); + else if (te.goal_no == 2) + bprint(PRINT_HIGH, self.netname, Q" \slost\s the \sred\s flag!\n"); + } + } + te = find(te, classname, "item_tfgoal"); + } - // set view height to ground - self.view_ofs = '0 0 4'; + // die with axe equipped if carrying axe, medikit, knife or spanner + if (IsSlotMelee(self.current_slot)) { + spy_die_ax1(); + return; + } - // do extra stuff if feign is not silent feign - if (issilent == 0) { - // make a death sound - DeathSound(); + // randomize death animation + local float i; + i = 1 + floor((random() * 6)); + if (i == 1) + spy_diea1(); + else if (i == 2) + spy_dieb1(); + else if (i == 3) + spy_diec1(); + else if (i == 4) + spy_died1(); + else + spy_diee1(); +} + +void () FO_Spy_FeignOnNextDamage = { + self.feign_next_damage = 1; + sprint(self, PRINT_HIGH, "Feigning on next damage...\n"); +} + +void () FO_Spy_Unfeign = { + if (self.feign_next_damage) { + self.feign_next_damage = 0; + sprint(self, PRINT_HIGH, "Feign cancelled\n"); + } - // drop an empty backpack (if this is not disabled in settings) - if (feign_pack) - Spy_DropBackpack(); + if (!IsFeigned(self)) + return; - // print feign message (if this is not disabled in settings) - if (feign_msg) { - deathstring = GetDeathMessage(self, self.attacked_by, self.feignmsg); - bprint(PRINT_MEDIUM, deathstring); - } + if (!self.is_button_feigning) { + return; + } - // set movement speed to 0 if currently in the air to disable manipulation of trajectory - if (!(self.flags & FL_ONGROUND)) - self.maxspeed = 0; - } + // check area for obstructing entities + local float area_check; + area_check = Spy_CheckArea(self); - // drop flag if spy is carrying it - te = find(world, classname, "item_tfgoal"); - while (te) { - if (te.owner == self) { - if (!(te.goal_activation & TFGI_KEEP) || self.has_disconnected == 1) - tfgoalitem_RemoveFromPlayer(te, self, 0); - if (CTF_Map == 1) { - if (te.goal_no == 1) - bprint(PRINT_HIGH, self.netname, " ÌÏÓÔ the ÂÌÕÅ flag!\n"); - else if (te.goal_no == 2) - bprint(PRINT_HIGH, self.netname, " ÌÏÓÔ the ÒÅÄ flag!\n"); - } - } - te = find(te, classname, "item_tfgoal"); - } + // nothing on top => unfeign + if (!area_check) { - // die with axe equipped if carrying axe, medikit or spanner - if (self.weapon <= WEAP_AXE) { - spy_die_ax1(); - return; + // set size of player model + setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); + + // set view height + self.view_ofs = '0 0 22'; + + // unset feign variables + self.tfstate &= ~TFSTATE_FEIGNED; + self.is_button_feigning = 0; + self.feign_areachecked = 0; + + // load saved weapon state and set current ammo + W_UpdateCurrentWeapon(self); + + // allow spy to move again + self.movetype = MOVETYPE_WALK; + self.tfstate &= ~(TFSTATE_CANT_MOVE | TFSTATE_NO_WEAPON); + + // set revive animation + local float i; + i = 1 + floor(random() * 5); + if (i == 1) { + spy_upb1(); + } else if (i == 2) { + spy_upc1(); + } else if (i == 3) { + spy_upd1(); + } else { + spy_upe1(); } - - // randomize death animation - i = 1 + floor((random() * 6)); - if (i == 1) - spy_diea1(); - else if (i == 2) - spy_dieb1(); - else if (i == 3) - spy_diec1(); - else if (i == 4) - spy_died1(); - else - spy_diee1(); } -}; +} + +void (float issilent) FO_Spy_FeignCmd = { + if (IsFeigned(self)) + FO_Spy_Unfeign(); + else + FO_Spy_Feign(issilent, 0); +} void () CF_Spy_Invisible = { local entity e_timer; @@ -602,6 +365,7 @@ void () CF_Spy_Invisible = { sprint(self, PRINT_HIGH, "Going invisible...\n"); self.is_undercover = 2; + self.undercover_timer = PC_SPY_GO_UNDERCOVER_TIME; // create a timer for 4 seconds e_timer = spawn(); @@ -609,7 +373,6 @@ void () CF_Spy_Invisible = { e_timer.owner = self; e_timer.think = CF_Spy_UndercoverThink; e_timer.nextthink = time + 1; - self.undercover_timer = 4; } Status_Refresh(self); @@ -639,7 +402,6 @@ void () CF_Spy_DisguiseStop = { if (self.undercover_skin || self.undercover_team) { sprint(self, PRINT_HIGH, "Disguised as ", s_team, " ", s_class, "\n"); self.is_undercover = 1; - self.last_skin = self.undercover_skin; self.last_team = self.undercover_team; } else { sprint(self, PRINT_HIGH, "You stop going undercover\n"); @@ -663,20 +425,184 @@ void () CF_Spy_DisguiseStop = { } Status_Refresh(self); +}; +void (entity player, float is_user) FO_Spy_DisguiseLast = { + if (!player.last_selected_skin) { + FO_Spy_DisguiseLastSpawned(player, is_user); + return; + } + + if (self.is_undercover == 2 && player.last_selected_skin != player.disguise_skin) { + CF_Spy_DisguiseStop(); + } + + local float undercover_team; + if (player.last_team) { + undercover_team = player.last_team; + } else { + if (player.team_no == 1) { + undercover_team = 2; + } else { + undercover_team = 1; + } + } + + CF_Spy_ChangeColor(player, undercover_team, is_user); + CF_Spy_ChangeSkin(player, player.last_selected_skin, is_user); }; -void () CF_Spy_DisguiseLast = { - if (self.last_skin) { - CF_Spy_ChangeSkin(self, self.last_skin); +void FO_Spy_DisguiseCycle(entity spy, float direction) { + local float current_skin = spy.disguise_skin; + local entity te; + + if (!current_skin) { + te = find(world, classname, "player"); + while (te != world) { + if (te.team_no != spy.team_no && te.skin) { + CF_Spy_DisguiseStop(); + Menu_Spy_Skin_Input(te.skin); + spy.disguise_skin = te.skin; + return; + } + te = find(te, classname, "player"); + } + } else { + local float next_skin = current_skin + direction; + + while (next_skin != current_skin) { + te = find(world, classname, "player"); + while (te != world) { + if (te.skin == next_skin && te.team_no != spy.team_no) { + CF_Spy_DisguiseStop(); + Menu_Spy_Skin_Input(te.skin); + spy.disguise_skin = te.skin; + return; + } + te = find(te, classname, "player"); + } + + next_skin += direction; + + if (next_skin > 9) { + next_skin = 1; + } else if (next_skin < 1) { + next_skin = 9; + } + } } - if (self.last_team) { - CF_Spy_ChangeColor(self, self.last_team); + + sprint(spy, PRINT_HIGH, "No enemies to disguise as!\n"); +} + +void (entity player, float is_user) FO_Spy_DisguiseLastSpawned = { + local entity te = find(world, classname, "player"); + local float latest_spawn_time = 0; + local entity enemy_to_disguise_as = world; + + while (te != world) { + if (te.team_no != player.team_no) { + if (te.spawn_time > latest_spawn_time) { + latest_spawn_time = te.spawn_time; + enemy_to_disguise_as = te; + } + } + + te = find(te, classname, "player"); + } + + if (enemy_to_disguise_as == world) { + sprint(self.owner, PRINT_HIGH, "No enemies to disguise as!\n"); + return; + } + + if (self.is_undercover == 2 && enemy_to_disguise_as.skin != player.disguise_skin) { + CF_Spy_DisguiseStop(); + } + + if (enemy_to_disguise_as.team_no) { + CF_Spy_ChangeColor(player, enemy_to_disguise_as.team_no, is_user); + + if (enemy_to_disguise_as.skin) { + CF_Spy_ChangeSkin(player, enemy_to_disguise_as.skin, is_user); + } + } else { + local float undercover_team; + if (player.team_no == 1) { + undercover_team = 2; + } else { + undercover_team = 1; + } + + CF_Spy_ChangeColor(player, undercover_team, is_user); } }; +void (entity own) Spy_SetClientSkins = { + entity te; + string skin, dskin, sendcolor, sendskin, pteam, dsteam, sendteam; + float dteam, dpc; + + dteam = own.undercover_team == 0 ? own.team_no : own.undercover_team; + dpc = own.undercover_skin == 0 ? own.playerclass : own.undercover_skin; + + skin = TeamFortress_GetSkin(own.playerclass); + dskin = TeamFortress_GetSkin(dpc); + + pteam = GetTeamName(own.team_no); + dsteam = GetTeamName(dteam); + + te = find(world, classname, "player"); + while (te) + { + if (te.team_no) + { + if (te.team_no == own.team_no) + { + // on same team, send them spy/team (initial spawn etc) + sendcolor = TeamFortress_TeamGetColorFor(te, own.team_no); + sendskin = skin; + sendteam = pteam; + } + else // not on same team, send them disguise + { + sendcolor = TeamFortress_TeamGetColorFor(te, dteam); + sendskin = dskin; + sendteam = dsteam; + } + + msg_entity = te; + + // set skin + WriteByte(MSG_ONE, SVC_SETINFO); + WriteByte(MSG_ONE, own.colormap-1); // ???? 0 based player index + WriteString(MSG_ONE, "skin"); + WriteString(MSG_ONE, sendskin); + + // set top colour / color + WriteByte(MSG_ONE, SVC_SETINFO); + WriteByte(MSG_ONE, own.colormap-1); + WriteString(MSG_ONE, "topcolor"); + WriteString(MSG_ONE, sendcolor); + + // set bottom colour / color + WriteByte(MSG_ONE, SVC_SETINFO); + WriteByte(MSG_ONE, own.colormap-1); + WriteString(MSG_ONE, "bottomcolor"); + WriteString(MSG_ONE, sendcolor); + + // set team to fix r_enemyskincolor cheat in clients + WriteByte(MSG_ONE, SVC_SETINFO); + WriteByte(MSG_ONE, own.colormap-1); + WriteString(MSG_ONE, "team"); + WriteString(MSG_ONE, sendteam); + } + + te = find(te, classname, "player"); + } +} + void () CF_Spy_UndercoverThink = { - local float f_team_color; local string s_team, s_class; // keep track of seconds left to @@ -697,12 +623,10 @@ void () CF_Spy_UndercoverThink = { // make spy invisible if invis_only is turned on if (invis_only) { - self.owner.items = self.owner.items | IT_INVISIBILITY; self.owner.frame = 0; self.owner.modelindex = modelindex_eyes; self.owner.is_undercover = 1; - } else { // don't check for color cheating for next 10 seconds @@ -710,14 +634,13 @@ void () CF_Spy_UndercoverThink = { if (self.skin) { self.owner.undercover_skin = self.skin; - self.owner.last_skin = self.skin; self.owner.disguise_skin = 0; self.owner.queue_skin = 0; self.owner.is_undercover = 1; TeamFortress_SetSkin(self.owner); if (self.owner.queue_team) { - CF_Spy_ChangeColor(self.owner, self.owner.queue_team); + CF_Spy_ChangeColor(self.owner, self.owner.queue_team, FALSE); } else { if (self.owner.undercover_team) s_team = TeamFortress_TeamGetColorString(self.owner.undercover_team); @@ -733,18 +656,12 @@ void () CF_Spy_UndercoverThink = { } else if (self.team) { self.owner.undercover_team = self.team; self.owner.last_team = self.team; - - // tell client to change color - f_team_color = TeamFortress_TeamGetColor(self.team) - 1; - stuffcmd(self.owner, "color "); - stuffcmd(self.owner, ftos(f_team_color)); - stuffcmd(self.owner, "\n"); self.owner.queue_team = 0; self.owner.is_undercover = 1; TeamFortress_SetSkin(self.owner); if (self.owner.queue_skin) { - CF_Spy_ChangeSkin(self.owner, self.owner.queue_skin); + CF_Spy_ChangeSkin(self.owner, self.owner.queue_skin, FALSE); } else { s_team = TeamFortress_TeamGetColorString(self.owner.undercover_team); if (self.owner.undercover_skin) @@ -762,12 +679,12 @@ void () CF_Spy_UndercoverThink = { Status_Refresh(self.owner); if (self.owner.menu_input == Menu_Spy_Input) Menu_Spy(self.owner); - dremove(self); + dremove(self); }; -void (entity pe_player, float pf_class) CF_Spy_ChangeSkin = { - local entity e_timer; +void (entity pe_player, float pf_class, float is_user) CF_Spy_ChangeSkin = { + pe_player.last_selected_skin = pf_class; // stop if you're already disguised as the requested skin if (pe_player.undercover_skin == pf_class) @@ -802,26 +719,28 @@ void (entity pe_player, float pf_class) CF_Spy_ChangeSkin = { // prepare disguise if (!pe_player.undercover_team) sprint(pe_player, PRINT_HIGH, "Going undercover...\n"); + else if(pe_player.undercover_skin != pf_class) + sprint(pe_player, PRINT_HIGH, "Changing costumes...\n"); + pe_player.is_undercover = 2; pe_player.disguise_skin = pf_class; pe_player.disguise_team = 0; // disguise timer, finishes in 4 seconds + pe_player.undercover_timer = PC_SPY_GO_UNDERCOVER_TIME; + local entity e_timer; e_timer = spawn(); e_timer.classname = "spytimer"; e_timer.owner = pe_player; e_timer.think = CF_Spy_UndercoverThink; - e_timer.nextthink = time + 1; + e_timer.nextthink = (is_user ? time : time) + 1; e_timer.skin = pf_class; - pe_player.undercover_timer = 4; - TeamFortress_SetSkin(pe_player); Status_Refresh(pe_player); }; -void (entity pe_player, float pf_team_no) CF_Spy_ChangeColor = { +void (entity pe_player, float pf_team_no, float is_user) CF_Spy_ChangeColor = { local entity e_timer; - local float f_team_color = TeamFortress_TeamGetColor(pf_team_no) - 1; // stop if you're already disguised as the requested skin if (pe_player.undercover_team == pf_team_no) @@ -842,9 +761,6 @@ void (entity pe_player, float pf_team_no) CF_Spy_ChangeColor = { pe_player.undercover_team = 0; pe_player.disguise_team = 0; pe_player.queue_team = 0; - stuffcmd(pe_player, "color "); - stuffcmd(pe_player, ftos(f_team_color)); - stuffcmd(pe_player, "\n"); if (!pe_player.undercover_skin) pe_player.is_undercover = 0; TeamFortress_SetSkin(pe_player); @@ -865,11 +781,12 @@ void (entity pe_player, float pf_team_no) CF_Spy_ChangeColor = { pe_player.disguise_team = pf_team_no; // disguise timer, finishes in 4 seconds + pe_player.undercover_timer = PC_SPY_GO_UNDERCOVER_TIME; e_timer = spawn(); e_timer.classname = "spytimer"; e_timer.owner = pe_player; e_timer.think = CF_Spy_UndercoverThink; - e_timer.nextthink = time + 4; + e_timer.nextthink = (is_user ? time : time) + 1; e_timer.team = pf_team_no; Status_Refresh(pe_player); @@ -883,7 +800,7 @@ void (entity spy) TeamFortress_SpyCalcName = { te = find(world, classname, "player"); while (te) { if ((te.team_no == spy.undercover_team) && - (te.skin == spy.undercover_skin)) { + (te.skin == spy.undercover_skin)) { spy.undercover_name = te.netname; te = world; } else { @@ -905,32 +822,30 @@ void (entity spy) TeamFortress_SpyCalcName = { }; void () GasGrenadeTouch = { - sound(self, 1, "weapons/bounce.wav", 1, 1); + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, 1); if (self.velocity == '0 0 0') self.avelocity = '0 0 0'; }; -void () GasGrenadeExplode = { - local entity te; +void () GasGrenadeExplode2; + +// GasGrenadeExplode1 handles the initial "pre-explosion". +// GasGrenadeExplode2 handles aoe emission. +void () GasGrenadeExplode1 = { local float pos; pos = pointcontents(self.origin); if (pos == -1) { - te = spawn(); - te.think = GasGrenadeMakeGas; - te.nextthink = time + 0.1; - te.classname = "gastimer"; - te.heat = 0; - te.origin = self.origin; - te.owner = self.owner; - te.team_no = self.owner.team_no; - te.weapon = 0; - te.enemy = self; + self.think = GasGrenadeExplode2; + self.nextthink = time + 0.1; + self.heat = 0; + self.dimension_seen = DMN_INVISIBLE; + self.movetype = MOVETYPE_NONE; } else { pos = 0; while (pos < 10) { newmis = spawn(); - setmodel(newmis, "progs/s_bubble.spr"); + FO_SetModel(newmis, "progs/s_bubble.spr"); setorigin(newmis, self.origin); newmis.movetype = 8; newmis.solid = 0; @@ -944,81 +859,67 @@ void () GasGrenadeExplode = { setsize(newmis, '-8 -8 -8', '8 8 8'); pos = pos + 1; } + dremove(self); } - dremove(self); }; -void () GasGrenadeMakeGas = { +void () GasGrenadeExplode2 = { local entity te; local entity timer; - if (self.heat == 0) { - self.owner.no_active_gas_grens = - self.owner.no_active_gas_grens + 1; - if (self.owner.no_active_gas_grens > 2) { - te = find(world, classname, "gastimer"); - while (te) { - if ((te.owner == self.owner) && - (te.no_active_gas_grens == 1)) { - te.weapon = 24; - te.think = RemoveGrenade; - te.nextthink = time + 0.1; - } - te = find(te, classname, "gastimer"); - } - } - self.no_active_gas_grens = self.owner.no_active_gas_grens; - } self.nextthink = time + 0.75; te = findradius(self.origin, 200); while (te != world) { - if (((te.classname == "player") && (te.deadflag == 0)) && - (te.has_disconnected != 1)) { - deathmsg = 24; - TF_T_Damage(te, world, self.owner, 10, (1 | 2), 0); - if (te.tfstate & 16384) { - timer = find(world, classname, "timer"); - while (((timer.owner != te) || - (timer.think != HallucinationTimer)) && - (timer != world)) { - timer = find(timer, classname, "timer"); - } - if (timer != world) { - timer.health = timer.health + 25; - if (old_grens == 1) { - if (timer.health < 100) { - timer.health = 100; - } - timer.nextthink = time + 0.5; - } else { - if (timer.health < 150) { - timer.health = 150; + if (CanDamage(te, self)) { + if (((te.classname == "player") && (te.deadflag == 0)) && + (te.has_disconnected != 1)) { + deathmsg = 24; + TF_T_Damage(te, world, self.owner, 10, (1 | 2), 0); + if (te.tfstate & TFSTATE_HALLUCINATING) { + timer = find(world, classname, "timer"); + while (((timer.owner != te) || + (timer.think != HallucinationTimer)) && + (timer != world)) { + timer = find(timer, classname, "timer"); + } + if (timer != world) { + timer.health = timer.health + 25; + if (old_grens == 1) { + if (timer.health < 100) { + timer.health = 100; + } + timer.nextthink = time + 0.5; + } else { + if (timer.health < 150) { + timer.health = 150; + } + timer.nextthink = time + 0.3; } - timer.nextthink = time + 0.3; } - } - } else { - if (old_grens == 1) { - stuffcmd(te, "v_cshift 50 25 50 -50\n"); - sprint(te, PRINT_HIGH, "Far out man!\n"); } else { - sprint(te, PRINT_HIGH, - "Run for cover! They are everywhere!\n"); + LogEventAffliction(self.owner, te, TFSTATE_HALLUCINATING); + if (old_grens == 1) { + stuffcmd(te, "v_cshift 50 25 50 -50\n"); + sprint(te, PRINT_HIGH, "Far out man!\n"); + } else { + sprint(te, PRINT_HIGH, + "Run for cover! They are everywhere!\n"); + } + te.tfstate = te.tfstate | TFSTATE_HALLUCINATING; + timer = spawn(); + if (old_grens == 1) + timer.nextthink = time + 0.5; + else + timer.nextthink = time + 0.3; + timer.think = HallucinationTimer; + timer.classname = "timer"; + timer.owner = te; + if (old_grens == 1) + timer.health = 100; + else + timer.health = 150; + timer.team_no = self.team_no; } - te.tfstate = te.tfstate | TFSTATE_HALLUCINATING; - timer = spawn(); - if (old_grens == 1) - timer.nextthink = time + 0.5; - else - timer.nextthink = time + 0.3; - timer.think = HallucinationTimer; - timer.classname = "timer"; - timer.owner = te; - if (old_grens == 1) - timer.health = 100; - else - timer.health = 150; - timer.team_no = self.team_no; } } te = te.chain; @@ -1046,7 +947,7 @@ void () GasGrenadeMakeGas = { self.weapon = 0; return; } - RemoveGrenade(); + dremove(self); }; void () HallucinationTimer = { @@ -1062,15 +963,15 @@ void () HallucinationTimer = { self.health = self.health - 2.5; } if (((self.health <= 0) || (self.owner.deadflag != 0)) || - (self.owner.has_disconnected == 1)) { + (self.owner.has_disconnected == 1)) { self.owner.tfstate = - self.owner.tfstate - (self.owner.tfstate & 16384); + self.owner.tfstate - (self.owner.tfstate & TFSTATE_HALLUCINATING); } if ((self.owner.deadflag != 0) || (self.owner.has_disconnected == 1)) { dremove(self); return; } - if (!(self.owner.tfstate & 16384)) { + if (!(self.owner.tfstate & TFSTATE_HALLUCINATING)) { if (old_grens == 1) stuffcmd(self.owner, "v_cshift; wait; bf\n"); sprint(self.owner, 2, "You feel a little better now\n"); @@ -1149,7 +1050,7 @@ void () HallucinationTimer = { stuffcmd(self.owner, "play weapons/tink1.wav\n"); } else if (halltype2 < 0.55) { CenterPrint2(self.owner, "\n\n\n", - "Your team ÃÁÐÔÕÒÅÄ the flag!!"); + Q"Your team \scaptured\s the flag!!"); stuffcmd(self.owner, "play weapons/grenade.wav\n"); } else if (halltype2 < 0.6) { stuffcmd(self.owner, "play weapons/bounce.wav\n"); @@ -1201,7 +1102,7 @@ void () HallucinationTimer = { stuffcmd(self.owner, "play weapons/sniper.wav\n"); } else if (halltype2 < 0.6) { CenterPrint2(self.owner, "\n\n\n", - "Your flag has been ÔÁËÅÎ!!"); + Q"Your flag has been \staken\s!!"); stuffcmd(self.owner, "play weapons/flmfire2.wav\n"); } else if (halltype2 < 0.7) { stuffcmd(self.owner, "play weapons/flmgrexp.wav\n"); @@ -1219,43 +1120,46 @@ void () HallucinationTimer = { void () W_FireTranq = { self.ammo_shells = self.ammo_shells - 1; - self.currentammo = self.ammo_shells; KickPlayer(-2, self); - newmis = spawn(); - newmis.owner = self; - newmis.movetype = 9; - newmis.solid = 2; + entity proj = FOProj_Create(FPP_TRANQ); + proj.owner = self; + proj.movetype = 9; + proj.solid = 2; makevectors(self.v_angle); - newmis.velocity = v_forward; - newmis.velocity = newmis.velocity * 1500; - newmis.angles = vectoangles(newmis.velocity); - newmis.touch = T_TranqDartTouch; - newmis.think = SUB_Remove; - newmis.nextthink = time + 6; - setmodel(newmis, "progs/spike.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin + v_forward * 8 + '0 0 16'); + proj.velocity = v_forward; + proj.velocity = proj.velocity * FPP_Get(FPP_TRANQ)->speed; + proj.angles = vectoangles(proj.velocity); + proj.touch = T_TranqDartTouch; + proj.think = SUB_Remove; + proj.nextthink = time + 6; + proj.classname = "proj_tranq"; + setorigin(proj, self.origin + v_forward * 8 + '0 0 16'); + + FOProj_Finalize(proj); }; void () T_TranqDartTouch = { local entity timer; - if (other.solid == 1) + if (other.solid == SOLID_TRIGGER) return; - if (pointcontents(self.origin) == CONTENT_SKY) { - dremove(self); + if (self.voided) return; - } + self.voided = 1; + if (other.takedamage) { if ((other.classname == "player") && - !((other.team_no == self.owner.team_no) && - (teamplay & (2 | 4)))) { - if (other.tfstate & 32768) { + !((other.team_no == self.owner.team_no) && + (teamplay & (2 | 4)))) { + + LogEventAffliction(self.owner, other, TFSTATE_TRANQUILISED); + + if (other.tfstate & TFSTATE_TRANQUILISED) { timer = find(world, classname, "timer"); while (((timer.owner != other) || - (timer.think != TranquiliserTimer)) && - (timer != world)) { + (timer.think != TranquiliserTimer)) && + (timer != world)) { timer = find(timer, classname, "timer"); } if (timer != world) { @@ -1263,19 +1167,20 @@ void () T_TranqDartTouch = { } } else { sprint(other, 2, "You feel tired...\n"); - other.tfstate = other.tfstate | 32768; + other.tfstate |= TFSTATE_TRANQUILISED; timer = spawn(); timer.nextthink = time + 15; timer.think = TranquiliserTimer; timer.classname = "timer"; timer.owner = other; timer.team_no = self.owner.team_no; - TeamFortress_SetSpeed(other); } } spawn_touchblood(9); deathmsg = 25; - TF_T_Damage(other, self, self.owner, 20, 2, 2); + + float dmg = IsClownMode(CLOWN_LETHAL_TRANQ) ? 999 : 20; + TF_T_Damage(other, self, self.owner, dmg, 2, 2); } else { WriteByte(4, 23); if (self.classname == "wizspike") { @@ -1292,19 +1197,16 @@ void () T_TranqDartTouch = { WriteCoord(4, self.origin_z); multicast(self.origin, 2); } - dremove(self); + dremove_sent(self); }; void () TranquiliserTimer = { - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & 32768); - TeamFortress_SetSpeed(self.owner); + self.owner.tfstate &= ~TFSTATE_TRANQUILISED; sprint(self.owner, PRINT_HIGH, "You feel more alert now\n"); dremove(self); }; void (entity spy) Spy_RemoveDisguise = { - local string st; - local float tc; local float coverblown = 0; if (invis_only != 1) { @@ -1313,11 +1215,6 @@ void (entity spy) Spy_RemoveDisguise = { spy.immune_to_check = time + 10; spy.undercover_team = 0; spy.disguise_team = 0; - stuffcmd(spy, "color "); - tc = TeamFortress_TeamGetColor(spy.team_no) - 1; - st = ftos(tc); - stuffcmd(spy, st); - stuffcmd(spy, "\n"); coverblown = 1; } if (spy.undercover_skin != 0) { @@ -1336,19 +1233,42 @@ void (entity spy) Spy_RemoveDisguise = { coverblown = 1; spy.is_undercover = 0; spy.modelindex = modelindex_player; - if (spy.items & 524288) { + if (spy.items & IT_INVISIBILITY) { spy.invisible_finished = 0; spy.invisible_time = 0; - spy.items = spy.items - 524288; + spy.items = spy.items - IT_INVISIBILITY; } Status_Refresh(self); } - if (coverblown) + + local entity timer = find(world, classname, "spytimer"); + while (timer) { + if (timer.owner == self) { + dremove(timer); + } + + timer = find(timer, classname, "spytimer"); + } + + if (coverblown) { Status_Print(self, "\n\n\n\n\n\n\n", "You blew your cover!"); + } + + local float autodisguise = FO_GetUserSetting(self, "autodisguise", "ad", "off"); + if (self.playerclass == PC_SPY) { + switch(autodisguise) { + case 1: + FO_Spy_DisguiseLastSpawned(self, FALSE); + break; + case 2: + FO_Spy_DisguiseLast(self, FALSE); + break; + } + } }; void () Spy_DropBackpack = { - if ((cb_prematch_time + 3) > time) + if (cb_prematch) return; newmis = spawn(); @@ -1368,9 +1288,15 @@ void () Spy_DropBackpack = { newmis.flags = FL_ITEM; newmis.solid = SOLID_TRIGGER; newmis.movetype = MOVETYPE_TOSS; - setmodel(newmis, "progs/backpack.mdl"); + + if(splitbackpackmodels) + FO_SetModel(newmis, "progs/deathbag.mdl"); + else + FO_SetModel(newmis, "progs/backpack.mdl"); + setsize(newmis, '-16 -16 0', '16 16 56'); newmis.touch = BackpackTouch; + newmis.classname = "backpack"; newmis.nextthink = time + 120; newmis.think = SUB_Remove; diff --git a/status.qc b/ssqc/status.qc similarity index 55% rename from status.qc rename to ssqc/status.qc index 06572ea09..474f9adac 100644 --- a/status.qc +++ b/ssqc/status.qc @@ -1,14 +1,11 @@ float (float tno) TeamFortress_TeamGetScore; float () TeamFortress_TeamGetWinner; float () TeamFortress_TeamGetSecond; -string(float num) NumberToString1000; - string(float num) BlueScoreToString; string(float num) RedScoreToString; string(float num) YellowScoreToString; string(float num) GreenScoreToString; -string(entity pl) ClipSizeToString; -float(entity pl) GetClipSize; +string(entity pl, float csqcactive) ClipSizeToString; string(entity pl) SniperPowerToString; string(entity pl) DetpackToString; string(entity pl) AuraToString; @@ -19,6 +16,10 @@ string(entity pl) DisguiseToString; string(entity pl) SentryDetailsToString; string(entity pl) BuildingToString; string(float pc) TeamFortress_GetClassName; +entity(float ino) Finditem; +void () tfgoalitem_dropthink; +void () tfgoalitem_remove; +float (entity ent, string ps_short, string ps_setting, string ps_default) FO_GetUserSetting; string (float class) CF_GetRandomClassTip { local string tiptype = ""; @@ -39,8 +40,8 @@ string (float class) CF_GetRandomClassTip { if (self.tip_type == 1) { // general tip - tiptype = "Çåîåòáì ôéð:\n"; - + tiptype = Q"\sGeneral tip\s:\n"; + while (strlen(line1) == 0) { if (!self.display_tip) self.display_tip = ceil(random() * 14); @@ -93,7 +94,7 @@ string (float class) CF_GetRandomClassTip { } } else { // class tip - tiptype = "Ãìáóó Ôéð:\n"; + tiptype = Q"\sClass tip\s:\n"; if (class == PC_SCOUT) { while (strlen(line1) == 0) { @@ -232,7 +233,11 @@ string (float class) CF_GetRandomClassTip { line1 = "Use the super shotgun (2) at very close"; line2 = "range to avoid splash damage."; } else if (self.display_tip == 7) { - line1 = "Use a nail grenade (f) to clear a small"; + if (nailgren_type == NGR_TYPE_LASER) { + line1 = "Use a shock grenade (f) to clear a small"; + } else { + line1 = "Use a nail grenade (f) to clear a small"; + } line2 = "room or block a bottle neck."; } } @@ -506,7 +511,7 @@ void (entity pl, string...count) Status_Print = lines = 0; for (i = 0; i < len; i++) - if (substr(pl.StatusString, i, 1) == "\n") + if (substring(pl.StatusString, i, 1) == "\n") lines++; pl.StatusStringLines = lines; @@ -532,7 +537,7 @@ void (entity pl, f_void_float func, string...count) Status_Menu = lines = 0; for (i = 0; i < len; i++) - if (substr(pl.StatusString, i, 1) == "\n") + if (substring(pl.StatusString, i, 1) == "\n") lines++; pl.StatusStringLines = lines; @@ -561,18 +566,485 @@ void (entity pl, string s1, string s2, string s3) CenterPrint3 = { Status_Print(pl, s1, s2, s3); }; +void (entity pl, string s1, string s2, string s3, string s4) CenterPrint4 = { + Status_Print(pl, s1, s2, s3, s4); +}; + +void (entity pl, string s1, string s2, string s3, string s4, string s5) CenterPrint5 = { + Status_Print(pl, s1, s2, s3, s4, s5); +}; + +void (entity pl, string s1, string s2, string s3, string s4, string s5, string s6) CenterPrint6 = { + Status_Print(pl, s1, s2, s3, s4, s5, s6); +}; + +string getLocationName(vector location); + +void (entity Player, float index, entity Item, float icon) InitClientFlagStatus = { + if(!infokeyf(Player, INFOKEY_P_CSQCACTIVE) || !CF_GetSetting("ssbfi", "server_sbflaginfo", "1")) { + return; + } + msg_entity = Player; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_FLAGINFOINIT); + WriteFloat(MSG_MULTICAST, index); + WriteFloat(MSG_MULTICAST, Item.goal_no); + WriteString(MSG_MULTICAST, Item.mdl); + WriteFloat(MSG_MULTICAST, Item.skin); + WriteFloat(MSG_MULTICAST, Item.owned_by); + WriteFloat(MSG_MULTICAST, icon); + multicast('0 0 0', MULTICAST_ONE_R_NOSPECS); +} + +void (entity Player) InitAllStatuses = { + entity tfdet = find(world, classname, "info_tfdetect"); + if(tfdet) { + InitClientFlagStatus(Player, 0, Finditem(tfdet.display_item_status1), FLAGINFO_ICON_FLAG); + InitClientFlagStatus(Player, 1, Finditem(tfdet.display_item_status2), FLAGINFO_ICON_FLAG); + InitClientFlagStatus(Player, 2, Finditem(tfdet.display_item_status3), FLAGINFO_ICON_FLAG); + InitClientFlagStatus(Player, 3, Finditem(tfdet.display_item_status4), FLAGINFO_ICON_FLAG); + } + tfdet = find(world, classname, "info_tfgoal"); + while (tfdet) { + if (tfdet.track_goal) { + InitClientFlagStatus(Player, -1, tfdet, FLAGINFO_ICON_BUTTON); + } + tfdet = find(tfdet, classname, "info_tfgoal"); + } +}; + +float laststate; +void (entity Player, entity Goal) UpdateClientButtonStatus = { + if(!infokeyf(Player, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = Player; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_FLAGINFO); + WriteFloat(MSG_MULTICAST, Goal.goal_no); + if(Goal.goal_no == 11) { + if(Goal.goal_state != laststate) { + laststate = Goal.goal_state; + } + } + if(Goal.goal_state == TFGS_DELAYED) { + WriteFloat(MSG_MULTICAST, FLAGINFO_DROPPED); + WriteFloat(MSG_MULTICAST, rint(Goal.bubble_count - time)); + //WriteFloat(MSG_MULTICAST, FLAGINFO_NOLOCATION); + WriteFloat(MSG_MULTICAST, FLAGINFO_LOCATION); + WriteCoord(MSG_MULTICAST, Goal.origin_x); + WriteCoord(MSG_MULTICAST, Goal.origin_y); + WriteCoord(MSG_MULTICAST, Goal.origin_z); + //WriteString(MSG_MULTICAST, getLocationName(Item.origin)); + if(Goal.team_str_moved) { + WriteString(MSG_MULTICAST, Goal.team_str_moved); + } else { + WriteString(MSG_MULTICAST,"\sOffline\s"); + } + } else { + WriteFloat(MSG_MULTICAST, FLAGINFO_HOME); + } + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void (entity Player, entity Item) UpdateClientFlagStatus = { + if(!infokeyf(Player, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = Player; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_FLAGINFO); + WriteFloat(MSG_MULTICAST, Item.goal_no); + if (Item.goal_state == 1 && Item.owner != world) { + WriteFloat(MSG_MULTICAST, FLAGINFO_CARRIED); + if (Player == Item.owner) { + WriteString(MSG_MULTICAST, "YOU"); + } else { + WriteString(MSG_MULTICAST, Item.owner.netname); + } + } else { + if (Item.origin != Item.oldorigin) { + if((Item.nextthink - time) >= 0) { + WriteFloat(MSG_MULTICAST, FLAGINFO_DROPPED); + if(noreturn) { + WriteFloat(MSG_MULTICAST, -1); + } else { + WriteFloat(MSG_MULTICAST, rint(Item.bubble_count - time)); + } + if((Item.think == tfgoalitem_dropthink || Item.think == tfgoalitem_remove) && !Item.owner) { + WriteFloat(MSG_MULTICAST, FLAGINFO_LOCATION); + WriteCoord(MSG_MULTICAST, Item.origin_x); + WriteCoord(MSG_MULTICAST, Item.origin_y); + WriteCoord(MSG_MULTICAST, Item.origin_z); + WriteString(MSG_MULTICAST, getLocationName(Item.origin)); + } else { + WriteFloat(MSG_MULTICAST, FLAGINFO_NOLOCATION); + } + } else { + WriteFloat(MSG_MULTICAST, FLAGINFO_RETURNING); + } + } else { + WriteFloat(MSG_MULTICAST, FLAGINFO_HOME); + } + } + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +string (entity Player, entity Item, float teamno) GetItemStatus = { + local string st = ""; + switch (teamno) + { + case 1: + st = "Blue Flag"; + break; + case 2: + st = "Red Flag"; + break; + case 3: + st = "Yellow Flag"; + break; + case 4: + st = "Green Flag"; + break; + } + if (Player.team_no == Item.owned_by) { + st = strcat(Q"\x10", st ,Q"\x11"); + } + if (Item.goal_state == 1 && Item.owner != world) { + if (Player == Item.owner) { + st = strcat(st, Q"\s: Carried by \sYOU"); + } else { + st = strcat(st, Q"\s: Carried by \s", Item.owner.netname); + } + } else { + if (Item.origin != Item.oldorigin) { + //When the item is thrown, there is a touch think with a pad of 4.25s before the normal timer kicks in + if((Item.nextthink - time) >= 0) { + if(!noreturn) { + st = strcat(st, Q"\s: Return: \s", ftos(rint(Item.bubble_count - time))); + } else { + st = strcat(st, Q"\s: Dropped\s"); + } + } else { + st = strcat(st, Q"\s: Returning\s NOW!"); + } + } else { + st = strcat(st, Q"\s: Safe\s"); + } + } + + return st; +} + +string GetSBClassInfo(entity pl, float csqcactive) +{ + string st1 = ""; + if (pl.playerclass == PC_SCOUT) + st1 = ScannerToString(pl); + else if (pl.playerclass == PC_SNIPER && (pl.tfstate & TFSTATE_AIMING)) + st1 = SniperPowerToString(pl); + else if (pl.playerclass == PC_DEMOMAN && pl.detpack_left) + st1 = DetpackToString(pl); + else if (pl.playerclass == PC_MEDIC) + st1 = AuraToString(pl); + else if (pl.playerclass == PC_HVYWEAP) + st1 = AssaultCannonToString(pl); + else if (pl.playerclass == PC_SPY) + st1 = DisguiseToString(pl); + else if (pl.playerclass == PC_ENGINEER && pl.has_sentry) + st1 = SentryDetailsToString(pl); + if (pl.playerclass == PC_ENGINEER && pl.is_building) + st1 = BuildingToString(pl); + + return st1; +} + +void UpdateClientPlayerDie(entity pl) { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_PLAYER_DIE); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientPlayerSpawn(entity pl) { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_PLAYER_SPAWN); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientBuilding(entity pl) { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_BUILDING); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClient_Sentry(entity pl, entity sent) = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_SENTRY_POS); + WriteFloat(MSG_MULTICAST, sent.origin.x); + WriteFloat(MSG_MULTICAST, sent.origin.y); + WriteFloat(MSG_MULTICAST, sent.origin.z); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientReloadSound(entity pl, float weapon) { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_RELOADSOUND); + WriteFloat(MSG_MULTICAST, weapon); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void UpdateClientGrenadePrimed(entity pl, float grentype, float explodes_at) = { +#if 0 // Not needed until we re-enable TFX. + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_GRENPRIMED); + WriteEntity(MSG_MULTICAST, pl); + WriteByte(MSG_MULTICAST, grentype); + WriteFloat(MSG_MULTICAST, explodes_at); + + multicast('0 0 0', MULTICAST_ALL); // Actual primer has reliable transport. +#endif +} + +void UpdateClientGrenadeThrown(entity pl) = { +#if 0 // Not needed until we re-enable TFX. + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_GRENTHROWN); + WriteEntity(MSG_MULTICAST, pl); + multicast('0 0 0', MULTICAST_ALL); +#endif +} + +void UpdateClientIDString(entity pl) { + string ident = time < pl.ident_time ? pl.ident_string : ""; + + if (ident == "") // No need to send null, we'll expire clientside. + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_ID); + WriteString(MSG_MULTICAST, ident); + multicast('0 0 0', MULTICAST_ONE); +} + +void UpdateClientStatusBar(entity pl) +{ + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + // if we ever change to fte only, this could be changed to stats + string msg = ""; + msg_entity = pl; + + string clipMsg = ClipSizeToString(pl, TRUE); + + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_SBAR); + WriteString(MSG_MULTICAST, clipMsg); + WriteFloat(MSG_MULTICAST, pl.fragstreak); + WriteFloat(MSG_MULTICAST, pl.caps); + + WriteFloat(MSG_MULTICAST, pl.playerclass); // just in case we get a packet from "last life" after changing playerclass + + // class info + switch (pl.playerclass) + { + case PC_SCOUT: + // off, on + WriteFloat(MSG_MULTICAST, pl.ScannerOn); + + if (pl.ScannerOn) + { + entity te; + te = find(world, netname, "scanner"); + while ((te != world) && (te.owner != pl)) { + te = find(te, netname, "scanner"); + } + WriteFloat(MSG_MULTICAST, te.health); // range to person, 0 or less if no one + if (te.health) + { + WriteFloat(MSG_MULTICAST, te.team_no); + WriteFloat(MSG_MULTICAST, te.playerclass); + WriteFloat(MSG_MULTICAST, te.tf_items_flags); // nothing, friendly, enemy + } + } + break; + case PC_SNIPER: + float dam; + dam = 0; + if (sniperpower) + dam = pl.heat; + + WriteFloat(MSG_MULTICAST, dam); + break; + case PC_DEMOMAN: + WriteFloat(MSG_MULTICAST, pl.is_detpacking); + WriteFloat(MSG_MULTICAST, pl.detpack_left); + break; + case PC_MEDIC: + WriteFloat(MSG_MULTICAST, medicaura); + + if (medicaura) + { + WriteFloat(MSG_MULTICAST, pl.aura_active); + + if (pl.aura_active) + { + float c, a; + c = (time < pl.aura_healtime) ? pl.aura_healcount : 0; + a = (time < pl.aura_healtime) ? pl.aura_healamount : 0; + WriteFloat(MSG_MULTICAST, c); + WriteFloat(MSG_MULTICAST, a); + // state + float s = PC_MEDIC_AURA_ACTIVE; + if (pl.ammo_cells < ceil(PC_MEDIC_MAXAMMO_CELL / 2)) + { + s = PC_MEDIC_AURA_OUTOFPOWER; + } + else if (pl.ammo_cells < floor(PC_MEDIC_MAXAMMO_CELL * 0.95)) + { + s = PC_MEDIC_AURA_RECHARGING; + } + WriteFloat(MSG_MULTICAST, s); + } + } + break; + case PC_HVYWEAP: + // ass can locked + float l = FALSE; + if (pl.tfstate & TFSTATE_LOCK) + { + l = TRUE; + } + + WriteFloat(MSG_MULTICAST, l); + break; + case PC_PYRO: + if(pl.airblast_cooldown == 1) + { + WriteFloat(MSG_MULTICAST, TRUE); + } + else if (pl.airblast_cooldown == 0) + { + WriteFloat(MSG_MULTICAST, FALSE); + } + break; + case PC_SPY: + WriteFloat(MSG_MULTICAST, pl.is_undercover); + + if (pl.is_undercover == 1) + { + WriteFloat(MSG_MULTICAST, invis_only); + WriteFloat(MSG_MULTICAST, pl.undercover_team); + WriteFloat(MSG_MULTICAST, pl.undercover_skin); + } + else if (pl.is_undercover == 2) + { + WriteFloat(MSG_MULTICAST, invis_only); + WriteFloat(MSG_MULTICAST, pl.undercover_timer); + WriteFloat(MSG_MULTICAST, pl.undercover_team); + WriteFloat(MSG_MULTICAST, pl.disguise_team); + WriteFloat(MSG_MULTICAST, pl.queue_team); + WriteFloat(MSG_MULTICAST, pl.undercover_skin); + WriteFloat(MSG_MULTICAST, pl.disguise_skin); + WriteFloat(MSG_MULTICAST, pl.queue_skin); + } + break; + case PC_ENGINEER: + // building status?? + WriteFloat(MSG_MULTICAST, pl.is_building); + if (pl.is_building) + { + WriteFloat(MSG_MULTICAST, pl.building_percentage); + } + + WriteFloat(MSG_MULTICAST, pl.has_sentry); + if (pl.has_sentry) + { + WriteFloat(MSG_MULTICAST, pl.sentry_ent.weapon); // level + WriteFloat(MSG_MULTICAST, pl.sentry_ent.health); + WriteFloat(MSG_MULTICAST, pl.sentry_ent.ammo_shells); + WriteFloat(MSG_MULTICAST, pl.sentry_ent.ammo_rockets); + + } + + WriteFloat(MSG_MULTICAST, pl.has_dispenser); + if (pl.has_dispenser) + { + entity disp; + disp = find(world, classname, "building_dispenser"); + while (disp) + { + if (disp.real_owner == pl) + { + WriteFloat(MSG_MULTICAST, disp.health); + } + disp = find(disp, classname, "building_dispenser"); + } + } + break; + } + + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void (entity pl, float flag_team) UpdateClientFlagPickUp = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_FLAG_PICKUP); + WriteFloat(MSG_MULTICAST, flag_team); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + +void (entity pl) UpdateClientFlagDrop = { + if(!infokeyf(pl, INFOKEY_P_CSQCACTIVE)) + return; + + msg_entity = pl; + WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); + WriteByte(MSG_MULTICAST, MSG_FLAG_DROP); + multicast('0 0 0', MULTICAST_ONE_NOSPECS); +} + void (entity pl) RefreshStatusBar = { local string pad; - local string s1; // will be used for grenade timers - local string s2; // class line - local string s3; // score & clip - local string ct; // class tip + local string s1 = ""; // will be used for grenade timers + local string s2 = ""; // class line + local string s3 = ""; // score & clip + local string ct = ""; // class tip local string st1, st2, st3, st4; // status bar columns 1-4 - local string ident; + local string ident = ""; local float height; local float i; - local float win, sec; - + local entity tfdet; //info_tfdetect entity + local entity te = world, tg; + local string bi; //button info + local float csqcactive; + //By default, show after tips; 0/off = off; 2 = always + local float sbflaginfostate = FO_GetUserSetting(pl, "sbflaginfo", "sbflaginfo", "on"); + tfdet = find(world, classname, "info_tfdetect"); + pad = ""; if (pl.StatusStringLines > 0 && pl.StatusStringTime <= time && !pl.menu_input) { @@ -587,8 +1059,50 @@ void (entity pl) RefreshStatusBar = { height = 300; height = height - pl.StatusStringLines - 13; + pl.StatusRefreshTime = time + 1.5; + + csqcactive = infokeyf(pl, INFOKEY_P_CSQCACTIVE); + if(pl.classname == "observer" && csqcactive) { + if (tfdet) + { + for (float t = 1; t <= number_of_teams; t++) + { + switch (t) + { + case 1: + te = Finditem(tfdet.display_item_status1); + break; + case 2: + te = Finditem(tfdet.display_item_status2); + break; + case 3: + te = Finditem(tfdet.display_item_status3); + break; + case 4: + te = Finditem(tfdet.display_item_status4); + break; + } + + if (te) + { + UpdateClientFlagStatus(pl, te); + } + } + } + tg = find(world, classname, "info_tfgoal"); + while (tg) { + if (tg.track_goal) { + UpdateClientButtonStatus(pl, tg); + } + tg = find(tg, classname, "info_tfgoal"); + } + UpdateClientIDString(pl); + + return; + } + // no sbar can be displayed - if (height <= 0 || pl.playerclass == PC_UNDEFINED) { + if (height <= 0 || (pl.playerclass == PC_UNDEFINED && !votemode)) { centerprint(pl, pl.StatusString); pl.StatusRefreshTime = time + 1.5; return; @@ -598,153 +1112,214 @@ void (entity pl) RefreshStatusBar = { pad = strcat(pad, "\n"); pad = strzone(pad); + // class tip - if (((time - 6) < pl.spawn_time) || ((time - 6) < pl.tip_time)) { + if ((((time - 6) < pl.spawn_time) || ((time - 6) < pl.tip_time)) && sbflaginfostate < 2 && !csqcactive) { ct = CF_GetRandomClassTip(pl.playerclass); } else { pl.display_tip = 0; - ct = strzone("\n\n\n\n\n\n"); + + // we get flag info from tfdetect + if (!tfdet || !sbflaginfostate) + { + ct = strzone("\n\n\n\n\n\n"); + } + //else + if (csqcactive) // has csqc + { + //pl.StatusRefreshTime = time + 1; + UpdateClientStatusBar(pl); + UpdateClientIDString(pl); + + // flag info + if (CF_GetSetting("ssbfi", "server_sbflaginfo", "1")) + { + if (tfdet) + { + for (float t = 1; t <= number_of_teams; t++) + { + switch (t) + { + case 1: + te = Finditem(tfdet.display_item_status1); + break; + case 2: + te = Finditem(tfdet.display_item_status2); + break; + case 3: + te = Finditem(tfdet.display_item_status3); + break; + case 4: + te = Finditem(tfdet.display_item_status4); + break; + } + + if (te) + { + UpdateClientFlagStatus(pl, te); + } + } + } + + tg = find(world, classname, "info_tfgoal"); + while (tg) { + if (tg.track_goal) { + UpdateClientButtonStatus(pl, tg); + } + tg = find(tg, classname, "info_tfgoal"); + } + } + } + else if (CF_GetSetting("ssbfi", "server_sbflaginfo", "1")) // no csqc but has sbflaginfo on and server_sbflaginfo is enabled + { + ct = ""; + i = number_of_teams; // Extra newlines + for (float t = 1; t <= number_of_teams; t++) { + switch (t) + { + case 1: + te = Finditem(tfdet.display_item_status1); + break; + case 2: + te = Finditem(tfdet.display_item_status2); + break; + case 3: + te = Finditem(tfdet.display_item_status3); + break; + case 4: + te = Finditem(tfdet.display_item_status4); + break; + } + + if (te) + { + if(number_of_teams < 4) { + ct = strcat(ct, strpadr(GetItemStatus(pl, te, t),40),"\n"); + if((te.think == tfgoalitem_dropthink || te.think == tfgoalitem_remove) && !te.owner && (te.origin != te.oldorigin)) { + ct = strcat(ct, strpadr(strcat("\s Location: \s", getLocationName(te.origin)),40)); + } + i += 1; + } else { + if((te.think == tfgoalitem_dropthink || te.think == tfgoalitem_remove) && !te.owner && (te.origin != te.oldorigin)) { + ct = strcat(ct, strpadr(strcat(GetItemStatus(pl, te, t),"\s: \s", getLocationName(te.origin)),40)); + } else { + ct = strcat(ct, strpadr(GetItemStatus(pl, te, t),40)); + } + } + } + ct = strcat(ct, "\n"); + } + tg = find(world, classname, "info_tfgoal"); + while (tg) { + if (tg.track_goal && i < 6 && tg.goal_state == TFGS_DELAYED) { + bi = ""; + //only do this for named goals, otherwise there's no way to distinguish them (they don't ususally have owned_by or anything) + if(tg.netname) { + bi = tg.netname; + if(tg.team_str_moved) { + bi = strcat(bi,": ", tg.team_str_moved); + } else { + bi = strcat(bi,": \sOffline\s"); + } + bi = strcat(bi, " ", ftos(rint(tg.bubble_count - time))); + ct = strcat(ct, strpadr(bi,40),"\n"); + i += 1; + } + } + tg = find(tg, classname, "info_tfgoal"); + } + + for (float t = 0; t < (6 - i); t++) + ct = strcat(ct, "\n"); + + ct = strzone(ct); + } + else { + ct = strzone("\n\n\n\n\n\n"); + } } - // status line 1 column 1 - grenade timer - if (pl.StatusGrenTime > 0) { - st1 = strcat("Çòåîáäå: ", ftos(pl.StatusGrenTime)); - if (pl.fragstreak > 1 && pl.caps) - st1 = strcat(st1, " sec"); - else - st1 = strcat(st1, " seconds"); - } else + if (!csqcactive) + { + // status line 1 column 1 - grenade timer + if (pl.StatusGrenTime > 0) { + st1 = strcat(Q"\sGrenade\s: ", ftos(pl.StatusGrenTime)); + if (pl.fragstreak > 1 && pl.caps) + st1 = strcat(st1, " sec"); + else + st1 = strcat(st1, " seconds"); + } else + st1 = ""; + // status line 1 column 3 - kill streak & caps + if (pl.fragstreak > 1) { + st2 = Q"\sKill Streak\s: "; + st2 = strcat(st2, strpadl(ftos(pl.fragstreak),2)); + } else + st2 = ""; + if (pl.caps) { + if (pl.fragstreak > 1) + st2 = strcat(st2, " "); + st3 = Q"\sCaps\s: "; + st3 = strcat(st3, strpadl(ftos(pl.caps),2)); + } else + st3 = ""; + st2 = strcat(st2, st3); + // status line 1 + if (pl.fragstreak > 1 && pl.caps) { + st2 = strpadl(st2, 25); + s1 = strpadr(st1, 15); + } else { + st2 = strpadl(st2, 20); + s1 = strpadr(st1, 20); + } + s1 = strcat(s1, st2); + s1 = strcat(s1, "\n"); + s1 = strzone(s1); + + // status line 2 column 1 - class specific information + st1 = GetSBClassInfo(pl, csqcactive); + + // status line 2 + s2 = strpadr(st1, 40); + s2 = strcat(s2, "\n"); + s2 = strzone(s2); + st1 = ""; - // status line 1 column 3 - kill streak & caps - if (pl.fragstreak > 1) { - st2 = "Ëéìì óôòåáë: "; - st2 = strcat(st2, strpadl(ftos(pl.fragstreak),2)); - } else st2 = ""; - if (pl.caps) { - if (pl.fragstreak > 1) - st2 = strcat(st2, " "); - st3 = "Ãáðó: "; - st3 = strcat(st3, strpadl(ftos(pl.caps),2)); - } else - st3 = ""; - st2 = strcat(st2, st3); - // status line 1 - if (pl.fragstreak > 1 && pl.caps) { - st2 = strpadl(st2, 25); - s1 = strpadr(st1, 15); - } else { - st2 = strpadl(st2, 20); - s1 = strpadr(st1, 20); - } - s1 = strcat(s1, st2); - s1 = strcat(s1, "\n"); - s1 = strzone(s1); - // status line 2 column 1 - class specific information - st1 = ""; - if (pl.playerclass == PC_SCOUT) - st1 = ScannerToString(pl); - else if (pl.playerclass == PC_SNIPER && (pl.tfstate & TFSTATE_AIMING)) - st1 = SniperPowerToString(pl); - else if (pl.playerclass == PC_DEMOMAN && pl.detpack_left) - st1 = DetpackToString(pl); - else if (pl.playerclass == PC_MEDIC) - st1 = AuraToString(pl); - else if (pl.playerclass == PC_HVYWEAP) - st1 = AssaultCannonToString(pl); - else if (pl.playerclass == PC_SPY) - st1 = DisguiseToString(pl); - else if (pl.playerclass == PC_ENGINEER && pl.has_sentry) - st1 = SentryDetailsToString(pl); - if (pl.playerclass == PC_ENGINEER && pl.is_building) - st1 = BuildingToString(pl); - // status line 2 - s2 = strpadr(st1, 40); - s2 = strcat(s2, "\n"); - s2 = strzone(s2); - - // status line 3 column 1 - team 1 score - win = TeamFortress_TeamGetWinner(); - sec = TeamFortress_TeamGetSecond(); - if (win == 0) - st1 = BlueScoreToString(team1score); - else if (win == 1) - st1 = BlueScoreToString(team1score); - else if (win == 2) - st1 = RedScoreToString(team2score); - else if (win == 3) - st1 = YellowScoreToString(team3score); - else - st1 = GreenScoreToString(team4score); - st1 = strcat(st1, " "); - // status line 3 column 2 - team 2 score - if (sec == 0) { - if (win < 2) - st2 = RedScoreToString(team2score); - else - st2 = BlueScoreToString(team1score); - } - else if (sec == 1) - st2 = BlueScoreToString(team1score); - else if (sec == 2) - st2 = RedScoreToString(team2score); - else if (sec == 3) - st2 = YellowScoreToString(team3score); - else - st2 = GreenScoreToString(team4score); - // status line 3 column 3 - clip size - if (pl.tfstate & TFSTATE_RELOADING) { - st3 = "Ãìéð: "; - if ((sniperreloadpercent) && (reload_cliptick) && (pl.playerclass == PC_SNIPER)) { - st3 = strcat(st3, strpadl(ftos(25 * pl.reload_sniper_ticks), 3)); - st3 = strcat(st3, "% "); + // status line 3 column 2 - clip size + st2 = strcat(Q"\sClip\s: ", ClipSizeToString(pl, csqcactive)); + + // status line 3 column 3 - grenade 1 count + st3 = strcat(Q"\sGren1\s: ", ftos(pl.no_grenades_1)); + + // status line 3 column 4 - grenade 2 count + st4 = strcat(Q"\sGren2\s: ", ftos(pl.no_grenades_2)); + + // status line 3 + s3 = strcat(st1, st2); + s3 = strpadr(s3, 19); + s3 = strcat(s3, strpadl(strcat(st3, strcat(" ", st4)), 21)); + s3 = strcat(s3, "\n"); + s3 = strzone(s3); + + // identify + if (pl.ident_string != string_null && time < pl.ident_time) { + ident = strcat(pl.ident_string, "\n\n"); } else { - if (reload_cliptick) - st3 = strcat(st3, strpadl(ftos(pl.reload_clipsize), 2)); - else - st3 = strcat(st3, " 0"); - st3 = strcat(st3, "/"); - st3 = strcat(st3, strpadr(ftos(GetClipSize(pl)), 3)); + ident = "\n\n\n\n"; } - } else { - st3 = ClipSizeToString(pl); } - // status line 3 column 4 - grenade count - st4 = strcat("Çòåî: ", strcat(strcat(ftos(pl.no_grenades_1), "+"), ftos(pl.no_grenades_2))); - // status line 3 - s3 = strcat(st1, st2); - s3 = strpadr(s3, 19); - s3 = strcat(s3, strpadl(strcat(st3, st4), 21)); - s3 = strcat(s3, "\n"); - s3 = strzone(s3); - - // identify - if (pl.ident_string != string_null && time < pl.ident_time) { - ident = strcat(pl.ident_string, "\n\n"); - } else { - ident = "\n\n\n\n"; - } - centerprint(pl, pl.StatusString, pad, ident, ct, s1, s2, s3); - pl.StatusRefreshTime = time + 1.5; strunzone(pad); strunzone(ct); strunzone(s1); strunzone(s2); strunzone(s3); }; -string(float num) NumberToString1000 = -{ - if (num > 999) - return "999"; - - return strpadl(ftos(floor(num)), 3); -}; - string(float num) BlueScoreToString = { if (num > 999) num = 999; - return strcat("Âìõå:", strpadl(ftos(floor(num)), 3)); + return strcat(Q"\sBlue\s:", strpadl(ftos(floor(num)), 3)); }; string(float num) RedScoreToString = @@ -752,7 +1327,7 @@ string(float num) RedScoreToString = if (num > 999) num = 999; - return strcat("Òåä :", strpadl(ftos(floor(num)), 3)); + return strcat(Q"\sRed\s :", strpadl(ftos(floor(num)), 3)); }; string(float num) YellowScoreToString = @@ -760,7 +1335,7 @@ string(float num) YellowScoreToString = if (num > 999) num = 999; - return strcat("Ùåìì:", strpadl(ftos(floor(num)), 3)); + return strcat(Q"\sYell\s:", strpadl(ftos(floor(num)), 3)); }; string(float num) GreenScoreToString = @@ -768,94 +1343,29 @@ string(float num) GreenScoreToString = if (num > 999) num = 999; - return strcat("Çòåî:", strpadl(ftos(floor(num)), 3)); + return strcat(Q"\sGren\s:", strpadl(ftos(floor(num)), 3)); }; -string(entity pl) ClipSizeToString = +string(entity pl, float csqcactive) ClipSizeToString = { - local string st; - local float num, max; - - max = GetClipSize(pl); - if (pl.current_weapon == WEAP_SHOTGUN) { - if ((max - pl.reload_shotgun) > pl.ammo_shells) - pl.reload_shotgun = max - pl.ammo_shells; - num = max - pl.reload_shotgun; - } else if (pl.current_weapon == WEAP_SUPER_SHOTGUN) { - if ((max - pl.reload_super_shotgun) > pl.ammo_shells) - pl.reload_super_shotgun = max - pl.ammo_shells; - num = (max - pl.reload_super_shotgun); - } else if (pl.current_weapon == WEAP_SNIPER_RIFLE) { - if (!sniperreload) - return (""); - if ((max - pl.reload_sniper_rifle) > pl.ammo_shells) - pl.reload_sniper_rifle = max - pl.ammo_shells; - num = (max - pl.reload_sniper_rifle); - } else if (pl.current_weapon == WEAP_GRENADE_LAUNCHER) { - if ((max - pl.reload_grenade_launcher) > pl.ammo_rockets) - pl.reload_grenade_launcher = (max - pl.ammo_rockets); - num = (max - pl.reload_grenade_launcher); - } else if (pl.current_weapon == WEAP_ROCKET_LAUNCHER) { - if ((max - pl.reload_rocket_launcher) > pl.ammo_rockets) - pl.reload_rocket_launcher = (max - pl.ammo_rockets); - num = (max - pl.reload_rocket_launcher); - } else - return (""); - - if (num > 99) - num = 99; - - st = "Ãìéð: "; - st = strcat(st, strpadl(ftos(floor(num)), 2)); - st = strcat(st, "/"); - st = strcat(st, strpadr(ftos(max), 3)); + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + FO_WeapInfo* wi = ws->wi; - return st; -}; + if (!wi->needs_reload) + return ""; -float(entity pl) GetClipSize = -{ - if (pl.current_weapon == WEAP_SHOTGUN) - return 8; - else if (pl.current_weapon == WEAP_SUPER_SHOTGUN) - return 16; - else if (pl.current_weapon == WEAP_SNIPER_RIFLE) - return 1; - else if (pl.current_weapon == WEAP_GRENADE_LAUNCHER) - return 6; - else if (pl.current_weapon == WEAP_ROCKET_LAUNCHER) - return 4; - else - return 0; -}; + float clip_rem = min(wi->clip_size - *ws->clip_fired - pl.last_still_loading, 999); -string(float num) TeamToString = -{ - if (num == 1) return "Blue"; - if (num == 2) return "Red"; - if (num == 3) return "Yellow"; - if (num == 4) return "Green"; - return " "; -}; + string st = ""; + if (csqcactive) + st = strcat(ftos(floor(clip_rem)), "/", ftos(wi->clip_size)); + else + st = strcat(strpadl(ftos(clip_rem), 2), "/", strpadr(ftos(wi->clip_size), 3)); -string(float num) ClassToString = -{ - if (num == 1) return "Scout"; - if (num == 2) return "Sniper"; - if (num == 3) return "Soldier"; - if (num == 4) return "Demoman"; - if (num == 5) return "Medic"; - if (num == 6) return "HWGuy"; - if (num == 7) return "Pyro"; - if (num == 8) return "Spy"; - if (num == 9) return "Engineer"; - if (num == 11) return "Civilian"; - if (num == 13) return "Sentry Gun"; - if (num == 14) return "Goal Item"; - return ""; + return st; }; - string(entity pl) DisguiseToString = { local string st = ""; @@ -864,9 +1374,9 @@ string(entity pl) DisguiseToString = if (pl.is_undercover == 1) { if (self.items & IT_INVISIBILITY) { - st = "Éîöéóéâìå"; + st = Q"\sInvisible\s"; } else { - st = "Õîäåòãïöåò: "; + st = Q"\sUndercover\s: "; if (pl.undercover_team) { st = strcat(st, TeamToString(pl.undercover_team)); st = strcat(st, " "); @@ -876,7 +1386,7 @@ string(entity pl) DisguiseToString = } } else if (pl.is_undercover == 2) { if (invis_only) { - st = "Éîöéóéâìå in "; + st = Q"\sInvisible\s in "; st = strcat(st, ftos(pl.undercover_timer)); st = strcat(st, " seconds"); } else { @@ -905,7 +1415,7 @@ string(entity pl) DisguiseToString = } else if (pl.undercover_skin) { skin = strcat(skin, ClassToString(pl.undercover_skin)); } - st = "Õîäåòãïöåò: "; + st = Q"\sUndercover\s: "; st = strcat(st, team); st = strcat(st, skin); } @@ -922,12 +1432,11 @@ string(entity pl) SniperPowerToString = return st; if (pl.heat) { - st = "Ðï÷åò: "; + st = Q"\spower\s: "; st = strcat(st, ftos(pl.heat)); st = strcat(st, " dmg"); - if (pl.power_full) { + if (pl.heat == PC_SNIPER_MAXDAM) st = strcat(st, " (max)"); - } } return st; @@ -938,14 +1447,14 @@ string(entity pl) DetpackToString = local string st = ""; if (pl.is_detpacking) { - st = "Äåôðáãë: "; + st = Q"\sDetpack\s: "; st = strcat(st, ftos(pl.detpack_left)); st = strcat(st, " ("); st = strcat(st, ftos(pl.is_detpacking)); st = strcat(st, ")"); st = strcat(st, " seconds left"); } else if (pl.detpack_left) { - st = "Äåôðáãë: "; + st = Q"\sDetpack\s: "; st = strcat(st, ftos(pl.detpack_left)); st = strcat(st, " seconds left"); } @@ -958,7 +1467,7 @@ string(entity pl) AuraToString = local string st; if (medicaura) { - st = "Èåáìéîç Áõòá: "; + st = Q"\sHealing Aura\s: "; if (pl.aura_active) { if (time < pl.aura_healtime && pl.aura_healcount) { @@ -967,9 +1476,9 @@ string(entity pl) AuraToString = st = strcat(st, ftos(pl.aura_healamount)); st = strcat(st, " hp"); } - else if (pl.ammo_cells < 50) + else if (pl.ammo_cells < ceil(PC_MEDIC_MAXAMMO_CELL / 2)) st = strcat(st, "out of power"); - else if (pl.ammo_cells < 95) + else if (pl.ammo_cells < floor(PC_MEDIC_MAXAMMO_CELL * 0.95)) st = strcat(st, "recharging"); else st = strcat(st, "on"); @@ -985,8 +1494,8 @@ string(entity pl) AuraToString = string(entity pl) AssaultCannonToString = { - if (pl.current_weapon == WEAP_ASSAULT_CANNON && (pl.tfstate & TFSTATE_LOCK)) - return "Áóóáõìô Ãáîîïî Ìïãëåä"; + if (FO_CurrentWeapon() == WEAP_ASSAULT_CANNON && (pl.tfstate & TFSTATE_LOCK)) + return Q"\sAssault Cannon Locked\s"; else return ""; }; @@ -1026,29 +1535,30 @@ string(entity pl) ScannerToString = local string range = ""; local float pad; + st = Q"\sScanner\s: "; + + if (pl.ScannerOn != TRUE) { + return strcat(st, "off"); + } + te = find(world, netname, "scanner"); while ((te != world) && (te.owner != pl)) { te = find(te, netname, "scanner"); } - if (pl.ScannerOn != 1) { - return "Óãáîîåò: off"; - } - if (te.health > 0) { - st = "Óãáîîåò: "; st = strcat(st, TeamToString(te.team_no)); st = strcat(st, " "); st = strcat(st, ClassToString(te.playerclass)); st = strpadr(st, 26); - range = "Òáîçå: "; + range = Q"\srange\s: "; range = strcat(range, RangeToString(pl)); pad = 40 - strlen(range); st = strpadr(st, pad); st = strcat(st, range); } else { - st = "Óãáîîåò: on"; - st = strcat(st, " Óãáîîéîç: "); + st = strcat(st, "on"); + st = strcat(st, Q" \sScanning\s: "); if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { scanfor = "team"; @@ -1086,29 +1596,47 @@ string(entity pl) SentryDetailsToString = if (self.sentry_ent.weapon == 3) { rockets = ftos(floor(self.sentry_ent.ammo_rockets)); - ammo = strcat(" Áííï: ", strcat(shells, strcat("/", rockets))); + ammo = strcat(Q" \sAmmo\s: ", strcat(shells, strcat("/", rockets))); } else { - ammo = strcat(" Áííï: ", shells); + ammo = strcat(Q" \sAmmo\s: ", shells); } - st = strcat("Óåîôòù: ", strcat(hp, ammo)); + st = strcat(Q"\sSentry\s: ", strcat(hp, ammo)); st = strpadr(st, 32); - st = strcat(st, strcat("Ìåöåì: ", ftos(self.sentry_ent.weapon))); + st = strcat(st, strcat(Q"\slevel\s: ", ftos(self.sentry_ent.weapon))); } return st; }; -string(entity pl) BuildingToString = -{ +string(entity pl) BuildingToString ={ local string st = ""; + local int dist_percentage; + local string dist_string; + local string hp; + + if (engineer_move && self.sentry_ent) { + hp = strcat(strpadl(ftos(floor(self.sentry_ent.health)), 2), " hp"); + st = strcat(st,strcat(Q"\sSentry\s: ", hp)); + st = strpadr(st, 15); + } if (!buildstatus) return "Building..."; - st = "Âõéìäéîç: "; + st = strcat(st, Q"\sReady\s: "); st = strcat(st, ftos(pl.building_percentage)); st = strcat(st, "%"); + if (!engineer_move) { //disabling max_distance for moving engineer + if (engineer_move) { + dist_percentage = (vlen(self.origin - self.sentry_ent.origin)/BUILD_SENTRYGUN_MAX_DISTANCE_ENGMOVE)*100; + st = strpadr(st, 26); + st = strcat(st, Q"\sDistance\s:"); + dist_string = strcat(itos(dist_percentage),"%"); + dist_string = strcat(Q"\x10", dist_string ,Q"\x11"); + st = strcat(st, dist_string); + } + } return st; }; diff --git a/subs.qc b/ssqc/subs.qc similarity index 89% rename from subs.qc rename to ssqc/subs.qc index fefa07578..d0bae51af 100644 --- a/subs.qc +++ b/ssqc/subs.qc @@ -1,6 +1,22 @@ void (entity Goal, entity AP) DoGoalWork; void (entity Goal, entity AP) DoGroupWork; +void FO_SetModel(entity e, string fomdl) +{ + //e.dimension_seen = e.dimension_seen - (e.dimension_seen & DMN_FLASH); + e.dimension_seen = DMN_NOFLASH; + setmodel(e, fomdl); +} + +void FO_Sound(entity e, float chan, string samp, float vol, float atten) +{ + float olddimens = e.dimension_seen; + // make the sound go to no flash, so it isn't heard by those that are flashed + e.dimension_seen = DMN_NOFLASH; + sound(e, chan, samp, vol, atten); + e.dimension_seen = olddimens; +} + void () SUB_Null = { }; @@ -148,7 +164,7 @@ void () SUB_UseTargets = { CenterPrint(activator, self.message); if (!self.noise) - sound(activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + FO_Sound(activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); } if (activator.classname == "player") { diff --git a/ssqc/teamplay.qc b/ssqc/teamplay.qc new file mode 100644 index 000000000..05fe9ae76 --- /dev/null +++ b/ssqc/teamplay.qc @@ -0,0 +1,103 @@ +static void Apply(entity* targets, int tcount, int(entity) filter_fn, + void(entity) apply_fn) { + for (int i = 0; i < tcount; i++) + if (filter_fn(targets[i])) + apply_fn(targets[i]); +} + +static entity EntityOwner(entity e) { + if (e.real_owner != world) + return e.real_owner; + else + return e.owner; +} + +static void ApplyList(entity* targets, int count, + int(entity) player_filter_fn, void(entity) player_apply_fn, + int(entity) other_filter_fn, void(entity) other_apply_fn) { + for (int i = 0; i < count; i++) { + entity e = targets[i]; + + if (e.classname == "player") { + if (player_filter_fn(e)) + player_apply_fn(e); + } else if (EntityOwner(e).classname == "player") { + if (other_filter_fn(e)) + other_apply_fn(e); + } + } +} + +static void ApplyRadius(vector org, float rad, + int(entity) player_filter_fn, void(entity) player_apply_fn, + int(entity) other_filter_fn, void(entity) other_apply_fn) { + int count; + entity* ents = findradius_list(org, rad, count); + ApplyList(ents, count, + player_filter_fn, player_apply_fn, other_filter_fn, other_apply_fn); +} + +void RemovePrimedGrenades(entity player); + +static void EffStrip(entity player) { + player.ammo_cells = 0; + player.ammo_rockets = 0; + player.ammo_shells = 0; + player.ammo_nails = 0; + + player.no_grenades_1 = player.no_grenades_2 = 0; + player.current_slot = player.queue_slot = MakeSlot(4); + + RemovePrimedGrenades(player); + + string msg = "The enemy captured! You are afflicted by the Curse of Isma!"; + stuffcmd(player, "bf\n"); + sprint(player, PRINT_HIGH, msg, "\n"); + centerprint(player, msg); +} + +float IsEngEnt(entity ent); +void RemoveEngEnt(entity bld, float explode); + +static void EffRemove(entity non_player) { + if (non_player.classname == "player") { + printf("ERROR! Tried to remove player [%s]\n", non_player.netname); + return; + } + + pointparticles(particleeffectnum("fo_airblast"), non_player.origin); + if (IsEngEnt(non_player)) + RemoveEngEnt(non_player, FALSE); + else + dremove(non_player); +} + +static int(entity p) FilTeamEQ[] = { + { return p.team_no == 1; }, + { return p.team_no == 2; }, + { return p.team_no == 3; }, + { return p.team_no == 4; }, +}; + +static int(entity p) FilTeamNEQ[] = { + { return p.team_no != 1; }, + { return p.team_no != 2; }, + { return p.team_no != 3; }, + { return p.team_no != 4; }, +}; + +static int(entity p) FilOwnerTeamNEQ[] = { + { return EntityOwner(p).team_no != 1; }, + { return EntityOwner(p).team_no != 2; }, + { return EntityOwner(p).team_no != 3; }, + { return EntityOwner(p).team_no != 4; }, +}; + +void TeamPlay_Cap(vector origin, entity player) { + if (!org_game || !new_balance || !cap_strip) + return; + + ApplyRadius(origin, 1500, + FilTeamNEQ[player.team_no - 1], EffStrip, + FilOwnerTeamNEQ[player.team_no - 1], EffRemove); +} diff --git a/tfort.qc b/ssqc/tfort.qc similarity index 55% rename from tfort.qc rename to ssqc/tfort.qc index 1407aa310..8f2d6b8f0 100644 --- a/tfort.qc +++ b/ssqc/tfort.qc @@ -36,51 +36,88 @@ void () AutoId = { CF_Identify(self.owner, self.owner.autoid_type); } - self.nextthink = time + 0.3; + self.nextthink = time + 0.03; }; void () UseSpecialSkill = { local vector src; self.impulse = 0; - if (self.playerclass == PC_SCOUT) - self.impulse = TF_DASH; - else if (self.playerclass == PC_SNIPER) - self.impulse = TF_ZOOMTOGGLE; - else if (self.playerclass == PC_DEMOMAN) - self.impulse = TF_PB_DETONATE; - else if (self.playerclass == PC_MEDIC) - self.impulse = TF_MEDIC_AURA_TOGGLE; - else if (self.playerclass == PC_HVYWEAP) { - if (self.tfstate & TFSTATE_LOCK) - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_LOCK); - else - self.tfstate = self.tfstate | TFSTATE_LOCK; - Status_Refresh(self); - self.impulse = 0; - return; - } else if (self.playerclass == PC_SPY) - self.impulse = TF_SPY_DIE; - else if (self.playerclass == PC_ENGINEER) - self.impulse = TF_ENGINEER_DETDISP; - else if (self.playerclass == PC_UNDEFINED) { - if (self.enemy == world) { - src = self.origin + v_forward * 10; - src_z = self.absmin_z + self.size_z * 0.7; - traceline(src, src + v_forward * 2048, 0, self); - if ((trace_ent != world) && (trace_ent.origin != world.origin)) { - sprint3(self, PRINT_HIGH, "Locked onto ", - trace_ent.classname, "\n"); - self.enemy = trace_ent; - self.camdist = vlen(self.enemy.origin - self.origin); - self.camangle = self.origin - self.enemy.origin; - self.camangle_z = 0 - self.camangle_z; - self.camangle = vectoangles(self.camangle); + switch (self.playerclass) + { + case PC_SCOUT: + self.impulse = TF_DASH; + break; + case PC_DEMOMAN: + self.impulse = TF_PB_DETONATE; + break; + case PC_MEDIC: + self.impulse = TF_MEDIC_AURA_TOGGLE; + break; + case PC_HVYWEAP: + self.impulse = TF_HVYWEAP_LOCK_TOGGLE; + break; + case PC_PYRO: + self.impulse = TF_AIRBLAST; + break; + case PC_SPY: + self.impulse = TF_SPY_DIE; + break; + case PC_ENGINEER: + self.impulse = TF_ENGINEER_TOGGLEDISPENSER; + break; + case PC_UNDEFINED: + if (self.enemy == world) { + src = self.origin + v_forward * 10; + src_z = self.absmin_z + self.size_z * 0.7; + traceline(src, src + v_forward * 2048, 0, self); + if ((trace_ent != world) && (trace_ent.origin != world.origin)) { + sprint(self, PRINT_HIGH, "Locked onto ", + trace_ent.classname, "\n"); + self.enemy = trace_ent; + self.camdist = vlen(self.enemy.origin - self.origin); + self.camangle = self.origin - self.enemy.origin; + self.camangle_z = 0 - self.camangle_z; + self.camangle = vectoangles(self.camangle); + } + } else { + sprint(self, PRINT_HIGH, "Removed lock\n"); + self.enemy = world; } - } else { - sprint(self, PRINT_HIGH, "Removed lock\n"); - self.enemy = world; - } + break; + } +}; + +void () UseSpecialSkill2 = { + local vector src; + + self.impulse = 0; + switch (self.playerclass) + { + case PC_SCOUT: + self.impulse = TF_SCAN; + break; + case PC_SNIPER: + break; + case PC_SOLDIER: + break; + case PC_DEMOMAN: + self.impulse = TF_DETPACK_5; + break; + case PC_MEDIC: + break; + case PC_HVYWEAP: + break; + case PC_PYRO: + break; + case PC_SPY: + self.impulse = TF_DISGUISE_LAST_SPAWNED; + break; + case PC_ENGINEER: + self.impulse = TF_ENGINEER_TOGGLESENTRY; + break; + case PC_UNDEFINED: + break; } }; @@ -93,24 +130,21 @@ void () RemoveAutoIdTimer = { } }; -void () RemoveGrenadeTimers = { - local entity te = find(world, classname, "gtimer"); - while (te != world) { - if (te.owner == self) - dremove(te); - te = find(te, classname, "gtimer"); - } -}; +void RemovePrimedGrenades(entity player) { + entity* ents; + int count; + + ents = find_list(classname, "primetimer", EV_STRING, count); + for (int i = 0; i < count; i++) + if (ents[i].owner == player) + dremove(ents[i]); + + player.tfstate &= ~(TFSTATE_GREN1_PRIMED | TFSTATE_GREN2_PRIMED); +} void () RemovePrimeTimers = { - if (!drop_grenades) { - local entity te = find(world, classname, "primetimer"); - while (te != world) { - if (te.owner == self) - dremove(te); - te = find(te, classname, "primetimer"); - } - } + if (!drop_grenades) + RemovePrimedGrenades(self); }; void () RemoveGasTimers = { @@ -140,11 +174,12 @@ void (float inp) TeamFortress_ChangeClass = { if ((intermission_running != 0) || (intermission_exittime > time)) return; + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); if (self.playerclass != 0) { - if ((deathmatch != 3) && (cb_prematch_time < time)) + if ((deathmatch != 3) && (!cb_prematch)) return; - if (self.playerclass == inp) { + if (self.playerclass == inp && !(self.tfstate & TFSTATE_RANDOMPC) && self.nextpc == inp) { sprint(self, PRINT_HIGH, "You are already playing as a "); TeamFortress_PrintClassName(self, inp, 0); return; @@ -153,30 +188,36 @@ void (float inp) TeamFortress_ChangeClass = { return; } - if (TeamFortress_TeamIsCivilian(self.team_no)) { - sprint(self, PRINT_HIGH, "You cannot change class\n"); - return; - } - if (!IsLegalClass(inp)) { - sprint(self, PRINT_HIGH, - "Your team cannot play that class\n"); - TeamFortress_DisplayLegalClasses(); - return; - } - if ((spy_off == 1) && (inp == 8)) { - sprint(self, PRINT_HIGH, - "The spy class has been disabled on the server by the administrator\n"); - return; - } - if (CF_ClassIsRestricted(self.team_no, inp)) { - sprint(self, PRINT_HIGH, - "Your team already has enough of that class\n"); - return; + if(inp > 0) { + if (TeamFortress_TeamIsCivilian(self.team_no) && self.playerclass == PC_CIVILIAN) { + sprint(self, PRINT_HIGH, "You cannot change class\n"); + return; + } + if (!IsLegalClass(inp) && !override_mapclasses) { + sprint(self, PRINT_HIGH, + "Your team cannot play that class\n"); + TeamFortress_DisplayLegalClasses(); + return; + } + if ((spy_off == 1) && (inp == 8)) { + sprint(self, PRINT_HIGH, + "The spy class has been disabled on the server by the administrator\n"); + return; + } + if (self.playerclass != inp && CF_ClassIsRestricted(self.team_no, inp)) { + sprint(self, PRINT_HIGH, + "Your team already has enough of that class\n"); + return; + } } + self.nextpc = inp; - if (self.health == self.max_health && (self.spawn_time + 10) > time && CloseToSpawnPoint() && self.suicide_time <= time) { + if (self.health == self.max_health && (self.spawn_time + 10) > time && CloseToSpawnPoint()) { self.has_changedclass = 1; - ClientKill(0, 1); + self.clientkillforce = 0; + self.clientkillfree = 1; + self.spawn_at_last_spawn_spot = 1; + ClientKill(); self.has_changedclass = 0; self.suicide_time = time; } else { @@ -186,6 +227,7 @@ void (float inp) TeamFortress_ChangeClass = { self.immune_to_check = time + 10; return; } + if (teamplay && (self.team_no == 0)) { if (toggleflags & TFLAG_AUTOTEAM) { if (TeamFortress_TeamPutPlayerInTeam() == 0) @@ -199,7 +241,7 @@ void (float inp) TeamFortress_ChangeClass = { sprint(self, PRINT_HIGH, "You have no lives left\n"); return; } - if (!IsLegalClass(inp) && (inp != 11)) { + if (!IsLegalClass(inp) && !override_mapclasses && (inp != 11)) { sprint(self, PRINT_HIGH, "You cannot play that class on this map\n"); TeamFortress_DisplayLegalClasses(); @@ -215,9 +257,12 @@ void (float inp) TeamFortress_ChangeClass = { return; } TeamFortress_ExecClassScript(self); + + // FIXME - should this be using putclientinserver instead? self.playerclass = inp; - self.nextpc = 0; + //self.nextpc = 0; + self.nextpc = inp; self.takedamage = 2; self.movetype = 3; self.flags = FL_CLIENT | FL_ONGROUND; @@ -229,13 +274,12 @@ void (float inp) TeamFortress_ChangeClass = { self.origin = spot.origin + '0 0 1'; self.angles = spot.angles; self.fixangle = 1; - CheckClientSpawn(); setmodel(self, string_null); modelindex_null = self.modelindex; setmodel(self, "progs/eyes.mdl"); modelindex_eyes = self.modelindex; - setmodel(self, "progs/player.mdl"); + FO_SetModel(self, "progs/player.mdl"); modelindex_player = self.modelindex; setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); self.view_ofs = '0 0 22'; @@ -250,7 +294,7 @@ void (float inp) TeamFortress_ChangeClass = { self.playerclass = 1 + floor(random() * 9); } if ((spot.classname == "info_player_teamspawn") && - (cb_prematch_time < time)) { + (!cb_prematch)) { if (spot.items != 0) { te = Finditem(spot.items); if (te) @@ -291,23 +335,39 @@ void (float inp) TeamFortress_ChangeClass = { TeamFortress_SetSpeed(self); TeamFortress_SetSkin(self); TeamFortress_ExecClassScript(self); - W_ChangeWeapon(1); + W_ChangeToBestWeapon(); if (cease_fire) { sprint(self, PRINT_HIGH, "\n\nCease fire mode\n"); self.immune_to_check = time + 10; - self.tfstate = self.tfstate | TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(self); + self.tfstate |= TFSTATE_CANT_MOVE; } self.spawn_time = time; + + local float autodisguise = FO_GetUserSetting(self, "autodisguise", "ad", "off"); + if (self.playerclass == PC_SPY) { + switch(autodisguise) { + case 1: + FO_Spy_DisguiseLastSpawned(self, FALSE); + break; + case 2: + FO_Spy_DisguiseLast(self, FALSE); + break; + } + } }; void () TeamFortress_DisplayLegalClasses = { local float gotone; local float ill; + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); sprint(self, PRINT_HIGH, "Legal classes for your team are:\n"); gotone = 0; - ill = TeamFortress_TeamGetIllegalClasses(self.team_no); + if(override_mapclasses) { + ill = -1; //Nothing is illegal unless restricted on server + } else { + ill = TeamFortress_TeamGetIllegalClasses(self.team_no); + } if (!(illegalclasses & 1) && !(ill & 1)) { if (gotone) { sprint(self, PRINT_HIGH, ", "); @@ -405,74 +465,41 @@ void () TeamFortress_Inventory = { fl = self.no_grenades_1; if (fl > 0) { - sprint(self, PRINT_HIGH, ftos(fl)); - if (self.tp_grenades_1 == GR_TYPE_NORMAL) - sprint(self, PRINT_HIGH, " normal grenade"); - else if (self.tp_grenades_1 == GR_TYPE_NAIL) - sprint(self, PRINT_HIGH, " nail grenade"); - else if (self.tp_grenades_1 == GR_TYPE_MIRV) - sprint(self, PRINT_HIGH, " mirv grenade"); - else if (self.tp_grenades_1 == GR_TYPE_NAPALM) - sprint(self, PRINT_HIGH, " napalm grenade"); - else if (self.tp_grenades_1 == GR_TYPE_FLARE) - sprint(self, PRINT_HIGH, " flare"); - else if (self.tp_grenades_1 == GR_TYPE_GAS) - sprint(self, PRINT_HIGH, " hallucinogenic grenade"); - else if (self.tp_grenades_1 == GR_TYPE_EMP) - sprint(self, PRINT_HIGH, " EMP grenade"); - else if (self.tp_grenades_1 == GR_TYPE_CALTROP) - sprint(self, PRINT_HIGH, " caltrop canister"); - else if (self.tp_grenades_1 == GR_TYPE_FLASH) - sprint(self, PRINT_HIGH, " flash grenade"); - + string g1 = strcat(ftos(fl), " ", FO_GrenName(FO_ClassGren(self.playerclass, 0)->id)); if (fl > 1) - sprint(self, PRINT_HIGH, "s"); - sprint(self, PRINT_HIGH, "\n"); + g1 = strcat(g1, "s"); + + g1 = strcat(g1, "\n"); + sprint(self, PRINT_HIGH, g1); } fl = self.no_grenades_2; if (fl > 0) { - sprint(self, PRINT_HIGH, ftos(fl)); - if (self.tp_grenades_2 == GR_TYPE_NORMAL) - sprint(self, PRINT_HIGH, " normal grenade"); - else if (self.tp_grenades_2 == GR_TYPE_CONCUSSION) - sprint(self, PRINT_HIGH, " concussion grenade"); - else if (self.tp_grenades_2 == GR_TYPE_NAIL) - sprint(self, PRINT_HIGH, " nail grenade"); - else if (self.tp_grenades_2 == GR_TYPE_MIRV) - sprint(self, PRINT_HIGH, " mirv grenade"); - else if (self.tp_grenades_2 == GR_TYPE_NAPALM) - sprint(self, PRINT_HIGH, " napalm grenade"); - else if (self.tp_grenades_2 == GR_TYPE_FLARE) - sprint(self, PRINT_HIGH, " flare"); - else if (self.tp_grenades_2 == GR_TYPE_GAS) - sprint(self, PRINT_HIGH, " hallucinogenic grenade"); - else if (self.tp_grenades_2 == GR_TYPE_EMP) - sprint(self, PRINT_HIGH, " EMP grenade"); - else if (self.tp_grenades_2 == GR_TYPE_FLASH) - sprint(self, PRINT_HIGH, " flash grenade"); - + string g2 = strcat(ftos(fl), " ", FO_GrenName(FO_ClassGren(self.playerclass, 1)->id)); if (fl > 1) - sprint(self, PRINT_HIGH, "s"); - sprint(self, PRINT_HIGH, "\n"); + g2 = strcat(g2, "s"); + + g2 = strcat(g2, "\n"); + sprint(self, PRINT_HIGH, g2); } + if (self.tf_items & NIT_SCANNER) - sprint(self, PRINT_HIGH, "Scanner"); + sprint(self, PRINT_HIGH, "Scanner\n"); - if (self.weapons_carried & WEAP_MEDIKIT) - sprint(self, PRINT_HIGH, "Medikit"); + float current_weapons = FO_WeaponsMask(self); + if (current_weapons & WEAP_MEDIKIT) + sprint(self, PRINT_HIGH, "Medikit\n"); - if (self.weapons_carried & WEAP_DETPACK) { - if (self.ammo_detpack > 0) { - st = ftos(self.ammo_detpack); - sprint(self, PRINT_HIGH, st, " detpack"); - if (self.ammo_detpack > 1) - sprint(self, PRINT_HIGH, "s"); - } + if (self.ammo_detpack > 0) { + st = ftos(self.ammo_detpack); + sprint(self, PRINT_HIGH, st, " detpack"); + if (self.ammo_detpack > 1) + sprint(self, PRINT_HIGH, "s"); + sprint(self, PRINT_HIGH, "\n"); } if (self.playerclass == PC_SPY && invis_only) { - sprint(self, PRINT_HIGH, "Invisibility device"); + sprint(self, PRINT_HIGH, "Invisibility device\n"); } if (self.armorvalue > 0) @@ -513,7 +540,7 @@ void (string ps_description, float pf_setting, string ps_last, float pf_bool) CF void () TeamFortress_ShowTF = { local string st; - sprint(self, PRINT_HIGH, "\nThis server is running Classic Fortress "); + sprint(self, PRINT_HIGH, "\nThis server is running FortressOne "); sprint(self, PRINT_HIGH, VER); sprint(self, PRINT_HIGH, "\n\n"); @@ -548,7 +575,7 @@ void () TeamFortress_ShowTF = { sprint(self, PRINT_HIGH, "Respawn delay "); if (toggleflags & TFLAG_RESPAWNDELAY) { - st = ftos(respawn_delay_time); + st = ftos(Role_None.respawn_delay_time); } else { st = "off"; } @@ -570,37 +597,64 @@ void () TeamFortress_ShowTF = { sprint(self, PRINT_HIGH, "Full teamscore off\n"); } - sprint(self, PRINT_HIGH, "\n== Classic Fortress ==\n"); + sprint(self, PRINT_HIGH, "\n== FortressOne Server ==\n"); CF_PrintSetting("Spawn with full health/armor", spawnfull, "", 1); CF_PrintSetting("Stock players with full health/armor", stockfull, "", 1); + CF_PrintSetting("Stock player on cap", stock_on_cap, "", 1); + CF_PrintSetting("Strip chasers on cap", cap_strip, "", 1); + CF_PrintSetting("Stock reloadable clip", stock_reload, "", 1); CF_PrintSetting("Old dropflag behaviour", old_dropflag, "", 1); CF_PrintSetting("Remember weapon", remember_weapon, "", 1); CF_PrintSetting("Pick up discarded ammo", discammo_pickup, "", 1); - CF_PrintSetting("Reload clip tick", reload_cliptick, "", 1); CF_PrintSetting("ID extras", id_extended, "", 1); CF_PrintSetting("Display class tips", classtips, "", 1); CF_PrintSetting("Old grenades", old_grens, "", 1); CF_PrintSetting("Drop grenades on ground", drop_grenades, "", 1); CF_PrintSetting("Drop grenades in pack", drop_grenpack, "", 1); + if (drop_grenpack) { CF_PrintSetting("- Grenades type 1", drop_gren1, "", 0); CF_PrintSetting("- Grenades type 2", drop_gren2, "", 0); } - CF_PrintSetting("Grenade timers", grentimers, "", 1); + + CF_PrintSetting("Scout max grenades type 1 (caltrops)", Role_None.gren1_limits[1], "", PC_SCOUT_GRENADE_MAX_1); + CF_PrintSetting("Sniper max grenades type 1 (normal)", Role_None.gren1_limits[2], "", PC_SNIPER_GRENADE_MAX_1); + CF_PrintSetting("Soldier max grenades type 1 (normal)", Role_None.gren1_limits[3], "", PC_SOLDIER_GRENADE_MAX_1); + CF_PrintSetting("Demoman max grenades type 1 (normal)", Role_None.gren1_limits[4], "", PC_DEMOMAN_GRENADE_MAX_1); + CF_PrintSetting("Medic max grenades type 1 (normal)", Role_None.gren1_limits[5], "", PC_MEDIC_GRENADE_MAX_1); + CF_PrintSetting("Heavy Weapons max grenades type 1 (normal)", Role_None.gren1_limits[6], "", PC_HVYWEAP_GRENADE_MAX_1); + CF_PrintSetting("Pyro max grenades type 1 (normal)", Role_None.gren1_limits[7], "", PC_PYRO_GRENADE_MAX_1); + CF_PrintSetting("Spy max grenades type 1 (normal)", Role_None.gren1_limits[8], "", PC_SPY_GRENADE_MAX_1); + CF_PrintSetting("Engineer max grenades type 1 (normal)", Role_None.gren1_limits[9], "", PC_ENGINEER_GRENADE_MAX_1); + + CF_PrintSetting("Scout max grenades type 2 (concussion)", Role_None.gren2_limits[1], "", PC_SCOUT_GRENADE_MAX_2); + CF_PrintSetting("Sniper max grenades type 2 (flare)", Role_None.gren2_limits[2], "", PC_SNIPER_GRENADE_MAX_2); + CF_PrintSetting("Soldier max grenades type 2 (nail/shock)", Role_None.gren2_limits[3], "", PC_SOLDIER_GRENADE_MAX_2); + CF_PrintSetting("Demoman max grenades type 2 (mirv)", Role_None.gren2_limits[4], "", PC_DEMOMAN_GRENADE_MAX_2); + CF_PrintSetting("Medic max grenades type 2 (concussion/blast)", Role_None.gren2_limits[5], "", PC_MEDIC_GRENADE_MAX_2); + CF_PrintSetting("Heavy Weapons max grenades type 2 (mirv)", Role_None.gren2_limits[6], "", PC_HVYWEAP_GRENADE_MAX_2); + CF_PrintSetting("Pyro max grenades type 2 (napalm)", Role_None.gren2_limits[7], "", PC_PYRO_GRENADE_MAX_2); + CF_PrintSetting("Spy max grenades type 2 (gas)", Role_None.gren2_limits[8], "", PC_SPY_GRENADE_MAX_2); + CF_PrintSetting("Engineer max grenades type 2 (emp)", Role_None.gren2_limits[9], "", PC_ENGINEER_GRENADE_MAX_2); + CF_PrintSetting("Distance based cuss", distance_based_cuss_duration, "", 1); + sprint(self, PRINT_HIGH, "Concussion effect lasts "); + sprint(self, PRINT_HIGH, ftos(cussgrentime)); + sprint(self, PRINT_HIGH, " seconds"); sprint(self, PRINT_HIGH, "\n== Scout ==\n"); CF_PrintSetting("Scout dash ability", scoutdash, "", 1); sprint(self, PRINT_HIGH, "\n== Sniper ==\n"); - CF_PrintSetting("Sniper reload", sniperreload, "", 1); CF_PrintSetting("Old sniper rifle range", old_sniperrange, "", 1); CF_PrintSetting("Sniper Rifle power in status bar", sniperpower, "", 1); - CF_PrintSetting("Reload percentage in status bar", sniperreloadpercent, "", 1); + + sprint(self, PRINT_HIGH, "\n== Medic ==\n"); + CF_PrintSetting("Medic immune to concussion grenade", medicnocuss, "", 1); sprint(self, PRINT_HIGH, "\n== Demolitions Man ==\n"); - if (detpipe_limit > 0) { - CF_PrintSetting("Pipebomb limit (per demoman)", detpipe_limit, "", 0); - } else if (detpipe_limit == 0) { + if (Role_None.detpipe_limit > 0) { + CF_PrintSetting("Pipebomb limit (per demoman)", Role_None.detpipe_limit, "", 0); + } else if (Role_None.detpipe_limit == 0) { sprint(self, PRINT_HIGH, "Pipebomb limit (per demoman): 0 (disallow)\n"); } else { sprint(self, PRINT_HIGH, "Pipebomb limit (per demoman): unlimited\n"); @@ -612,11 +666,10 @@ void () TeamFortress_ShowTF = { } else { sprint(self, PRINT_HIGH, "Pipebomb limit (team): unlimited\n"); } - CF_PrintSetting("Old pipebomb cooldown", old_pipecooldown, "", 1); + CF_PrintSetting("Pipebomb cooldown time", pipecooldown_time, "", 0.5); sprint(self, PRINT_HIGH, "\n== Combat Medic ==\n"); CF_PrintSetting("Medic aura ability", medicaura, "", 1); - CF_PrintSetting("Old medikit behaviour", old_medikit, "", 1); CF_PrintSetting("Old bioweapon damage", old_sniperrange, "", 1); sprint(self, PRINT_HIGH, "\n== Heavy Weapons Guy ==\n"); @@ -626,7 +679,6 @@ void () TeamFortress_ShowTF = { CF_PrintSetting("Allow Assault Cannon while moving", cannon_move, "", 1); } CF_PrintSetting("Spin Assault Cannon while moving", cannon_movespin, "", 1); - CF_PrintSetting("Allow Assault Cannon concussion", cannon_conc, "", 1); if (cannon_movespin == 0) { sprint(self, PRINT_HIGH, "Assault Cannon accuracy: CF style\n"); } else if (cannon_movespin == 1) { @@ -640,389 +692,267 @@ void () TeamFortress_ShowTF = { sprint(self, PRINT_HIGH, "\n== Engineer ==\n"); CF_PrintSetting("Old spanner behaviour", old_spanner, "", 1); - CF_PrintSetting("Old railgun behaviour", old_railgun, "", 1); CF_PrintSetting("Old dispenser behaviour", old_dispenser, "", 1); - CF_PrintSetting("Bigger dispenser explosions", disp_explosion, "", 1); CF_PrintSetting("Build status in status bar", buildstatus, "", 1); sprint(self, PRINT_HIGH, "\n== Spy ==\n"); CF_PrintSetting("Allow feign death while airborne", feign_air, "", 1); CF_PrintSetting("Drop feign backpack", feign_pack, "", 1); CF_PrintSetting("Print feign death message", feign_msg, "", 1); + CF_PrintSetting("Override Map Class Limits", override_mapclasses, "", 0); + CF_PrintSetting("Solid Detpack", solid_detpack, "", 0); + CF_PrintSetting("EMP Blocked By Walls", walls_block_emp, "", 0); if (server_faithful) { sprint(self, PRINT_HIGH, "\nThis server is running faithful Team Fortress settings.\n"); } else if (server_default) { - sprint(self, PRINT_HIGH, "\nThis server is running default Classic Fortress settings.\n"); + sprint(self, PRINT_HIGH, "\nThis server is running default FortressOne Server settings.\n"); + } + else if (server_huetf) { + sprint(self, PRINT_HIGH, "\nThis server is running HueTF FortressOne Server settings.\n"); } }; -void () TeamFortress_GrenadePrimed; +void () NormalGrenadeTouch = { + if (other == self.owner) + return; + + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; -void () GrenadeTimer = { - self.heat = self.heat - 1; - self.nextthink = time + 1; - self.owner.StatusGrenTime = self.heat; - Status_Refresh(self.owner); +static struct stg_table_entry { + int id; + void() touch; + void() think; + float no_expiry; +}; - if (!self.heat) { - dremove(self); - } +stg_table_entry stg_table[] = { + { GREN_NORMAL, NormalGrenadeTouch, FO_T_GrenExplode}, + { GREN_CONC, ConcussionGrenadeTouch, FO_T_GrenExplode}, + { GREN_BLAST, BlastGrenadeTouch, BlastGrenadeExplode}, + { GREN_NAIL, NailGrenadeTouch, NailGrenadeExplode}, + { GREN_SHOCK, ShockGrenadeTouch, ShockGrenadeExplode, TRUE}, + { GREN_BURST, BurstGrenadeTouch, BurstGrenadeExplode}, + { GREN_MIRV, MirvGrenadeTouch, MirvGrenadeExplode}, + { GREN_NAPALM, NapalmGrenadeTouch, NapalmGrenadeExplode1, TRUE}, + { GREN_FLARE, FlareGrenadeTouch, FlareGrenadeExplode}, + { GREN_GAS, GasGrenadeTouch, GasGrenadeExplode1, TRUE}, + { GREN_EMP, EMPGrenadeTouch, EMPGrenadeExplode}, + { GREN_FLASH, FlashGrenadeTouch, FlashGrenadeExplode}, + { GREN_CALTROP, CanisterTouch, ScatterCaltrops}, }; +void (entity timer) FO_SpawnThrownGrenade = { + local entity user = timer.owner; + user.tfstate &= ~TFSTATE_GREN_MASK_ALL; + user.last_throw = time; + + UpdateClientGrenadeThrown(user); + + KickPlayer(-1, user); + entity proj = FOProj_Create(FPP_HANDGRENADE); + proj.owner = user; + proj.movetype = MOVETYPE_BOUNCE; + proj.solid = SOLID_BBOX; + proj.classname = "grenade"; + makevectors(user.v_angle); + if (user.deadflag) { + if (timer.weapon == GREN_NORMAL) + proj.velocity = '0 0 200'; + else + return; + } else if (user.v_angle_x) { + proj.velocity = + v_forward * 600 + v_up * 200 + crandom() * v_right * 10 + + crandom() * v_up * 10; + } else { + proj.velocity = aim(user, 10000); + proj.velocity = proj.velocity * 600; + proj.velocity_z = 200; + } + proj.angles = vectoangles(proj.velocity); + proj.think = SUB_Null; + proj.nextthink = timer.heat; + + int gtype = timer.fpp.gren_type; + FO_GrenInfo* gdesc = FO_GrenDesc(gtype); + stg_table_entry* ste = &stg_table[gtype - GREN_FIRST]; + + proj.fpp.gren_type = gtype; + if (!ste->no_expiry) + proj.fpp.expires_at = timer.heat; + proj.skin = gdesc->skin; + proj.touch = ste->touch; + proj.think = ste->think; + + setorigin(proj, user.origin); + FOProj_Finalize(proj); +} + void (float inp) TeamFortress_PrimeThrowGrenade = { - if ((self.tfstate & TFSTATE_GRENPRIMED) || - (self.tfstate & TFSTATE_GRENTHROWING)) + if (self.tfstate & TFSTATE_GREN_MASK_PRIMED) TeamFortress_ThrowGrenade(); - else { - TeamFortress_PrimeGrenade(inp); + else + TeamFortress_PrimeGrenade(inp, TRUE); - if ( ((inp == 1 && self.tp_grenade_switch != 1) || (inp == 2 && self.tp_grenade_switch == 1)) - && self.tp_grenades_1 == GR_TYPE_CALTROP) + if (FO_ClassGren(self.playerclass, inp - 1)->id == GREN_CALTROP) TeamFortress_ThrowGrenade(); - } -}; +} -void (float inp) TeamFortress_PrimeGrenade = { - local float gtype; - local string gs; - local string ptime; - local entity tGrenade; - local entity timer; - gtype = 0; +void () FO_GrenadeThink; - if ((self.tfstate & TFSTATE_GRENPRIMED) || - (self.tfstate & TFSTATE_GRENTHROWING)) +// is_player defines whether this originated from the player or server. +void TeamFortress_PrimeGrenade(float inp, float is_player) { + if (self.tfstate & TFSTATE_GREN_MASK_PRIMED) return; - if ( (inp == 1 && self.tp_grenade_switch != 1) - || (inp == 2 && self.tp_grenade_switch == 1)) { - gtype = self.tp_grenades_1; - - if (self.tp_grenades_1 == 2) - gs = "Concussion grenade"; - else if (self.tp_grenades_1 == 3) - gs = "Nail grenade"; - else if (self.tp_grenades_1 == 4) - gs = "Mirv grenade"; - else if (self.tp_grenades_1 == 5) - gs = "Napalm grenade"; - else if (self.tp_grenades_1 == 6) - gs = "Flare"; - else if (self.tp_grenades_1 == 7) - gs = "Gas grenade"; - else if (self.tp_grenades_1 == 8) - gs = "EMP grenade"; - else if (self.tp_grenades_1 == 9) - gs = "Flash grenade"; - else if (self.tp_grenades_1 == 10) - gs = "Caltrop canister"; - else - gs = "Grenade"; - - if (self.no_grenades_1 > 0) { - self.has_throwngren = TRUE; - self.no_grenades_1 = self.no_grenades_1 - 1; - if (gtype == 6) { - newmis = spawn(); - newmis.owner = self; - newmis.movetype = 6; - newmis.solid = 2; - newmis.classname = "grenade"; - makevectors(self.v_angle); - newmis.velocity = (v_forward * 600) + (v_up * 25); - newmis.velocity = newmis.velocity * 700; - newmis.angles = vectoangles(newmis.velocity); - newmis.weapon = self.team_no; - newmis.think = FlareGrenadeExplode; - newmis.nextthink = time + 0.8; - newmis.touch = FlareGrenadeTouch; - newmis.skin = 1; - newmis.mdl = "flare"; - setmodel(newmis, "progs/flare.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin); - return; - } - if (gtype == GR_TYPE_CALTROP) { - ptime = ftos(0.5); - sprint(self, PRINT_HIGH, "Opening ", gs, "...\n"); + if (no_fire_mode) + return; + + FO_GrenInfo* gdesc = FO_PlayerGren(self, inp - 1); + + if (gdesc->id == GREN_NONE) + return; + + float *numg = (inp == 1) ? &self.no_grenades_1 : &self.no_grenades_2; + string gs = FO_GrenName(gdesc->id); + + if (*numg > 0) { + self.has_throwngren = TRUE; + *numg -= 1; + if (gdesc->id == GREN_FLARE) { + newmis = spawn(); + newmis.owner = self; + newmis.movetype = MOVETYPE_TOSS; + newmis.solid = SOLID_BBOX; + newmis.classname = "grenade"; + newmis.fpp.gren_type = GREN_FLARE; + makevectors(self.v_angle); + if (self.v_angle_x) { + newmis.velocity = v_forward * 1200 + v_up * 200; } else { - ptime = ftos(3); - sprint(self, PRINT_HIGH, gs, " primed, ", ptime, - " seconds...\n"); + newmis.velocity = aim(self, 10000); + newmis.velocity = newmis.velocity * 1200; + newmis.velocity_z = 75; } - } else { - sprint(self, PRINT_HIGH, "No ", gs, "s left\n"); + newmis.angles = vectoangles(newmis.velocity); + newmis.weapon = self.team_no; + newmis.think = FlareGrenadeExplode; + newmis.nextthink = time + 0.8; + newmis.touch = FlareGrenadeTouch; + newmis.skin = 1; + newmis.mdl = "flare"; + FO_SetModel(newmis, "progs/flare.mdl"); + setsize(newmis, '0 0 0', '0 0 0'); + setorigin(newmis, self.origin); return; - } - } - if ( (inp == 2 && self.tp_grenade_switch != 1) - || (inp == 1 && self.tp_grenade_switch == 1)) { - gtype = self.tp_grenades_2; - - if (self.tp_grenades_2 == 2) - gs = "Concussion grenade"; - else if (self.tp_grenades_2 == 3) - gs = "Nail grenade"; - else if (self.tp_grenades_2 == 4) - gs = "Mirv grenade"; - else if (self.tp_grenades_2 == 5) - gs = "Napalm grenade"; - else if (self.tp_grenades_2 == 6) - gs = "Flare"; - else if (self.tp_grenades_2 == 7) - gs = "Gas grenade"; - else if (self.tp_grenades_2 == 8) - gs = "EMP grenade"; - else if (self.tp_grenades_2 == 9) - gs = "Flash grenade"; - else - gs = "Grenade"; - - if (self.no_grenades_2 > 0) { - self.has_throwngren = TRUE; - self.no_grenades_2 = self.no_grenades_2 - 1; - if (gtype == 6) { - newmis = spawn(); - newmis.owner = self; - newmis.movetype = 6; - newmis.solid = 2; - newmis.classname = "grenade"; - makevectors(self.v_angle); - if (self.v_angle_x) { - newmis.velocity = v_forward * 1200 + v_up * 200; - } else { - newmis.velocity = aim(self, 10000); - newmis.velocity = newmis.velocity * 1200; - newmis.velocity_z = 75; - } - newmis.angles = vectoangles(newmis.velocity); - newmis.weapon = self.team_no; - newmis.think = FlareGrenadeExplode; - newmis.nextthink = time + 0.8; - newmis.touch = FlareGrenadeTouch; - newmis.skin = 1; - newmis.mdl = "flare"; - setmodel(newmis, "progs/flare.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin); - return; - } - if (gtype == GR_TYPE_CALTROP) { - ptime = ftos(0.5); - sprint(self, PRINT_HIGH, "Opening ", gs, "...\n"); - } else { - ptime = ftos(3); - sprint(self, PRINT_HIGH, gs, " primed, ", ptime, - " seconds...\n"); - } + } if (gdesc->id == GREN_CALTROP) { + sprint(self, PRINT_HIGH, "Opening ", gs, "...\n"); } else { - sprint(self, PRINT_HIGH, "No ", gs, "s left\n"); - return; + sprint(self, PRINT_HIGH, gs, " primed, 3 seconds...\n"); } + } else { + sprint(self, PRINT_HIGH, "No ", gs, "s left\n"); + return; } - self.tfstate = self.tfstate | 1; - tGrenade = spawn(); + + self.tfstate |= (inp == 1) ? TFSTATE_GREN1_PRIMED : TFSTATE_GREN2_PRIMED; + + entity tGrenade = spawn(); tGrenade.owner = self; - tGrenade.weapon = gtype; + tGrenade.weapon = gdesc->id; + tGrenade.fpp.gren_type = gdesc->id; + tGrenade.classname = "primetimer"; if (inp == 1) tGrenade.impulse = TF_GRENADE_1; else if (inp == 2) tGrenade.impulse = TF_GRENADE_2; - tGrenade.nextthink = time + 0.8; - if (gtype == GR_TYPE_CALTROP) - tGrenade.heat = time + 0.5 + 0.5; + float time_base = max(remote_time(), self.last_throw); + tGrenade.nextthink = time_base + 0.8; + if (gdesc->id == GREN_CALTROP) + tGrenade.heat = time_base + 0.5 + 0.5; else { - tGrenade.heat = time + 3 + 0.8; - - RemoveGrenadeTimers(); - - local float notimers = stof(infokey(self, "nt")); - if (grentimers && notimers != 1) { - timer = spawn(); - timer.nextthink = time + 0.8; - timer.think = GrenadeTimer; - timer.heat = 4; - timer.owner = self; - timer.classname = "gtimer"; - if (!notimers) { - stuffcmd(self, "play grentimer\n"); - } - } + tGrenade.heat = time_base + 3 + 0.8; + + self.primed_gren_type = gdesc->id; + self.primed_gren_exp = tGrenade.heat; + UpdateClientGrenadePrimed(self, gdesc->id, tGrenade.heat); } - tGrenade.think = TeamFortress_GrenadePrimed; -}; + tGrenade.think = FO_GrenadeThink; + self.grenade_timer = tGrenade; + self.last_prime = global_to_client_time(time_base); +} + +void () FO_GrenadeThink = { + local entity user = self.owner; -void () TeamFortress_GrenadePrimed = { - local entity user; + // Claim: These cases do not exist for FO; to be removed once proven true. + if (user.deadflag || user.has_disconnected) { + dprint("BUG: saw deadflag / disconnected in PreciseGrenadePrimed\n"); + dremove(self); + return; + } - user = self.owner; - if (!(user.tfstate & TFSTATE_GRENTHROWING) && !user.deadflag && !user.has_disconnected) { - self.nextthink = time + 0.1; - if (!self.think) + // This timer fires at most 2 times: + // - Once at 0.8s, this is the first time you are allowed to throw. We catch + // GRENTHROWING which latches this state for throws before this point. + // - A second time at the grenade's expiration for the case it was not thrown. + // We remove the timer if the client throws before expiration. + if (time < self.heat) { + if (user.tfstate & TFSTATE_GRENTHROWING) { // Latched throw case. + FO_SpawnThrownGrenade(self); dremove(self); + } else { + // When REWIND_GRENADES is enabled, we slightly time-warp the + // spawned grenade to open up the timing window on being able to + // rewind a throw further. Total time is the same. + float expiry = self.heat + FO_RewindGrenWinDt(self.fpp.gren_type); - if (time > self.heat && self.weapon != GR_TYPE_CALTROP) + // If this fires, we never threw. + self.nextthink = expiry; + } + } else { + if (self.weapon != GREN_CALTROP) TeamFortress_ExplodePerson(); - - return; + dremove(self); } - if (!(user.tfstate & TFSTATE_GRENPRIMED)) - dprint("GrenadePrimed logic error\n"); +} - user.tfstate = user.tfstate - (user.tfstate & TFSTATE_GRENPRIMED); - user.tfstate = user.tfstate - (user.tfstate & TFSTATE_GRENTHROWING); - sound(user, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); - KickPlayer(-1, user); - newmis = spawn(); - newmis.owner = user; - newmis.movetype = 10; - newmis.solid = 2; - newmis.classname = "grenade"; - makevectors(user.v_angle); - if (user.deadflag) { - if (self.weapon == GR_TYPE_NORMAL) - newmis.velocity = '0 0 200'; - else - return; - } else if (user.v_angle_x) { - newmis.velocity = - v_forward * 600 + v_up * 200 + crandom() * v_right * 10 + - crandom() * v_up * 10; +void () FO_ThrowGrenade = { + entity timer = self.grenade_timer; + + float throwtime = time; + if (RewindFlagEnabled(REWIND_GRENADES)) + throwtime = bounded_remote_time(FO_RewindGrenDt(timer.fpp.gren_type)); + + if ((timer.nextthink < timer.heat || throwtime > timer.heat - grenade_lockout) && + !IsClownMode(CLOWN_SPAM_GRENADES)) { + // We do not allow throwing within the 0.8s, or the last 0.1s. + // The former is a priming time, the latter is so that client side + // knockback will be able to reliably predict whether a grenade was held + // (e.g. gren jump) or thrown up to ~200ms ping. + // + // In the first case, we've set THROWING so think will handle. + // In the second, buckle up. } else { - newmis.velocity = aim(user, 10000); - newmis.velocity = newmis.velocity * 600; - newmis.velocity_z = 200; + FO_SpawnThrownGrenade(timer); + dremove(timer); } - newmis.angles = vectoangles(newmis.velocity); - newmis.think = SUB_Null; - newmis.nextthink = self.heat; - if (self.weapon == GR_TYPE_NORMAL) { - newmis.touch = NormalGrenadeTouch; - newmis.think = NormalGrenadeExplode; - newmis.skin = 0; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/hgren2.mdl"); - } else if (self.weapon == GR_TYPE_CONCUSSION) { - newmis.touch = ConcussionGrenadeTouch; - newmis.think = ConcussionGrenadeExplode; - newmis.skin = 1; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/hgren2.mdl"); - } else if (self.weapon == GR_TYPE_NAIL) { - newmis.touch = NailGrenadeTouch; - newmis.think = NailGrenadeExplode; - newmis.skin = 1; - newmis.avelocity = '0 300 0'; - setmodel(newmis, "progs/biggren.mdl"); - } else if (self.weapon == GR_TYPE_MIRV) { - newmis.touch = MirvGrenadeTouch; - newmis.think = MirvGrenadeExplode; - newmis.skin = 0; - newmis.avelocity = '0 300 0'; - setmodel(newmis, "progs/biggren.mdl"); - } else if (self.weapon == GR_TYPE_NAPALM) { - newmis.touch = NapalmGrenadeTouch; - newmis.think = NapalmGrenadeExplode; - newmis.skin = 2; - newmis.avelocity = '0 300 0'; - setmodel(newmis, "progs/biggren.mdl"); - } else if (self.weapon == GR_TYPE_FLARE) { - newmis.touch = FlareGrenadeTouch; - newmis.weapon = self.team_no; - newmis.think = FlareGrenadeExplode; - newmis.skin = 1; - newmis.avelocity = '300 300 300'; - newmis.mdl = "flare"; - setmodel(newmis, "progs/flare.mdl"); - } else if (self.weapon == GR_TYPE_GAS) { - newmis.touch = GasGrenadeTouch; - newmis.think = GasGrenadeExplode; - newmis.skin = 3; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/grenade2.mdl"); - } else if (self.weapon == GR_TYPE_EMP) { - newmis.touch = EMPGrenadeTouch; - newmis.think = EMPGrenadeExplode; - newmis.skin = 4; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/grenade2.mdl"); - } else if (self.weapon == GR_TYPE_CALTROP) { - newmis.touch = CanisterTouch; - newmis.think = ScatterCaltrops; - newmis.skin = 0; - newmis.avelocity = '0 0 0'; - } else if (self.weapon == GR_TYPE_FLASH) { - newmis.touch = FlashGrenadeTouch; - newmis.think = FlashGrenadeExplode; - newmis.skin = 2; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/hgren2.mdl"); - } - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, user.origin); - dremove(self); -}; +} void () TeamFortress_ThrowGrenade = { - if (!(self.tfstate & TFSTATE_GRENPRIMED)) + if ((self.tfstate & TFSTATE_GREN_MASK_PRIMED == 0) || + (self.tfstate & TFSTATE_GRENTHROWING)) return; - self.tfstate = self.tfstate | TFSTATE_GRENTHROWING; -}; - -void () TeamFortress_GrenadeSwitch = { - local string gs; - - if (self.tp_grenade_switch == 1) { - self.tp_grenade_switch = 0; - if (self.tp_grenades_1 == 2) - gs = "Concussion grenade"; - else if (self.tp_grenades_1 == 3) - gs = "Nail grenade"; - else if (self.tp_grenades_1 == 4) - gs = "Mirv grenade"; - else if (self.tp_grenades_1 == 5) - gs = "Napalm grenade"; - else if (self.tp_grenades_1 == 6) - gs = "Flare"; - else if (self.tp_grenades_1 == 7) - gs = "Gas grenade"; - else if (self.tp_grenades_1 == 8) - gs = "EMP grenade"; - else - gs = "Grenade"; - } else { - self.tp_grenade_switch = 1; - if (self.tp_grenades_2 == 2) - gs = "Concussion grenade"; - else if (self.tp_grenades_2 == 3) - gs = "Nail grenade"; - else if (self.tp_grenades_2 == 4) - gs = "Mirv grenade"; - else if (self.tp_grenades_2 == 5) - gs = "Napalm grenade"; - else if (self.tp_grenades_2 == 6) - gs = "Flare"; - else if (self.tp_grenades_2 == 7) - gs = "Gas grenade"; - else if (self.tp_grenades_2 == 8) - gs = "EMP grenade"; - else if (self.tp_grenades_2 == 9) - gs = "Flash grenade"; - else if (self.tp_grenades_2 == 10) - gs = "Caltrop canister"; - else - gs = "Grenade"; - } - if (gs == "Caltrop canister" || gs == "Flare") - sprint(self, PRINT_HIGH, gs, " is now the primary throwable item\n"); - else - sprint(self, PRINT_HIGH, gs, " is now the primary grenade\n"); + self.tfstate |= TFSTATE_GRENTHROWING; + FO_ThrowGrenade(); }; float (float pc) IsLegalClass = { @@ -1053,427 +983,221 @@ float (float pc) IsLegalClass = { else if (pc == PC_RANDOM) bit = TF_ILL_RANDOMPC; + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); if ((illegalclasses & bit) || - (TeamFortress_TeamGetIllegalClasses(self.team_no) & bit)) + (TeamFortress_TeamGetIllegalClasses(self.team_no) & bit && !override_mapclasses)) return (FALSE); return (TRUE); }; +float PmtBlockMaxSpeed(); + +static void SetMaxSpeed(entity p, float speed) { + p.csqc_maxspeed = speed; + if (!PmtBlockMaxSpeed()) + p.maxspeed = speed; +} + void (entity p) TeamFortress_SetSpeed = { - local string sp; - local float tf; - local entity te; + string sp; + float tf; + entity te; - stuffcmd(p, "cl_movespeedkey 1\n"); - if (p.tfstate & TFSTATE_CANT_MOVE || p.is_building == 1 || p.is_detpacking > 0) { -#ifdef STOP_MOUSE_MOVEMENT - stuffcmd(p, "m_forward 0\n"); - stuffcmd(p, "m_side 0\n"); -#endif + if (p.tfstate & TFSTATE_CANT_MOVE) { p.velocity = '0 0 0'; - stuffcmd(p, "cl_backspeed 0\n"); - stuffcmd(p, "cl_forwardspeed 0\n"); - stuffcmd(p, "cl_sidespeed 0\n"); - p.maxspeed = 0; + SetMaxSpeed(p, 0); return; } -#ifdef STOP_MOUSE_MOVEMENT - else { - stuffcmd(p, "m_forward 1\n"); - stuffcmd(p, "m_side 0.8\n"); - } -#endif - if (p.playerclass == PC_SCOUT) { - p.maxfbspeed = PC_SCOUT_MAXSPEED; - p.maxstrafespeed = PC_SCOUT_MAXSTRAFESPEED; - } else if (p.playerclass == PC_SNIPER) { - p.maxfbspeed = PC_SNIPER_MAXSPEED; - p.maxstrafespeed = PC_SNIPER_MAXSTRAFESPEED; - } else if (p.playerclass == PC_SOLDIER) { - p.maxfbspeed = PC_SOLDIER_MAXSPEED; - p.maxstrafespeed = PC_SOLDIER_MAXSTRAFESPEED; - } else if (p.playerclass == PC_DEMOMAN) { - p.maxfbspeed = PC_DEMOMAN_MAXSPEED; - p.maxstrafespeed = PC_DEMOMAN_MAXSTRAFESPEED; - } else if (p.playerclass == PC_MEDIC) { - p.maxfbspeed = PC_MEDIC_MAXSPEED; - p.maxstrafespeed = PC_MEDIC_MAXSTRAFESPEED; - } else if (p.playerclass == PC_HVYWEAP) { - p.maxfbspeed = PC_HVYWEAP_MAXSPEED; - p.maxstrafespeed = PC_HVYWEAP_MAXSTRAFESPEED; - } else if (p.playerclass == PC_PYRO) { - p.maxfbspeed = PC_PYRO_MAXSPEED; - p.maxstrafespeed = PC_PYRO_MAXSTRAFESPEED; - } else if (p.playerclass == PC_CIVILIAN) { - p.maxfbspeed = PC_CIVILIAN_MAXSPEED; - p.maxstrafespeed = PC_CIVILIAN_MAXSTRAFESPEED; - } else if (p.playerclass == PC_SPY) { - p.maxfbspeed = PC_SPY_MAXSPEED; - p.maxstrafespeed = PC_SPY_MAXSTRAFESPEED; - } else if (p.playerclass == PC_ENGINEER) { - p.maxfbspeed = PC_ENGINEER_MAXSPEED; - p.maxstrafespeed = PC_ENGINEER_MAXSTRAFESPEED; - } else if (p.playerclass == PC_UNDEFINED) { -// p.maxfbspeed = 320; -// p.maxstrafespeed = 320; - p.maxfbspeed = 0; - p.maxstrafespeed = 0; - p.maxspeed = 0; + + if (p.playerclass == PC_UNDEFINED) { + // Typically this hits spectators and then spec movetype --> maxspeed + // actually ignored. + SetMaxSpeed(p, SPEC_MAXSPEED); + stuffcmd(p, "cl_movespeedkey 1;cl_backspeed 500; cl_forwardspeed 500; cl_sidespeed 500;cl_upspeed 500;\n"); return; } + + float new_max = Class_MaxSpeed(p.playerclass); + tf = 0; te = find(world, classname, "item_tfgoal"); while ((te != world) && (tf == 0)) { if (te.owner == p) { if (te.goal_activation & TFGI_SLOW) { tf = 1; - p.maxfbspeed = p.maxfbspeed / 2; - p.maxstrafespeed = p.maxstrafespeed / 2; + new_max /= 2; } } te = find(te, classname, "item_tfgoal"); } - if (p.tfstate & TFSTATE_TRANQUILISED) { - p.maxfbspeed = p.maxfbspeed / 2; - p.maxstrafespeed = p.maxstrafespeed / 2; - } - if (p.leg_damage) { - if (p.leg_damage > 6) { - p.leg_damage = 6; - } - p.maxfbspeed = p.maxfbspeed * (10 - p.leg_damage) / 10; - p.maxstrafespeed = p.maxstrafespeed * (10 - p.leg_damage) / 10; - } - if (p.tfstate & TFSTATE_AIMING) { - if (p.maxfbspeed > 80) { - p.maxfbspeed = 80; - } - if (p.maxstrafespeed > 80) { - p.maxstrafespeed = 80; - } - } - sp = ftos(p.maxfbspeed); - stuffcmd(p, "cl_backspeed "); - stuffcmd(p, sp); - stuffcmd(p, "\n"); - stuffcmd(p, "cl_forwardspeed "); - stuffcmd(p, sp); - stuffcmd(p, "\n"); - sp = ftos(p.maxstrafespeed); - stuffcmd(p, "cl_sidespeed "); - stuffcmd(p, sp); - stuffcmd(p, "\n"); - p.maxspeed = p.maxfbspeed; -}; + + if (p.tfstate & TFSTATE_TRANQUILISED) + new_max /= 2; + + if (p.leg_damage) + new_max *= (10 - min(p.leg_damage, 6)) / 10; + +#if 0 + // Clients cooperate to handle TFSTATE_AIMING transitions locally due to the + // frequent edges here and negative maxspeed/pmove interaction (bounds + // client prediction until maxspeed makes it to client, even though released + // on unpredicted earlier frame, leading to lots of skipping). + if (p.tfstate & TFSTATE_AIMING) + new_max = min(new_max, 80); +#endif + + SetMaxSpeed(p, new_max); +} void () TeamFortress_SetHealth = { if (self.playerclass == PC_SCOUT) { - self.max_health = PC_SCOUT_MAXHEALTH; + if (old_hp_armor) + self.max_health = 75; + else + self.max_health = PC_SCOUT_MAXHEALTH; } else if (self.playerclass == PC_SNIPER) { - self.max_health = PC_SNIPER_MAXHEALTH; + if (old_hp_armor) + self.max_health = 90; + else + self.max_health = PC_SNIPER_MAXHEALTH; } else if (self.playerclass == PC_SOLDIER) { - self.max_health = PC_SOLDIER_MAXHEALTH; + if (old_hp_armor) + self.max_health = 100; + else + self.max_health = PC_SOLDIER_MAXHEALTH; } else if (self.playerclass == PC_DEMOMAN) { - self.max_health = PC_DEMOMAN_MAXHEALTH; + if (old_hp_armor) + self.max_health = 90; + else + self.max_health = PC_DEMOMAN_MAXHEALTH; } else if (self.playerclass == PC_MEDIC) { - self.max_health = PC_MEDIC_MAXHEALTH; + if (old_hp_armor) + self.max_health = 90; + else + self.max_health = PC_MEDIC_MAXHEALTH; } else if (self.playerclass == PC_HVYWEAP) { - self.max_health = PC_HVYWEAP_MAXHEALTH; + if (old_hp_armor) + self.max_health = 100; + else + self.max_health = PC_HVYWEAP_MAXHEALTH; } else if (self.playerclass == PC_PYRO) { - self.max_health = PC_PYRO_MAXHEALTH; + if (old_hp_armor) + self.max_health = 100; + else + self.max_health = PC_PYRO_MAXHEALTH; } else if (self.playerclass == PC_CIVILIAN) { - self.max_health = PC_CIVILIAN_MAXHEALTH; + if (old_hp_armor) + self.max_health = 50; + else + self.max_health = PC_CIVILIAN_MAXHEALTH; } else if (self.playerclass == PC_SPY) { - self.max_health = PC_SPY_MAXHEALTH; + if (old_hp_armor) + self.max_health = 90; + else + self.max_health = PC_SPY_MAXHEALTH; } else if (self.playerclass == PC_ENGINEER) { - self.max_health = PC_ENGINEER_MAXHEALTH; + if (old_hp_armor) + self.max_health = 80; + else + self.max_health = PC_ENGINEER_MAXHEALTH; } else { - self.max_health = 1; + self.max_health = 0; self.takedamage = DAMAGE_NO; } self.health = self.max_health; }; -string(entity p) TeamFortress_GetSkin = -{ - local float tn; - local float pc; - local string st; +string (float tn) TeamFortress_GetColorSkin = { + string s; + s = ""; + switch (tn) + { + case 1: + s = "blue"; + break; + case 2: + s = "red"; + break; + case 3: + s = "yell"; + break; + case 4: + s = "gren"; + break; + } + + return s; +} + +string(float pc) TeamFortress_GetSkin = { local string skin = "base"; - tn = p.team_no; - pc = p.playerclass; - if (p.playerclass == 8) { - if (p.undercover_team != 0) { - tn = p.undercover_team; - } - if (p.undercover_skin != 0) { - pc = p.undercover_skin; - } - } - if (tn == 4) { - if (pc == 1) { - st = infokey(world, "sk_t4_scout"); - if (st != string_null) { - skin = st; - } - skin = "gren_sco"; - } else if (pc == 2) { - st = infokey(world, "sk_t4_sniper"); - if (st != string_null) { - skin = st; - } - skin = "gren_sni"; - } else if (pc == 3) { - st = infokey(world, "sk_t4_soldier"); - if (st != string_null) { - skin = st; - } - skin = "gren_sol"; - } else if (pc == 4) { - st = infokey(world, "sk_t4_demoman"); - if (st != string_null) { - skin = st; - } - skin = "gren_dem"; - } else if (pc == 5) { - st = infokey(world, "sk_t4_medic"); - if (st != string_null) { - skin = st; - } - skin = "gren_med"; - } else if (pc == 6) { - st = infokey(world, "sk_t4_hwguy"); - if (st != string_null) { - skin = st; - } - skin = "gren_hwg"; - } else if (pc == 7) { - st = infokey(world, "sk_t4_pyro"); - if (st != string_null) { - skin = st; - } - skin = "gren_pyr"; - } else if (pc == 8) { - st = infokey(world, "sk_t4_spy"); - if (st != string_null) { - skin = st; - } - skin = "gren_spy"; - } else if (pc == 9) { - st = infokey(world, - "sk_t4_engineer"); - if (st != string_null) { - skin = st; - } - skin = "gren_eng"; - } else if (pc == 11) { - skin = "gren_civ"; - } - } else if (tn == 3) { - if (pc == 1) { - st = infokey(world, "sk_t3_scout"); - if (st != string_null) { - skin = st; - } - skin = "yell_sco"; - } else if (pc == 2) { - st = infokey(world, "sk_t3_sniper"); - if (st != string_null) { - skin = st; - } - skin = "yell_sni"; - } else if (pc == 3) { - st = infokey(world, "sk_t3_soldier"); - if (st != string_null) { - skin = st; - } - skin = "yell_sol"; - } else if (pc == 4) { - st = infokey(world, "sk_t3_demoman"); - if (st != string_null) { - skin = st; - } - skin = "yell_dem"; - } else if (pc == 5) { - st = infokey(world, "sk_t3_medic"); - if (st != string_null) { - skin = st; - } - skin = "yell_med"; - } else if (pc == 6) { - st = infokey(world, "sk_t3_hwguy"); - if (st != string_null) { - skin = st; - } - skin = "yell_hwg"; - } else if (pc == 7) { - st = infokey(world, "sk_t3_pyro"); - if (st != string_null) { - skin = st; - } - skin = "yell_pyr"; - } else if (pc == 8) { - st = infokey(world, - "sk_t3_spy"); - if (st != string_null) { - skin = st; - } - skin = "yell_spy"; - } else if (pc == 9) { - st = infokey(world, - "sk_t3_engineer"); - if (st != string_null) { - skin = st; - } - skin = "yell_eng"; - } else if (pc == 11) { - skin = "yell_civ"; - } - } else if (tn == 2) { - if (pc == 1) { - st = infokey(world, "sk_t2_scout"); - if (st != string_null) { - skin = st; - } - skin = "red_sco"; - } else if (pc == 2) { - st = infokey(world, "sk_t2_sniper"); - if (st != string_null) { - skin = st; - } - skin = "red_sni"; - } else if (pc == 3) { - st = infokey(world, "sk_t2_soldier"); - if (st != string_null) { - skin = st; - } - skin = "red_sol"; - } else if (pc == 4) { - st = infokey(world, "sk_t2_demoman"); - if (st != string_null) { - skin = st; - } - skin = "red_dem"; - } else if (pc == 5) { - st = infokey(world, "sk_t2_medic"); - if (st != string_null) { - skin = st; - } - skin = "red_med"; - } else if (pc == 6) { - st = infokey(world, "sk_t2_hwguy"); - if (st != string_null) { - skin = st; - } - skin = "red_hwg"; - } else if (pc == 7) { - st = infokey(world, - "sk_t2_pyro"); - if (st != string_null) { - skin = st; - } - skin = "red_pyr"; - } else if (pc == 8) { - st = infokey(world, - "sk_t2_spy"); - if (st != string_null) { - skin = st; - } - skin = "red_spy"; - } else if (pc == 9) { - st = infokey(world, - "sk_t2_engineer"); - if (st != string_null) { - skin = st; - } - skin = "red_eng"; - } else if (pc == 11) { - skin = "red_civ"; - } - } else if (tn == 1) { - if (pc == 1) { - st = infokey(world, "sk_t1_scout"); - if (st != string_null) { - skin = st; - } - skin = "blue_sco"; - } else if (pc == 2) { - st = infokey(world, "sk_t1_sniper"); - if (st != string_null) { - skin = st; - } - skin = "blue_sni"; - } else if (pc == 3) { - st = infokey(world, "sk_t1_soldier"); - if (st != string_null) { - skin = st; - } - skin = "blue_sol"; - } else if (pc == 4) { - st = infokey(world, "sk_t1_demoman"); - if (st != string_null) { - skin = st; - } - skin = "blue_dem"; - } else if (pc == 5) { - st = infokey(world, "sk_t1_medic"); - if (st != string_null) { - skin = st; - } - skin = "blue_med"; - } else if (pc == 6) { - st = infokey(world, "sk_t1_hwguy"); - if (st != string_null) { - skin = st; - } - skin = "blue_hwg"; - } else if (pc == 7) { - st = infokey(world, - "sk_t1_pyro"); - if (st != string_null) { - skin = st; - } - skin = "blue_pyr"; - } else if (pc == 8) { - st = infokey(world, - "sk_t1_spy"); - if (st != string_null) { - skin = st; - } - skin = "blue_spy"; - } else if (pc == 9) { - st = infokey(world, - "sk_t1_engineer"); - if (st != string_null) { - skin = st; - } - skin = "blue_eng"; - } else if (pc == 11) { - skin = "blue_civ"; - } + switch (pc) { + case 1: + skin = "fo_scout"; + break; + case 2: + skin = "fo_snipe"; + break; + case 3: + skin = "fo_sold"; + break; + case 4: + skin = "fo_demo"; + break; + case 5: + skin = "fo_medic"; + break; + case 6: + skin = "fo_hwguy"; + break; + case 7: + skin = "fo_pyro"; + break; + case 8: + skin = "fo_spy"; + break; + case 9: + skin = "fo_eng"; + break; + case 11: + skin = "fo_civ"; + break; } return skin; }; -void (entity p) TeamFortress_SetSkin = { - local string st; +void (entity player) TeamFortress_SetSkin = { + player.immune_to_check = time + 10; - p.immune_to_check = time + 10; - if ((p.playerclass == PC_SPY) && (p.undercover_skin != 0)) - p.skin = p.undercover_skin; - else - p.skin = p.playerclass; - - if (p.skin != 0) { - stuffcmd(p, "skin "); - st = TeamFortress_GetSkin(p); - stuffcmd(p, st); - stuffcmd(p, "\n"); - } else - stuffcmd(p, "skin base\n"); + if (player.playerclass == PC_SPY && player.is_undercover) { + Spy_SetClientSkins(player); + return; + } + + player.skin = player.playerclass; + + if (player.skin != 0) { + player.needs_skin_update = 1; + local string sk = TeamFortress_GetSkin(player.playerclass); + forceinfokey(player, "skin", sk); + stuffcmd(player, sprintf("skin %s\n", sk)); + + local string col = TeamFortress_TeamGetColor(player.team_no); + forceinfokey(player, "bottomcolor", col); + stuffcmd(player, sprintf("setinfo bottomcolor %s\n", col)); + forceinfokey(player, "topcolor", col); + stuffcmd(player, sprintf("setinfo topcolor %s\n", col)); + } else { + forceinfokey(player, "skin", "base"); + stuffcmd(player, sprintf("skin %s\n", "base")); + forceinfokey(player, "bottomcolor", NOTEAMCOLOR); + stuffcmd(player, sprintf("bottomcolor %s\n", NOTEAMCOLOR)); + forceinfokey(player, "topcolor", NOTEAMCOLOR); + stuffcmd(player, sprintf("topcolor %s\n", NOTEAMCOLOR)); + } }; void () TeamFortress_SetEquipment = { @@ -1484,18 +1208,14 @@ void () TeamFortress_SetEquipment = { if (self.classname != "player") return; + Team_Role * role = GetTeamRole(self.team_no); + kept_items = self.tf_items & (IT_KEY1 | IT_KEY2); - self.items = 0; - self.weapons_carried = 0; - if (!remember_weapon || self.last_playerclass != self.playerclass || (self.tfstate & TFSTATE_RANDOMPC)) { - self.current_weapon = 0; - self.last_weapon = 0; - self.weaponmode = 0; - self.last_weaponmode = 0; - self.current_weaponslot = 1; - self.last_weaponslot = 2; - W_WeaponState_Save(self); + if (!remember_weapon || self.last_playerclass != self.playerclass || + (self.tfstate & TFSTATE_RANDOMPC)) { + self.current_slot = SlotMelee; + self.last_slot = SlotNull; } self.tf_items = 0; @@ -1512,16 +1232,16 @@ void () TeamFortress_SetEquipment = { self.immune_to_check = time + 10; self.undercover_team = 0; stuffcmd(self, "color "); - st = ftos(TeamFortress_TeamGetColor(self.team_no) - 1); + st = TeamFortress_TeamGetColor(self.team_no); stuffcmd(self, st); stuffcmd(self, "\n"); } - self.is_building = 0; + self.is_building = FALSE; self.is_detpacking = 0; + self.airblast_cooldown = 0; self.is_undercover = 0; - self.is_feigning = 0; + self.next_feign_time = 0; self.is_unabletospy = 0; - self.is_concussed = 0; self.disguise_skin = 0; self.disguise_team = 0; self.detpack_left = 0; @@ -1533,35 +1253,41 @@ void () TeamFortress_SetEquipment = { self.maxammo_medikit = 0; self.ammo_detpack = 0; self.maxammo_detpack = 0; - self.items_allowed = 0; self.armor_allowed = 0; self.maxarmor = 0; self.respawn_time = 0; self.heat = 0; - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_RELOADING); + self.tfstate &= (TFSTATE_RELOADING | TFSTATE_FEIGNED); if (self.team_no == 0) self.lives = -1; - self.items = self.items | kept_items; - if (self.playerclass == PC_SCOUT) { - self.weapons_carried = self.weapons_carried | PC_SCOUT_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_SCOUT_MAXAMMO_ROCKET; self.ammo_nails = PC_SCOUT_MAXAMMO_NAIL; self.ammo_shells = PC_SCOUT_MAXAMMO_SHOT; self.ammo_cells = PC_SCOUT_MAXAMMO_CELL; self.armortype = PC_SCOUT_MAXARMORTYPE; - self.armorvalue = PC_SCOUT_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[1]; + self.no_grenades_2 = role.gren2_limits[1]; + if (old_hp_armor) + self.armorvalue = 50; + else + self.armorvalue = PC_SCOUT_MAXARMOR; } else { self.ammo_rockets = PC_SCOUT_INITAMMO_ROCKET; self.ammo_nails = PC_SCOUT_INITAMMO_NAIL; self.ammo_shells = PC_SCOUT_INITAMMO_SHOT; self.ammo_cells = PC_SCOUT_INITAMMO_CELL; self.armortype = PC_SCOUT_INITARMORTYPE; - self.armorvalue = PC_SCOUT_INITARMOR; + self.no_grenades_1 = min(PC_SCOUT_GRENADE_INIT_1,role.gren1_limits[1]); + self.no_grenades_2 = min(PC_SCOUT_GRENADE_INIT_2,role.gren2_limits[1]); + + if (old_hp_armor) + self.armorvalue = 25; + else + self.armorvalue = PC_SCOUT_INITARMOR; } // dispenser needs @@ -1574,15 +1300,8 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_SCOUT_MAXAMMO_NAIL; self.maxammo_shells = PC_SCOUT_MAXAMMO_SHOT; self.maxammo_cells = PC_SCOUT_MAXAMMO_CELL; - self.no_grenades_1 = PC_SCOUT_GRENADE_INIT_1; - self.no_grenades_2 = PC_SCOUT_GRENADE_INIT_2; - - if (old_grens == 1) - self.tp_grenades_1 = GR_TYPE_FLASH; - else - self.tp_grenades_1 = GR_TYPE_CALTROP; - - self.tp_grenades_2 = GR_TYPE_CONCUSSION; + self.max_grenades_1 = role.gren1_limits[1]; + self.max_grenades_2 = role.gren2_limits[1]; self.tf_items = PC_SCOUT_TF_ITEMS; @@ -1591,30 +1310,35 @@ void () TeamFortress_SetEquipment = { self.armorclass = self.armorclass | 0; self.armor_allowed = PC_SCOUT_MAXARMORTYPE; - self.maxarmor = PC_SCOUT_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_NAILGUN; - - self.items_allowed = PC_SCOUT_WEAPONS; - - self.items = self.items | IT_SHOTGUN | IT_NAILGUN; + if (old_hp_armor) + self.maxarmor = 50; + else + self.maxarmor = PC_SCOUT_MAXARMOR; } else if (self.playerclass == PC_SNIPER) { - self.weapons_carried = self.weapons_carried | PC_SNIPER_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_SNIPER_MAXAMMO_ROCKET; self.ammo_nails = PC_SNIPER_MAXAMMO_NAIL; self.ammo_shells = PC_SNIPER_MAXAMMO_SHOT; self.ammo_cells = PC_SNIPER_MAXAMMO_CELL; self.armortype = PC_SNIPER_MAXARMORTYPE; - self.armorvalue = PC_SNIPER_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[2]; + self.no_grenades_2 = role.gren2_limits[2]; + if (old_hp_armor) + self.armorvalue = 50; + else + self.armorvalue = PC_SNIPER_MAXARMOR; } else { self.ammo_rockets = PC_SNIPER_INITAMMO_ROCKET; self.ammo_nails = PC_SNIPER_INITAMMO_NAIL; self.ammo_shells = PC_SNIPER_INITAMMO_SHOT; self.ammo_cells = PC_SNIPER_INITAMMO_CELL; self.armortype = PC_SNIPER_INITARMORTYPE; - self.armorvalue = PC_SNIPER_INITARMOR; + self.no_grenades_1 = min(PC_SNIPER_GRENADE_INIT_1,role.gren1_limits[2]); + self.no_grenades_2 = min(PC_SNIPER_GRENADE_INIT_2,role.gren2_limits[2]); + if (old_hp_armor) + self.armorvalue = 0; + else + self.armorvalue = PC_SNIPER_INITARMOR; } // dispenser needs @@ -1627,39 +1351,42 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_SNIPER_MAXAMMO_NAIL; self.maxammo_shells = PC_SNIPER_MAXAMMO_SHOT; self.maxammo_cells = PC_SNIPER_MAXAMMO_CELL; + self.max_grenades_1 = role.gren1_limits[2]; + self.max_grenades_2 = role.gren2_limits[2]; - self.no_grenades_1 = PC_SNIPER_GRENADE_INIT_1; - self.no_grenades_2 = PC_SNIPER_GRENADE_INIT_2; - self.tp_grenades_1 = PC_SNIPER_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_SNIPER_GRENADE_TYPE_2; self.tf_items = PC_SNIPER_TF_ITEMS; self.armorclass = self.armorclass | PC_SNIPER_INITARMORCLASS; self.armor_allowed = PC_SNIPER_MAXARMORTYPE; - self.maxarmor = PC_SNIPER_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_SNIPER_RIFLE; - - self.items_allowed = PC_SNIPER_WEAPONS; - self.items = - self.items | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_NAILGUN; + if (old_hp_armor) + self.maxarmor = 50; + else + self.maxarmor = PC_SNIPER_MAXARMOR; } else if (self.playerclass == PC_SOLDIER) { - self.weapons_carried = self.weapons_carried | PC_SOLDIER_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_SOLDIER_MAXAMMO_ROCKET; self.ammo_nails = PC_SOLDIER_MAXAMMO_NAIL; self.ammo_shells = PC_SOLDIER_MAXAMMO_SHOT; self.ammo_cells = PC_SOLDIER_MAXAMMO_CELL; self.armortype = PC_SOLDIER_MAXARMORTYPE; - self.armorvalue = PC_SOLDIER_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[3]; + self.no_grenades_2 = role.gren2_limits[3]; + if (old_hp_armor) + self.armorvalue = 200; + else + self.armorvalue = PC_SOLDIER_MAXARMOR; } else { self.ammo_rockets = PC_SOLDIER_INITAMMO_ROCKET; self.ammo_nails = PC_SOLDIER_INITAMMO_NAIL; self.ammo_shells = PC_SOLDIER_INITAMMO_SHOT; self.ammo_cells = PC_SOLDIER_INITAMMO_CELL; self.armortype = PC_SOLDIER_INITARMORTYPE; - self.armorvalue = PC_SOLDIER_INITARMOR; + self.no_grenades_1 = min(PC_SOLDIER_GRENADE_INIT_1,role.gren1_limits[3]); + self.no_grenades_2 = min(PC_SOLDIER_GRENADE_INIT_2,role.gren2_limits[3]); + if (old_hp_armor) + self.armorvalue = 100; + else + self.armorvalue = PC_SOLDIER_INITARMOR; } // dispenser needs @@ -1672,40 +1399,42 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_SOLDIER_MAXAMMO_NAIL; self.maxammo_shells = PC_SOLDIER_MAXAMMO_SHOT; self.maxammo_cells = PC_SOLDIER_MAXAMMO_CELL; + self.max_grenades_1 = role.gren1_limits[3]; + self.max_grenades_2 = role.gren2_limits[3]; - self.no_grenades_1 = PC_SOLDIER_GRENADE_INIT_1; - self.no_grenades_2 = PC_SOLDIER_GRENADE_INIT_2; - self.tp_grenades_1 = PC_SOLDIER_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_SOLDIER_GRENADE_TYPE_2; self.tf_items = PC_SOLDIER_TF_ITEMS; self.armorclass = self.armorclass | PC_SOLDIER_INITARMORCLASS; self.armor_allowed = PC_SOLDIER_MAXARMORTYPE; - self.maxarmor = PC_SOLDIER_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_ROCKET_LAUNCHER; - - self.items_allowed = PC_SOLDIER_WEAPONS; - self.items = - self. - items | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_ROCKET_LAUNCHER; + if (old_hp_armor) + self.maxarmor = 200; + else + self.maxarmor = PC_SOLDIER_MAXARMOR; } else if (self.playerclass == PC_DEMOMAN) { - self.weapons_carried = self.weapons_carried | PC_DEMOMAN_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_DEMOMAN_MAXAMMO_ROCKET; self.ammo_nails = PC_DEMOMAN_MAXAMMO_NAIL; self.ammo_shells = PC_DEMOMAN_MAXAMMO_SHOT; self.ammo_cells = PC_DEMOMAN_MAXAMMO_CELL; self.armortype = PC_DEMOMAN_MAXARMORTYPE; - self.armorvalue = PC_DEMOMAN_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[4]; + self.no_grenades_2 = role.gren2_limits[4]; + if (old_hp_armor) + self.armorvalue = 120; + else + self.armorvalue = PC_DEMOMAN_MAXARMOR; } else { self.ammo_rockets = PC_DEMOMAN_INITAMMO_ROCKET; self.ammo_nails = PC_DEMOMAN_INITAMMO_NAIL; self.ammo_shells = PC_DEMOMAN_INITAMMO_SHOT; self.ammo_cells = PC_DEMOMAN_INITAMMO_CELL; self.armortype = PC_DEMOMAN_INITARMORTYPE; - self.armorvalue = PC_DEMOMAN_INITARMOR; + self.no_grenades_1 = min(PC_DEMOMAN_GRENADE_INIT_1,role.gren1_limits[4]); + self.no_grenades_2 = min(PC_DEMOMAN_GRENADE_INIT_2,role.gren2_limits[4]); + if (old_hp_armor) + self.armorvalue = 50; + else + self.armorvalue = PC_DEMOMAN_INITARMOR; } // dispenser needs @@ -1718,11 +1447,9 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_DEMOMAN_MAXAMMO_NAIL; self.maxammo_shells = PC_DEMOMAN_MAXAMMO_SHOT; self.maxammo_cells = PC_DEMOMAN_MAXAMMO_CELL; + self.max_grenades_1 = role.gren1_limits[4]; + self.max_grenades_2 = role.gren2_limits[4]; - self.no_grenades_1 = PC_DEMOMAN_GRENADE_INIT_1; - self.no_grenades_2 = PC_DEMOMAN_GRENADE_INIT_2; - self.tp_grenades_1 = PC_DEMOMAN_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_DEMOMAN_GRENADE_TYPE_2; self.tf_items = PC_DEMOMAN_TF_ITEMS; self.ammo_detpack = PC_DEMOMAN_INITAMMO_DETPACK; @@ -1730,29 +1457,35 @@ void () TeamFortress_SetEquipment = { self.armorclass = self.armorclass | PC_DEMOMAN_INITARMORCLASS; self.armor_allowed = PC_DEMOMAN_MAXARMORTYPE; - self.maxarmor = PC_DEMOMAN_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_GRENADE_LAUNCHER; - - self.items_allowed = PC_DEMOMAN_WEAPONS; - self.items = self.items | IT_SHOTGUN | IT_GRENADE_LAUNCHER; + if (old_hp_armor) + self.maxarmor = 120; + else + self.maxarmor = PC_DEMOMAN_MAXARMOR; } else if (self.playerclass == PC_MEDIC) { - self.weapons_carried = self.weapons_carried | PC_MEDIC_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_MEDIC_MAXAMMO_ROCKET; self.ammo_nails = PC_MEDIC_MAXAMMO_NAIL; self.ammo_shells = PC_MEDIC_MAXAMMO_SHOT; self.ammo_cells = PC_MEDIC_MAXAMMO_CELL; self.armortype = PC_MEDIC_MAXARMORTYPE; - self.armorvalue = PC_MEDIC_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[5]; + self.no_grenades_2 = role.gren2_limits[5]; + if (old_hp_armor) + self.armorvalue = 100; + else + self.armorvalue = PC_MEDIC_MAXARMOR; } else { self.ammo_rockets = PC_MEDIC_INITAMMO_ROCKET; self.ammo_nails = PC_MEDIC_INITAMMO_NAIL; self.ammo_shells = PC_MEDIC_INITAMMO_SHOT; self.ammo_cells = PC_MEDIC_INITAMMO_CELL; self.armortype = PC_MEDIC_INITARMORTYPE; - self.armorvalue = PC_MEDIC_INITARMOR; + self.no_grenades_1 = min(PC_MEDIC_GRENADE_INIT_1,role.gren1_limits[5]); + self.no_grenades_2 = min(PC_MEDIC_GRENADE_INIT_2,role.gren2_limits[5]); + if (old_hp_armor) + self.armorvalue = 50; + else + self.armorvalue = PC_MEDIC_INITARMOR; } // dispenser needs @@ -1765,18 +1498,17 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_MEDIC_MAXAMMO_NAIL; self.maxammo_shells = PC_MEDIC_MAXAMMO_SHOT; self.maxammo_cells = PC_MEDIC_MAXAMMO_CELL; + self.max_grenades_1 = role.gren1_limits[5]; + self.max_grenades_2 = role.gren2_limits[5]; - self.no_grenades_1 = PC_MEDIC_GRENADE_INIT_1; - self.no_grenades_2 = PC_MEDIC_GRENADE_INIT_2; - self.tp_grenades_1 = PC_MEDIC_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_MEDIC_GRENADE_TYPE_2; self.tf_items = PC_MEDIC_TF_ITEMS; self.armorclass = self.armorclass | PC_MEDIC_INITARMORCLASS; self.armor_allowed = PC_MEDIC_MAXARMORTYPE; - self.maxarmor = PC_MEDIC_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_SUPER_NAILGUN; + if (old_hp_armor) + self.maxarmor = 100; + else + self.maxarmor = PC_MEDIC_MAXARMOR; self.ammo_medikit = PC_MEDIC_INITAMMO_MEDIKIT; self.maxammo_medikit = PC_MEDIC_MAXAMMO_MEDIKIT; @@ -1798,27 +1530,35 @@ void () TeamFortress_SetEquipment = { te.think = CF_Medic_AuraFindPlayers; te.owner = self; te.classname = "timer"; - - self.items_allowed = PC_MEDIC_WEAPONS; - self.items = - self.items | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_SUPER_NAILGUN; } else if (self.playerclass == PC_HVYWEAP) { - self.weapons_carried = self.weapons_carried | PC_HVYWEAP_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_HVYWEAP_MAXAMMO_ROCKET; self.ammo_nails = PC_HVYWEAP_MAXAMMO_NAIL; self.ammo_shells = PC_HVYWEAP_MAXAMMO_SHOT; self.ammo_cells = PC_HVYWEAP_MAXAMMO_CELL; self.armortype = PC_HVYWEAP_MAXARMORTYPE; - self.armorvalue = PC_HVYWEAP_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[6]; + self.no_grenades_2 = role.gren2_limits[6]; + + if (old_hp_armor) { + self.armorvalue = 300; + } else { + self.armorvalue = max_armor_hwguy; + } + } else { self.ammo_rockets = PC_HVYWEAP_INITAMMO_ROCKET; self.ammo_nails = PC_HVYWEAP_INITAMMO_NAIL; self.ammo_shells = PC_HVYWEAP_INITAMMO_SHOT; self.ammo_cells = PC_HVYWEAP_INITAMMO_CELL; self.armortype = PC_HVYWEAP_INITARMORTYPE; - self.armorvalue = PC_HVYWEAP_INITARMOR; + self.no_grenades_1 = min(PC_HVYWEAP_GRENADE_INIT_1,role.gren1_limits[6]); + self.no_grenades_2 = min(PC_HVYWEAP_GRENADE_INIT_2,role.gren2_limits[6]); + + if (old_hp_armor) + self.armorvalue = 150; + else + self.armorvalue = PC_HVYWEAP_INITARMOR; } // dispenser needs @@ -1831,40 +1571,43 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_HVYWEAP_MAXAMMO_NAIL; self.maxammo_shells = PC_HVYWEAP_MAXAMMO_SHOT; self.maxammo_cells = PC_HVYWEAP_MAXAMMO_CELL; - - self.no_grenades_1 = PC_HVYWEAP_GRENADE_INIT_1; - self.no_grenades_2 = PC_HVYWEAP_GRENADE_INIT_2; - self.tp_grenades_1 = PC_HVYWEAP_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_HVYWEAP_GRENADE_TYPE_2; - self.tf_items = PC_HVYWEAP_TF_ITEMS; + self.max_grenades_1 = role.gren1_limits[6]; + self.max_grenades_2 = role.gren2_limits[6]; self.armorclass = self.armorclass | PC_HVYWEAP_INITARMORCLASS; self.armor_allowed = PC_HVYWEAP_MAXARMORTYPE; - self.maxarmor = PC_HVYWEAP_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_SUPER_SHOTGUN; - - self.items_allowed = PC_HVYWEAP_WEAPONS; - self.items = - self. - items | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_ROCKET_LAUNCHER; - } else if (self.playerclass == PC_PYRO) { - self.weapons_carried = self.weapons_carried | PC_PYRO_WEAPONS; + if (old_hp_armor) { + self.maxarmor = 300; + } else { + self.maxarmor = max_armor_hwguy; + } + + } else if (self.playerclass == PC_PYRO) { if (spawnfull) { self.ammo_rockets = PC_PYRO_MAXAMMO_ROCKET; self.ammo_nails = PC_PYRO_MAXAMMO_NAIL; self.ammo_shells = PC_PYRO_MAXAMMO_SHOT; self.ammo_cells = PC_PYRO_MAXAMMO_CELL; self.armortype = PC_PYRO_MAXARMORTYPE; - self.armorvalue = PC_PYRO_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[7]; + self.no_grenades_2 = role.gren2_limits[7]; + if (old_hp_armor) + self.armorvalue = 150; + else + self.armorvalue = PC_PYRO_MAXARMOR; } else { self.ammo_rockets = PC_PYRO_INITAMMO_ROCKET; self.ammo_nails = PC_PYRO_INITAMMO_NAIL; self.ammo_shells = PC_PYRO_INITAMMO_SHOT; self.ammo_cells = PC_PYRO_INITAMMO_CELL; self.armortype = PC_PYRO_INITARMORTYPE; - self.armorvalue = PC_PYRO_INITARMOR; + self.no_grenades_1 = min(PC_PYRO_GRENADE_INIT_1,role.gren1_limits[7]); + self.no_grenades_2 = min(PC_PYRO_GRENADE_INIT_2,role.gren2_limits[7]); + if (old_hp_armor) + self.armorvalue = 50; + else + self.armorvalue = PC_PYRO_INITARMOR; } // dispenser needs @@ -1877,40 +1620,40 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_PYRO_MAXAMMO_NAIL; self.maxammo_shells = PC_PYRO_MAXAMMO_SHOT; self.maxammo_cells = PC_PYRO_MAXAMMO_CELL; - - self.no_grenades_1 = PC_PYRO_GRENADE_INIT_1; - self.no_grenades_2 = PC_PYRO_GRENADE_INIT_2; - self.tp_grenades_1 = PC_PYRO_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_PYRO_GRENADE_TYPE_2; - self.tf_items = PC_PYRO_TF_ITEMS; + self.max_grenades_1 = role.gren1_limits[7]; + self.max_grenades_2 = role.gren2_limits[7]; self.armorclass = self.armorclass | PC_PYRO_INITARMORCLASS; self.armor_allowed = PC_PYRO_MAXARMORTYPE; - self.maxarmor = PC_PYRO_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_FLAMETHROWER; - - self.items_allowed = PC_PYRO_WEAPONS; - self.items = - self. - items | IT_SHOTGUN | IT_GRENADE_LAUNCHER | IT_ROCKET_LAUNCHER; + if (old_hp_armor) + self.maxarmor = 150; + else + self.maxarmor = PC_PYRO_MAXARMOR; } else if (self.playerclass == PC_CIVILIAN) { - self.weapons_carried = self.weapons_carried | PC_CIVILIAN_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_CIVILIAN_MAXAMMO_ROCKET; self.ammo_nails = PC_CIVILIAN_MAXAMMO_NAIL; self.ammo_shells = PC_CIVILIAN_MAXAMMO_SHOT; self.ammo_cells = PC_CIVILIAN_MAXAMMO_CELL; self.armortype = PC_CIVILIAN_MAXARMORTYPE; - self.armorvalue = PC_CIVILIAN_MAXARMOR; + self.no_grenades_1 = PC_CIVILIAN_GRENADE_MAX_1; + self.no_grenades_2 = PC_CIVILIAN_GRENADE_MAX_2; + if (old_hp_armor) + self.armorvalue = 0; + else + self.armorvalue = PC_CIVILIAN_MAXARMOR; } else { self.ammo_rockets = PC_CIVILIAN_INITAMMO_ROCKET; self.ammo_nails = PC_CIVILIAN_INITAMMO_NAIL; self.ammo_shells = PC_CIVILIAN_INITAMMO_SHOT; self.ammo_cells = PC_CIVILIAN_INITAMMO_CELL; self.armortype = PC_CIVILIAN_INITARMORTYPE; - self.armorvalue = PC_CIVILIAN_INITARMOR; + self.no_grenades_1 = PC_CIVILIAN_GRENADE_INIT_1; + self.no_grenades_2 = PC_CIVILIAN_GRENADE_INIT_2; + if (old_hp_armor) + self.armorvalue = 0; + else + self.armorvalue = PC_CIVILIAN_INITARMOR; } // dispenser needs @@ -1923,38 +1666,44 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_CIVILIAN_MAXAMMO_NAIL; self.maxammo_shells = PC_CIVILIAN_MAXAMMO_SHOT; self.maxammo_cells = PC_CIVILIAN_MAXAMMO_CELL; + self.max_grenades_1 = PC_CIVILIAN_GRENADE_MAX_1; + self.max_grenades_2 = PC_CIVILIAN_GRENADE_MAX_2; - self.no_grenades_1 = PC_CIVILIAN_GRENADE_INIT_1; - self.no_grenades_2 = PC_CIVILIAN_GRENADE_INIT_2; - self.tp_grenades_1 = PC_CIVILIAN_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_CIVILIAN_GRENADE_TYPE_2; self.tf_items = PC_CIVILIAN_TF_ITEMS; self.armorclass = self.armorclass | PC_CIVILIAN_INITARMORCLASS; self.armor_allowed = PC_CIVILIAN_MAXARMORTYPE; - self.maxarmor = PC_CIVILIAN_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_AXE; - - self.items_allowed = PC_CIVILIAN_WEAPONS; - self.items = 0; + if (old_hp_armor) + self.maxarmor = 0; + else + self.maxarmor = PC_CIVILIAN_MAXARMOR; } else if (self.playerclass == PC_SPY) { - self.weapons_carried = self.weapons_carried | PC_SPY_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_SPY_MAXAMMO_ROCKET; self.ammo_nails = PC_SPY_MAXAMMO_NAIL; self.ammo_shells = PC_SPY_MAXAMMO_SHOT; self.ammo_cells = PC_SPY_MAXAMMO_CELL; self.armortype = PC_SPY_MAXARMORTYPE; - self.armorvalue = PC_SPY_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[8]; + self.no_grenades_2 = role.gren2_limits[8]; + + if (old_hp_armor) + self.armorvalue = 100; + else + self.armorvalue = PC_SPY_MAXARMOR; } else { self.ammo_rockets = PC_SPY_INITAMMO_ROCKET; self.ammo_nails = PC_SPY_INITAMMO_NAIL; self.ammo_shells = PC_SPY_INITAMMO_SHOT; self.ammo_cells = PC_SPY_INITAMMO_CELL; self.armortype = PC_SPY_INITARMORTYPE; - self.armorvalue = PC_SPY_INITARMOR; + self.no_grenades_1 = min(PC_SPY_GRENADE_INIT_1,role.gren1_limits[8]); + self.no_grenades_2 = min(PC_SPY_GRENADE_INIT_2,role.gren2_limits[8]); + + if (old_hp_armor) + self.armorvalue = 25; + else + self.armorvalue = PC_SPY_INITARMOR; } // dispenser needs @@ -1967,22 +1716,17 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_SPY_MAXAMMO_NAIL; self.maxammo_shells = PC_SPY_MAXAMMO_SHOT; self.maxammo_cells = PC_SPY_MAXAMMO_CELL; + self.max_grenades_1 = role.gren1_limits[8]; + self.max_grenades_2 = role.gren2_limits[8]; - self.no_grenades_1 = PC_SPY_GRENADE_INIT_1; - self.no_grenades_2 = PC_SPY_GRENADE_INIT_2; - self.tp_grenades_1 = PC_SPY_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_SPY_GRENADE_TYPE_2; self.tf_items = PC_SPY_TF_ITEMS; self.armorclass = self.armorclass | PC_SPY_INITARMORCLASS; self.armor_allowed = PC_SPY_MAXARMORTYPE; - self.maxarmor = PC_SPY_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_TRANQ; - - self.items_allowed = PC_SPY_WEAPONS; - self.items = - self.items | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_NAILGUN; + if (old_hp_armor) + self.maxarmor = 100; + else + self.maxarmor = PC_SPY_MAXARMOR; if (invis_only == 1) { te = spawn(); @@ -1992,22 +1736,31 @@ void () TeamFortress_SetEquipment = { te.classname = "timer"; } } else if (self.playerclass == PC_ENGINEER) { - self.weapons_carried = self.weapons_carried | PC_ENGINEER_WEAPONS; - if (spawnfull) { self.ammo_rockets = PC_ENGINEER_MAXAMMO_ROCKET; self.ammo_nails = PC_ENGINEER_MAXAMMO_NAIL; self.ammo_shells = PC_ENGINEER_MAXAMMO_SHOT; self.ammo_cells = PC_ENGINEER_MAXAMMO_CELL; self.armortype = PC_ENGINEER_MAXARMORTYPE; - self.armorvalue = PC_ENGINEER_MAXARMOR; + self.no_grenades_1 = role.gren1_limits[9]; + self.no_grenades_2 = role.gren2_limits[9]; + + if (old_hp_armor) + self.armorvalue = 50; + else + self.armorvalue = PC_ENGINEER_MAXARMOR; } else { self.ammo_rockets = PC_ENGINEER_INITAMMO_ROCKET; self.ammo_nails = PC_ENGINEER_INITAMMO_NAIL; self.ammo_shells = PC_ENGINEER_INITAMMO_SHOT; self.ammo_cells = PC_ENGINEER_INITAMMO_CELL; self.armortype = PC_ENGINEER_INITARMORTYPE; - self.armorvalue = PC_ENGINEER_INITARMOR; + self.no_grenades_1 = min(PC_ENGINEER_GRENADE_INIT_1,role.gren1_limits[9]); + self.no_grenades_2 = min(PC_ENGINEER_GRENADE_INIT_2,role.gren2_limits[9]); + if (old_hp_armor) + self.armorvalue = 25; + else + self.armorvalue = PC_ENGINEER_INITARMOR; } // dispenser needs @@ -2020,23 +1773,19 @@ void () TeamFortress_SetEquipment = { self.maxammo_nails = PC_ENGINEER_MAXAMMO_NAIL; self.maxammo_shells = PC_ENGINEER_MAXAMMO_SHOT; self.maxammo_cells = PC_ENGINEER_MAXAMMO_CELL; + self.max_grenades_1 = role.gren1_limits[9]; + self.max_grenades_2 = role.gren2_limits[9]; - self.no_grenades_1 = PC_ENGINEER_GRENADE_INIT_1; - self.no_grenades_2 = PC_ENGINEER_GRENADE_INIT_2; - self.tp_grenades_1 = PC_ENGINEER_GRENADE_TYPE_1; - self.tp_grenades_2 = PC_ENGINEER_GRENADE_TYPE_2; self.tf_items = PC_ENGINEER_TF_ITEMS; self.armorclass = self.armorclass | PC_ENGINEER_INITARMORCLASS; self.armor_allowed = PC_ENGINEER_MAXARMORTYPE; - self.maxarmor = PC_ENGINEER_MAXARMOR; - if (self.last_playerclass != self.playerclass) - self.current_weapon = WEAP_LASER; + if (old_hp_armor) + self.maxarmor = 50; + else + self.maxarmor = PC_ENGINEER_MAXARMOR; - self.items_allowed = PC_ENGINEER_WEAPONS; - self.items = self.items | IT_SHOTGUN | IT_SUPER_SHOTGUN; } else if (self.playerclass == PC_UNDEFINED) { - self.items = 0; self.ammo_rockets = 0; self.ammo_nails = 0; self.ammo_shells = 0; @@ -2044,15 +1793,14 @@ void () TeamFortress_SetEquipment = { self.no_grenades_1 = 0; self.no_grenades_2 = 0; - self.tp_grenades_1 = 0; - self.tp_grenades_2 = 0; + self.max_grenades_1 = 0; + self.max_grenades_2 = 0; self.armorclass = 0; self.armortype = 0; self.armorvalue = 0; self.weapon = 0; - self.current_weapon = 0; - self.weapons_carried = 0; + self.current_slot = SlotNull; self.flags = FL_CLIENT | FL_NOTARGET; self.waterlevel = 3; @@ -2065,10 +1813,15 @@ void () TeamFortress_SetEquipment = { self.modelindex = 0; self.weaponmodel = string_null; modelindex_player = 0; - self.tfstate = self.tfstate | TFSTATE_RELOADING; setmodel(self, string_null); } + if(duelmode && !duel_all_grens) { + //unless duel_all_grens is set, remove all secondary grens except emps + if(self.playerclass != PC_ENGINEER) { + self.no_grenades_2 = 0; + } + } if (self.armortype >= 0.8) self.items = self.items | IT_ARMOR3; @@ -2077,7 +1830,11 @@ void () TeamFortress_SetEquipment = { else if (self.armortype >= 0.3) self.items = self.items | IT_ARMOR1; - W_SetCurrentAmmo(self); + if (self.last_playerclass != self.playerclass) + W_ChangeToBestWeapon(); + + W_UpdateCurrentWeapon(self); + self.last_playerclass = self.playerclass; }; float (entity Retriever, float AmmoType) TeamFortress_GetMaxAmmo = { @@ -2100,7 +1857,7 @@ float (entity Retriever, float AmmoType) TeamFortress_GetMaxAmmo = { }; float (entity Retriever, float WeaponType) TeamFortress_CanGetWeapon = { - if (Retriever.items_allowed & WeaponType) + if (Retriever.items & WeaponType) return TRUE; return FALSE; @@ -2185,13 +1942,17 @@ void () TeamFortress_RemoveTimers = { local entity te; self.leg_damage = 0; - self.is_concussed = 0; self.is_undercover = 0; - self.is_building = 0; + RemoveConc(self); + + if (self.is_building && engineer_move) { + TeamFortress_EngineerBuildStop(); + } + + self.is_building = FALSE; self.building = world; if (self.tfstate & TFSTATE_AIMING) { - self.tfstate = self.tfstate - TFSTATE_AIMING; - TeamFortress_SetSpeed(self); + self.tfstate &= ~TFSTATE_AIMING; self.heat = 0; } if (self.tfstate & TFSTATE_INFECTED) @@ -2202,7 +1963,7 @@ void () TeamFortress_RemoveTimers = { te = find(world, classname, "timer"); while (te != world) { - if ((te.owner == self) && (te.no_active_gas_grens <= 0)) { + if (te.owner == self) { dremove(te); te = find(world, classname, "timer"); } else @@ -2210,34 +1971,24 @@ void () TeamFortress_RemoveTimers = { } RemoveAutoIdTimer(); - RemoveGrenadeTimers(); RemovePrimeTimers(); self.StatusGrenTime = 0; Status_Refresh(self); - te = find(world, classname, "grentimer"); - while (te != world) { - if ((te.owner == self) && (te.no_active_napalm_grens <= 0)) { - dremove(te); - te = find(world, classname, "grentimer"); - } else - te = find(te, classname, "grentimer"); - } - te = find(world, classname, "item_tfgoal"); while (te) { if (te.owner == self) { if (!(te.goal_activation & TFGI_KEEP) || - (self.has_disconnected == 1)) { + (self.has_disconnected == 1)) { tfgoalitem_RemoveFromPlayer(te, self, 0); } if (CTF_Map == 1) { if (te.goal_no == 1) { bprint(PRINT_HIGH, self.netname, - " ÌÏÓÔ the ÂÌÕÅ flag!\n"); + Q" \stook\s the \sblue\s flag!\n"); } else if (te.goal_no == 2) { bprint(PRINT_HIGH, self.netname, - " ÌÏÓÔ the ÒÅÄ flag!\n"); + Q" \stook\s the \sred\s flag!\n"); } } } @@ -2246,8 +1997,8 @@ void () TeamFortress_RemoveTimers = { te = find(world, classname, "detpack"); while (te) { - if ((te.weaponmode == 1) && (te.enemy == self)) - te.weaponmode = 0; + if ((te.is_disarming) && (te.enemy == self)) + te.is_disarming = FALSE; te = find(te, classname, "detpack"); } @@ -2263,7 +2014,6 @@ void () TeamFortress_RemoveTimers = { } } if (old_grens == 1) { - stuffcmd(self, "v_idlescale 0\n"); stuffcmd(self, "v_cshift; wait; bf\n"); self.FlashTime = 0; } @@ -2280,25 +2030,28 @@ void (float Suicided) TeamFortress_SetupRespawn = { return; if (toggleflags & TFLAG_RESPAWNDELAY) - restime = respawn_delay_time; + restime = GetTeamRole(self.team_no).respawn_delay_time; else restime = 0; - if (cb_prematch_time < time) { + if (!cb_prematch && !votemode) { if (Suicided) { if (self.lives > 0) self.lives = self.lives - 1; - restime = restime + 7; + + if (!allowpracspawns) + restime = restime + (new_balance ? 5 : 7); } } - if (cb_prematch_time > time) { + + if (cb_prematch) { if (self.lives > 0) self.lives = self.lives - 1; if (self.lives != -1) { if (self.lives == 0) { sprint(self, PRINT_HIGH, - "No lives left, returning to observer mode\n"); + "No lives left, returning to observer mode\n"); self.playerclass = PC_UNDEFINED; self.tfstate = self.tfstate - (self.tfstate & TFSTATE_RANDOMPC); @@ -2323,7 +2076,7 @@ void (float Suicided) TeamFortress_SetupRespawn = { } } self.respawn_time = time + restime; - if (restime > 3) { + if (restime > 3 && !duelmode) { db = ftos(restime); sprint(self, PRINT_HIGH, db, " seconds till respawn\n"); } @@ -2375,7 +2128,7 @@ void () TeamFortress_CheckClassStats = { // Check health if ((self.health > self.max_health) && !(self.items & IT_SUPERHEALTH)) TF_T_Damage(self, world, world, self.max_health - self.health, 0, - TF_TD_NOSOUND); + TF_TD_NOSOUND); if (self.health < 0) T_Heal(self, self.health - self.health, 0); @@ -2390,20 +2143,20 @@ void () TeamFortress_CheckClassStats = { self.items = self.items | IT_ARMOR1; }; -void (float type) TeamFortress_DropAmmo = { - local float ammo; - ammo = 0; +void (float type, float ammo) TeamFortress_DropAmmo = { + //local float ammo; + //ammo = 0; if (type == 1) { - ammo = DROP_SHELLS; + if(!ammo) ammo = DROP_SHELLS; if (self.ammo_shells < ammo) { if (self.playerclass == PC_ENGINEER) { - if ((self.ammo_cells / AMMO_COST_SHELLS) > - (ammo - self.ammo_shells)) { + if ((self.ammo_cells / AMMO_COST_SHELLS) >= + (ammo - self.ammo_shells)) { sprint(self, PRINT_HIGH, "You make some shells\n"); self.ammo_cells = self.ammo_cells - (ammo - - self.ammo_shells) * + self.ammo_shells) * AMMO_COST_SHELLS; self.ammo_shells = ammo; } @@ -2413,15 +2166,15 @@ void (float type) TeamFortress_DropAmmo = { } self.ammo_shells = self.ammo_shells - ammo; } else if (type == 2) { - ammo = DROP_NAILS; + if(!ammo) ammo = DROP_NAILS; if (self.ammo_nails < ammo) { if (self.playerclass == PC_ENGINEER) { - if ((self.ammo_cells / AMMO_COST_NAILS) > - (ammo - self.ammo_nails)) { + if ((self.ammo_cells / AMMO_COST_NAILS) >= + (ammo - self.ammo_nails)) { sprint(self, PRINT_HIGH, "You make some nails\n"); self.ammo_cells = self.ammo_cells - (ammo - - self.ammo_nails) * + self.ammo_nails) * AMMO_COST_NAILS; self.ammo_nails = ammo; } @@ -2431,15 +2184,15 @@ void (float type) TeamFortress_DropAmmo = { } self.ammo_nails = self.ammo_nails - ammo; } else if (type == 3) { - ammo = DROP_ROCKETS; + if(!ammo) ammo = DROP_ROCKETS; if (self.ammo_rockets < ammo) { if (self.playerclass == PC_ENGINEER) { - if ((self.ammo_cells / AMMO_COST_ROCKETS) > - (ammo - self.ammo_rockets)) { + if ((self.ammo_cells / AMMO_COST_ROCKETS) >= + (ammo - self.ammo_rockets)) { sprint(self, PRINT_HIGH, "You make some rockets\n"); self.ammo_cells = self.ammo_cells - (ammo - - self.ammo_rockets) * + self.ammo_rockets) * AMMO_COST_ROCKETS; self.ammo_rockets = ammo; } @@ -2449,15 +2202,15 @@ void (float type) TeamFortress_DropAmmo = { } self.ammo_rockets = self.ammo_rockets - ammo; } else if (type == 4) { - ammo = DROP_CELLS; + if(!ammo) ammo = DROP_CELLS; if (self.ammo_cells < ammo) { if (self.playerclass == PC_ENGINEER) { - if ((self.ammo_cells / AMMO_COST_CELLS) > - (ammo - self.ammo_cells)) { + if ((self.ammo_cells / AMMO_COST_CELLS) >= + (ammo - self.ammo_cells)) { sprint(self, PRINT_HIGH, "You make some cells\n"); self.ammo_cells = self.ammo_cells - (ammo - - self.ammo_cells) * + self.ammo_cells) * AMMO_COST_CELLS; self.ammo_cells = ammo; } @@ -2467,7 +2220,6 @@ void (float type) TeamFortress_DropAmmo = { } self.ammo_cells = self.ammo_cells - ammo; } - W_SetCurrentAmmo(self); newmis = spawn(); newmis.aflag = ammo; newmis.weapon = type; @@ -2479,8 +2231,8 @@ void (float type) TeamFortress_DropAmmo = { newmis.ammo_rockets = ammo; else if (newmis.weapon == 4) newmis.ammo_cells = ammo; - newmis.enemy = self; - newmis.health = time; + newmis.dropped_by = self; + newmis.dropped_at = time; newmis.movetype = 6; newmis.solid = 1; newmis.classname = "ammobox"; @@ -2494,14 +2246,14 @@ void (float type) TeamFortress_DropAmmo = { newmis.velocity_z = 200; } newmis.avelocity = '0 300 0'; - setsize(newmis, '0 0 0', '0 0 0'); + setsize(newmis, '-16 -16 0', '16 16 56'); setorigin(newmis, self.origin); newmis.nextthink = time + 30; newmis.think = SUB_Remove; newmis.touch = TeamFortress_AmmoboxTouch; newmis.skin = type - 1; - setmodel(newmis, "progs/ammobox.mdl"); + FO_SetModel(newmis, "progs/ammobox.mdl"); }; void () TeamFortress_AmmoboxTouch = { @@ -2509,9 +2261,9 @@ void () TeamFortress_AmmoboxTouch = { local string quantity; took = 0; - if ((other == self.enemy) && (time < (self.health + 2))) + if ((other == self.dropped_by) && (time < (self.dropped_at + 2))) return; - if ((other.tfstate & 65536) || (other.tfstate & 2048)) + if ((other.tfstate & TFSTATE_CANT_MOVE) || (other.tfstate & TFSTATE_AIMING)) return; if (other.classname != "player") return; @@ -2528,13 +2280,12 @@ void () TeamFortress_AmmoboxTouch = { other.ammo_cells = other.ammo_cells + self.ammo_cells; dremove(self); bound_other_ammo(other); - sound(other, 3, "weapons/lock4.wav", 1, 1); + FO_Sound(other, CHAN_AUTO, "weapons/lock4.wav", 1, 1); stuffcmd(other, "bf\n"); self = other; - W_SetCurrentAmmo(self); return; } else if ((other.playerclass == PC_SCOUT) - || (other.playerclass == PC_ENGINEER)) { + || (other.playerclass == PC_ENGINEER)) { if (!(self.ammo_shells + self.ammo_nails + self.ammo_cells)) { other.nopickup = self; return; @@ -2546,8 +2297,8 @@ void () TeamFortress_AmmoboxTouch = { other.ammo_cells = other.ammo_cells + self.ammo_cells; self.ammo_cells = 0; } else if ((other.playerclass == PC_SNIPER) - || (other.playerclass == PC_SPY) - || (other.playerclass == PC_MEDIC)) { + || (other.playerclass == PC_SPY) + || (other.playerclass == PC_MEDIC)) { if (!(self.ammo_shells + self.ammo_nails)) { other.nopickup = self; return; @@ -2557,7 +2308,7 @@ void () TeamFortress_AmmoboxTouch = { other.ammo_nails = other.ammo_nails + self.ammo_nails; self.ammo_nails = 0; } else if ((other.playerclass == PC_SOLDIER) - || (other.playerclass == PC_DEMOMAN)) { + || (other.playerclass == PC_DEMOMAN)) { if (!(self.ammo_shells + self.ammo_rockets)) { other.nopickup = self; return; @@ -2617,87 +2368,32 @@ void () TeamFortress_AmmoboxTouch = { dremove(self); } bound_other_ammo(other); - sound(other, 3, "weapons/lock4.wav", 1, 1); + FO_Sound(other, CHAN_ITEM, "weapons/lock4.wav", 1, 1); stuffcmd(other, "bf\n"); self = other; - W_SetCurrentAmmo(self); -}; - -void () TeamFortress_AssaultWeapon = { - self.impulse = 0; - if (self.tfstate & TFSTATE_RELOADING) - return; - - if (!(self.weapons_carried & WEAP_ASSAULT_CANNON)) - return; - - if (self.heat > 0) { - sprint(self, PRINT_HIGH, "The Assault Cannon is still overheated\n"); - return; - } - - if (self.ammo_shells < 1) { - sprint(self, PRINT_HIGH, "Not enough ammo\n"); - return; - } - - if (self.ammo_cells < 7 && time >= self.antispam_assault_cannon) { - sprint(self, PRINT_HIGH, "Not enough cells to power the Assault Cannon\n"); - self.antispam_assault_cannon = time + 3; - return; - } - self.current_weapon = WEAP_ASSAULT_CANNON; - W_SetCurrentAmmo(self); }; void () TeamFortress_ExplodePerson = { - local entity te; + entity te; + int gtype = self.fpp.gren_type; + FO_GrenInfo* gdesc = FO_GrenDesc(gtype); - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & 1); + self.owner.tfstate &= ~TFSTATE_GREN_MASK_ALL; KickPlayer(-2, self.owner); - newmis = spawn(); - newmis.movetype = 10; - newmis.solid = 2; - newmis.classname = "grenade"; - newmis.team_no = self.owner.team_no; - newmis.owner = self.owner; - newmis.velocity = '0 0 0'; - newmis.angles = vectoangles(newmis.velocity); - newmis.think = SUB_Null; - newmis.nextthink = time + 0.1; - - if (self.weapon == GR_TYPE_NORMAL) { - newmis.touch = NormalGrenadeTouch; - newmis.think = NormalGrenadeExplode; - newmis.skin = 0; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/hgren2.mdl"); - } else if (self.weapon == GR_TYPE_CONCUSSION) { - newmis.touch = ConcussionGrenadeTouch; - newmis.think = ConcussionGrenadeExplode; - newmis.skin = 1; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/hgren2.mdl"); - } else if (self.weapon == GR_TYPE_NAIL) { - newmis.touch = NailGrenadeTouch; - newmis.think = NailGrenadeExplode; - newmis.skin = 1; - newmis.avelocity = '0 300 0'; - setmodel(newmis, "progs/biggren.mdl"); - } else if (self.weapon == GR_TYPE_MIRV) { - newmis.touch = MirvGrenadeTouch; - newmis.think = MirvGrenadeExplode; - newmis.skin = 0; - newmis.avelocity = '0 300 0'; - setmodel(newmis, "progs/biggren.mdl"); - } else if (self.weapon == GR_TYPE_NAPALM) { - newmis.touch = NapalmGrenadeTouch; - newmis.think = NapalmGrenadeExplode; - newmis.skin = 2; - newmis.avelocity = '0 300 0'; - setmodel(newmis, "progs/biggren.mdl"); - } else if (self.weapon == GR_TYPE_FLARE) { + entity proj = FOProj_Create(FPP_HANDGRENADE); + proj.movetype = MOVETYPE_BOUNCE; + proj.solid = SOLID_BBOX; + proj.classname = "grenade"; + proj.team_no = self.owner.team_no; + proj.owner = self.owner; + proj.velocity = '0 0 0'; + proj.angles = vectoangles(proj.velocity); + proj.think = SUB_Null; + float expires = time + 0.1; // Server generated, no client time here. + proj.nextthink = expires; + + if (self.weapon == GREN_FLARE) { sprint(self.owner, PRINT_HIGH, "Flare lit\n"); te = spawn(); te.touch = SUB_Null; @@ -2707,135 +2403,101 @@ void () TeamFortress_ExplodePerson = { te.solid = 0; self.owner.effects = self.owner.effects | EF_BRIGHTLIGHT; dremove(self); - dremove(newmis); + dremove(proj); return; - } else if (self.weapon == GR_TYPE_GAS) { - newmis.touch = GasGrenadeTouch; - newmis.think = GasGrenadeExplode; - newmis.skin = 2; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/grenade2.mdl"); - } else if (self.weapon == GR_TYPE_EMP) { - newmis.touch = EMPGrenadeTouch; - newmis.think = EMPGrenadeExplode; - newmis.skin = 4; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/grenade2.mdl"); - } else if (self.weapon == GR_TYPE_CALTROP) { - newmis.touch = CaltropTouch; - newmis.think = ScatterCaltrops; - } else if (self.weapon == GR_TYPE_FLASH) { - newmis.touch = FlashGrenadeTouch; - newmis.think = FlashGrenadeExplode; - newmis.skin = 1; - newmis.avelocity = '300 300 300'; - setmodel(newmis, "progs/grenade2.mdl"); - } - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.owner.origin); + } + + stg_table_entry* ste = &stg_table[gtype - GREN_FIRST]; + + proj.fpp.gren_type = gtype; + proj.fpp.flags |= FPF_NO_REWIND; + proj.fpp.expires_at = expires; + proj.skin = gdesc->skin; + proj.avelocity = gdesc->avelocity; + proj.touch = ste->touch; + proj.think = ste->think; + setorigin(proj, self.owner.origin); + FOProj_Finalize(proj); + if ((self.owner.playerclass == PC_SCOUT) && (self.weapon != 10)) - bprint3(PRINT_MEDIUM, "No ", self.owner.netname, + bprint(PRINT_MEDIUM, "No ", self.owner.netname, ", swallowing the grenade isn't very effective!\n"); else if (self.owner.playerclass == PC_SNIPER) - bprint3(PRINT_MEDIUM, "Well ", self.owner.netname, + bprint(PRINT_MEDIUM, "Well ", self.owner.netname, ", don't quit your day job!\n"); else if (self.owner.playerclass == PC_SOLDIER) - bprint3(PRINT_MEDIUM, "Ummm, ", self.owner.netname, + bprint(PRINT_MEDIUM, "Ummm, ", self.owner.netname, ", you're supposed to THROW the grenade!\n"); else if (self.owner.playerclass == PC_DEMOMAN) - bprint3(PRINT_MEDIUM, "Ack! ", self.owner.netname, + bprint(PRINT_MEDIUM, "Ack! ", self.owner.netname, "! The grenade is your friend for another reason!\n"); else if (self.owner.playerclass == PC_MEDIC) { - if (self.weapon == GR_TYPE_CONCUSSION) - bprint3(PRINT_MEDIUM, "Yes ", self.owner.netname, - ", eating a concussion grenade is bad!\n"); - else - bprint3(PRINT_MEDIUM, "No ", self.owner.netname, + if (self.weapon == GREN_BLAST) { + if (medic_type == MEDIC_TYPE_BLAST) { + bprint(PRINT_MEDIUM, "Nice one ", self.owner.netname, + ", you forgot to throw your blast grenade!\n"); + } else { + bprint(PRINT_MEDIUM, "Yes ", self.owner.netname, + ", eating a concussion grenade is bad!\n"); + } + } else { + bprint(PRINT_MEDIUM, "No ", self.owner.netname, "! Assist your own suicide some other time!\n"); + } } else if (self.owner.playerclass == PC_HVYWEAP) - bprint3(PRINT_MEDIUM, "Hey ", self.owner.netname, + bprint(PRINT_MEDIUM, "Hey ", self.owner.netname, ", you're not THAT heavy!\n"); else if (self.owner.playerclass == PC_PYRO) - bprint3(PRINT_MEDIUM, "Yes ", self.owner.netname, + bprint(PRINT_MEDIUM, "Yes ", self.owner.netname, ", the grenade does explode on '3'!\n"); else if (self.owner.playerclass == PC_SPY) - bprint3(PRINT_MEDIUM, "You do realize ", self.owner.netname, + bprint(PRINT_MEDIUM, "You do realize ", self.owner.netname, ", you can blow your cover in easier ways!\n"); else if (self.owner.playerclass == PC_ENGINEER) - bprint3(PRINT_MEDIUM, "Hey ", self.owner.netname, + bprint(PRINT_MEDIUM, "Hey ", self.owner.netname, ", study grenade dynamics on your own time!\n"); else - bprint3(PRINT_MEDIUM, "No ", self.owner.netname, + bprint(PRINT_MEDIUM, "No ", self.owner.netname, ", throw the grenade, not the pin!\n"); dremove(self); }; -void () NormalGrenadeTouch = { - if (other == self.owner) - return; - - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); - if (self.velocity == '0 0 0') - self.avelocity = '0 0 0'; -}; - -void () NormalGrenadeExplode = { - deathmsg = DMSG_GREN_HAND; - T_RadiusDamage(self, self.owner, 180, world); - - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - - multicast(self.origin, MULTICAST_PHS); - dremove(self); -}; - void () TeamFortress_DisplayDetectionItems = { - local entity Goal; + local entity tfdet; local entity te; + te = world; - Goal = find(world, classname, "info_tfdetect"); - if (!Goal) - return; - - if (Goal.display_item_status1 != 0) { - te = Finditem(Goal.display_item_status1); - if (te) - DisplayItemStatus(Goal, self, te); - else - sprint(self, PRINT_HIGH, "Item is missing\n"); - } else - return; - - if (Goal.display_item_status2 != 0) { - te = Finditem(Goal.display_item_status2); - if (te) - DisplayItemStatus(Goal, self, te); - else - sprint(self, PRINT_HIGH, "Item is missing\n"); - } else + tfdet = find(world, classname, "info_tfdetect"); + if (!tfdet) return; - if (Goal.display_item_status3 != 0) { - te = Finditem(Goal.display_item_status3); - if (te) - DisplayItemStatus(Goal, self, te); - else - sprint(self, PRINT_HIGH, "Item is missing\n"); - } else - return; + for (float t = 1; t <= number_of_teams; t++) { + switch (t) + { + case 1: + te = Finditem(tfdet.display_item_status1); + break; + case 2: + te = Finditem(tfdet.display_item_status2); + break; + case 3: + te = Finditem(tfdet.display_item_status3); + break; + case 4: + te = Finditem(tfdet.display_item_status4); + break; + } - if (Goal.display_item_status4 != 0) { - te = Finditem(Goal.display_item_status4); if (te) - DisplayItemStatus(Goal, self, te); + { + sprint(self, PRINT_HIGH, strcat(GetItemStatus(self, te, t), "\n")); + } else + { sprint(self, PRINT_HIGH, "Item is missing\n"); + } } }; @@ -2872,9 +2534,7 @@ void () TeamFortress_RegenerateCells = { if (self.owner.ammo_cells == 0) { self.owner.is_undercover = 0; self.owner.modelindex = modelindex_player; - self.owner.items = - self.owner.items - - (self.owner.items & IT_INVISIBILITY); + self.owner.items &= ~IT_INVISIBILITY; } else { self.owner.ammo_cells = self.owner.ammo_cells - PC_SPY_CELL_USAGE; @@ -2915,7 +2575,7 @@ void () TeamFortress_CheckforCheats = { vf_z = 0; vf = normalize(vf); tf = vplf_x * vf_x + vplf_y * vf_y; - pf = self.owner.maxfbspeed + 100; + pf = self.owner.maxspeed + 100; if ((self.owner.tfstate & TFSTATE_CANT_MOVE) && (tf > 20)) { self.nextthink = time + 0.5; @@ -2934,10 +2594,10 @@ void () TeamFortress_CheckforCheats = { } if (self.owner.cheat_level > 1200) { self.owner.cheat_level = 0; - bprint2(PRINT_MEDIUM, self.owner.netname, + bprint(PRINT_MEDIUM, self.owner.netname, " has been kicked for cheating\n"); sprint(self.owner, PRINT_HIGH, - "You have been kicked for cheating, because of your speed\n"); + "You have been kicked for cheating, because of your speed\n"); KickCheater(self.owner); } }; @@ -2960,7 +2620,7 @@ void () PlayerObserverMode = { stuffcmd(self, "cl_rollangle 0\n"); }; -float (vector veca, vector vecb) crossproduct = { +float (vector veca, vector vecb) crossproducttf = { local float result; result = veca_x * vecb_y - vecb_x * veca_y; @@ -2991,6 +2651,7 @@ void (entity pl, float fr) TF_AddFrags = { team3score = team3score + fr; else if (pl.team_no == 4) team4score = team4score + fr; + UpdateAllClientsTeamScores(); //Update scoreboard for indi frags } if (pl.team_no == 1) @@ -3014,43 +2675,34 @@ void (entity pl, float fr) TF_AddFrags = { }; void (entity p) TeamFortress_ExecClassScript = { - local string st; + local float st = FO_GetUserSetting(p, "ec", "exec_class", "off"); - st = infokey(p, "ec"); - if (st == string_null) - st = infokey(p, "exec_class"); - - if (st == "1") { - stuffcmd(p, "exec classes/default.cfg\n"); + if (st) { if (p.playerclass == PC_SCOUT) - stuffcmd(p, "exec classes/scout.cfg\n"); + stuffcmd(p, "exec scout.cfg\n"); else if (p.playerclass == PC_SNIPER) - stuffcmd(p, "exec classes/sniper.cfg\n"); + stuffcmd(p, "exec sniper.cfg\n"); else if (p.playerclass == PC_SOLDIER) - stuffcmd(p, "exec classes/soldier.cfg\n"); + stuffcmd(p, "exec soldier.cfg\n"); else if (p.playerclass == PC_DEMOMAN) - stuffcmd(p, "exec classes/demoman.cfg\n"); + stuffcmd(p, "exec demoman.cfg\n"); else if (p.playerclass == PC_MEDIC) - stuffcmd(p, "exec classes/medic.cfg\n"); + stuffcmd(p, "exec medic.cfg\n"); else if (p.playerclass == PC_HVYWEAP) - stuffcmd(p, "exec classes/hwguy.cfg\n"); + stuffcmd(p, "exec hwguy.cfg\n"); else if (p.playerclass == PC_PYRO) - stuffcmd(p, "exec classes/pyro.cfg\n"); + stuffcmd(p, "exec pyro.cfg\n"); else if (p.playerclass == PC_SPY) - stuffcmd(p, "exec classes/spy.cfg\n"); + stuffcmd(p, "exec spy.cfg\n"); else if (p.playerclass == PC_ENGINEER) - stuffcmd(p, "exec classes/engineer.cfg\n"); + stuffcmd(p, "exec engineer.cfg\n"); } }; void (entity p) TeamFortress_ExecMapScript = { - local string st; - - st = infokey(p, "em"); - if (st == string_null) - st = infokey(p, "exec_map"); + local float st = FO_GetUserSetting(p, "em", "exec_map", "off"); - if (st == "1") { + if (st) { stuffcmd(p, "exec maps/default.cfg\n"); stuffcmd(p, "exec maps/"); stuffcmd(p, mapname); @@ -3064,8 +2716,7 @@ void (entity p) KickCheater = { p.touch = SUB_Null; p.health = 0; p.solid = SOLID_NOT; - p.tfstate = p.tfstate | TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(p); + p.tfstate |= TFSTATE_CANT_MOVE; TeamFortress_RemoveTimers(); }; diff --git a/ssqc/tforthlp.qc b/ssqc/tforthlp.qc new file mode 100644 index 000000000..ab35927d0 --- /dev/null +++ b/ssqc/tforthlp.qc @@ -0,0 +1,84 @@ +//======================================================== +// Functions handling all the help displaying for TeamFortress. +//======================================================== +// + +// so you can select a team (blindly) while reading the MOTD +void (float inp) MOTD_Input = { + Menu_Team_Input(inp); +}; + +void TeamFortress_MOTD() { + if (votemode) + return; + + if (loginRequired && !self.login) + Menu_Login(); + else + Menu_Team(0); + + float csqcactive = infokeyf(self, INFOKEY_P_CSQCACTIVE); + if (csqcactive) { + self.motd = -1; + return; + } + // Below is not really tested, it's an attempt at preserving for posterity. + // Expect potential bugs if we ever reintroduce !CSQC support. + const float ALIAS_OFFSET = 100; + + if (self.motd == 0) { + if ((teamplay != 0) && (self.team_no == 0)) + stuffcmd(self, "color 0\n"); + + sprint(self, PRINT_HIGH, "\nFortressOne ", VER, "\n\n"); + + string st1, st2; + st1 = infokey(world, "motd1"); + if (st1 != string_null) { + st2 = infokey(world, "motd2"); + if (st2 != string_null) { + st1 = strcat(strcat(st1, "\n"), st2); + } + } else { + st1 = "Welcome to FortressOne\n==================================\nwww.github.com/FortressOne"; + } + + sprint(self, PRINT_HIGH, strcat(st1, "\n\n\n")); + if(self.team_no == 0 && !intermission_running) + Status_Menu(self, MOTD_Input, st1); + + self.motd = ALIAS_OFFSET; + } + + float idx = self.motd - ALIAS_OFFSET; + if (idx >= 0 && idx < client_aliases.length) { + TFAlias* alias = &client_aliases[idx]; + + if (alias->nocsqc_cmd) + TeamFortress_AliasString(alias->alias, alias->nocsqc_cmd); + else if (alias->nocsqc_impulse) + TeamFortress_Alias(alias->alias, alias->nocsqc_impulse, 0); + else if (alias->impulse) + TeamFortress_Alias(alias->alias, alias->impulse, 0); + else + TeamFortress_AliasString(alias->alias, alias->cmd); + } + + if (idx < client_aliases.length) + self.motd += 1; + else + self.motd = -1; +}; + +void () TeamFortress_HelpMap = { + local entity te; + + te = find(world, classname, "info_tfdetect"); + if (te) { + if (te.non_team_broadcast != string_null) { + sprint(self, PRINT_HIGH, te.non_team_broadcast); + return; + } + } + sprint(self, PRINT_HIGH, "There is no help for this map\n"); +}; diff --git a/tfortmap.qc b/ssqc/tfortmap.qc similarity index 69% rename from tfortmap.qc rename to ssqc/tfortmap.qc index 461abf621..1862b5d8c 100644 --- a/tfortmap.qc +++ b/ssqc/tfortmap.qc @@ -41,8 +41,7 @@ void () ReturnItem; void (entity P) ForceRespawn; -void (entity Goal) UpdateAbbreviations = -{ +void (entity Goal) UpdateAbbreviations = { local string st; if (Goal.has_abbreviated == 0) { @@ -166,17 +165,17 @@ void (entity Goal) UpdateAbbreviations = flagem_checked = 1; } if ((toggleflags & TFLAG_FLAGEMU) && - !(toggleflags & TFLAG_WARSTANDARD)) { + !(toggleflags & TFLAG_WARSTANDARD)) { if ((Goal.mdl == "progs/b_s_key.mdl") - || (Goal.mdl == "progs/m_s_key.mdl") - || (Goal.mdl == "progs/w_s_key.mdl")) { + || (Goal.mdl == "progs/m_s_key.mdl") + || (Goal.mdl == "progs/w_s_key.mdl")) { Goal.mdl = "progs/tf_flag.mdl"; Goal.origin_z = Goal.origin_z + 6; Goal.skin = 1; } else if ((Goal.mdl == "progs/b_g_key.mdl") - || (Goal.mdl == "progs/m_g_key.mdl") - || (Goal.mdl == "progs/w_g_key.mdl")) { + || (Goal.mdl == "progs/m_g_key.mdl") + || (Goal.mdl == "progs/w_g_key.mdl")) { Goal.mdl = "progs/tf_flag.mdl"; Goal.origin_z = Goal.origin_z + 6; @@ -185,14 +184,14 @@ void (entity Goal) UpdateAbbreviations = } if (toggleflags & TFLAG_WARSTANDARD) { if ((Goal.mdl == "progs/b_s_key.mdl") - || (Goal.mdl == "progs/m_s_key.mdl") - || (Goal.mdl == "progs/w_s_key.mdl")) { + || (Goal.mdl == "progs/m_s_key.mdl") + || (Goal.mdl == "progs/w_s_key.mdl")) { Goal.mdl = "progs/tf_stan.mdl"; Goal.skin = 1; } else if ((Goal.mdl == "progs/b_g_key.mdl") - || (Goal.mdl == "progs/m_g_key.mdl") - || (Goal.mdl == "progs/w_g_key.mdl")) { + || (Goal.mdl == "progs/m_g_key.mdl") + || (Goal.mdl == "progs/w_g_key.mdl")) { Goal.mdl = "progs/tf_stan.mdl"; Goal.skin = 2; @@ -223,9 +222,6 @@ void () TF_PlaceItem = { self.movetype = 0; self.oldorigin = self.origin; - if (self.goal_activation & TFGI_ITEMGLOWS) - self.effects = self.effects | EF_DIMLIGHT; - if (item_list_bit == 0) item_list_bit = 1; @@ -233,11 +229,66 @@ void () TF_PlaceItem = { item_list_bit = item_list_bit * 2; }; +float TF_Item_GetGlow(float team) +{ + float ret = 0; + switch (team) + { + case 1: + ret = EF_BLUE; + break; + case 2: + ret = EF_RED; + break; + case 3: + ret = EF_RED + EF_MUZZLEFLASH; + break; + case 4: + ret = EF_MUZZLEFLASH; + break; + default: + ret = EF_DIMLIGHT; + } + + return ret; +} + +void TF_Item_ApplyGlow() +{ + if (self.spawnflags & TFGI_NOGLOW) + return; + + local entity tfdet; //info_tfdetect entity + tfdet = find(world, classname, "info_tfdetect"); + + if (tfdet && (tfdet.display_item_status1 == self.goal_no || tfdet.display_item_status2 == self.goal_no || tfdet.display_item_status3 == self.goal_no || tfdet.display_item_status4 == self.goal_no)) { + self.effects = self.effects | TF_Item_GetGlow(self.owned_by); + } else { + if (self.model) + { + switch (self.model) + { + case "progs/basrkey.bsp": + self.effects = self.effects | EF_DIMLIGHT; + self.effects = self.effects | EF_RED; + break; + case "progs/basbkey.bsp": + self.effects = self.effects | EF_DIMLIGHT; + self.effects = self.effects | EF_BLUE; + break; + default: + self.effects = self.effects | TF_Item_GetGlow(self.owned_by); + } + } + } +} + void () TF_StartItem = { + TF_Item_ApplyGlow(); UpdateAbbreviations(self); self.nextthink = time + 0.2; self.think = TF_PlaceItem; - if (self.goal_state == 3) + if (self.goal_state == TFGS_REMOVED) RemoveGoal(self); }; @@ -246,7 +297,9 @@ void () TF_PlaceGoal = { if (self.classname != "info_tfgoal_timer") { if (self.goal_activation & TFGA_TOUCH) { - self.touch = tfgoal_touch; + if(!duelmode || !duel_no_packs) { + self.touch = tfgoal_touch; + } } } else { self.think = tfgoal_timer_tick; @@ -254,6 +307,7 @@ void () TF_PlaceGoal = { self.classname = "info_tfgoal"; } if (self.goal_activation & 2048) { + self.movetype = MOVETYPE_TOSS; self.origin_z = self.origin_z + 6; oldz = self.origin_z; @@ -276,7 +330,7 @@ void () TF_StartGoal = { self.nextthink = time + 0.2; self.think = TF_PlaceGoal; self.use = info_tfgoal_use; - if (self.goal_state == 3) + if (self.goal_state == TFGS_REMOVED) RemoveGoal(self); }; @@ -289,10 +343,10 @@ float () CheckExistence = { return (FALSE); } if ((self.ex_skill_min && (self.ex_skill_min != -1)) && - (skill < self.ex_skill_min)) { + (skill < self.ex_skill_min)) { return (FALSE); } else if ((self.ex_skill_max && (self.ex_skill_max != -1)) && - (skill > self.ex_skill_max)) { + (skill > self.ex_skill_max)) { return (FALSE); } return (TRUE); @@ -333,6 +387,19 @@ void () info_player_teamspawn = { } }; +void () info_player_start = { + if (self.allowteams == "blue") { + self.classname = "info_player_teamspawn"; + self.team_no = 1; + info_player_teamspawn(); + } + if (self.allowteams == "red") { + self.classname = "info_player_teamspawn"; + self.team_no = 2; + info_player_teamspawn(); + } +}; + void () i_p_t = { self.classname = "info_player_teamspawn"; info_player_teamspawn(); @@ -346,7 +413,7 @@ void () info_tfgoal = { if (self.mdl) { precache_model(self.mdl); precache_model2(self.mdl); - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); } if (self.noise) { precache_sound(self.noise); @@ -392,7 +459,7 @@ void () info_tfgoal_timer = { if (self.mdl) { precache_model(self.mdl); precache_model2(self.mdl); - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); } if (self.noise) { precache_sound(self.noise); @@ -429,7 +496,7 @@ void () item_tfgoal = { if (self.mdl) { precache_model(self.mdl); precache_model2(self.mdl); - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); } else { self.mdl = ""; setmodel(self, ""); @@ -530,7 +597,7 @@ void (entity AD) ParseTFDetect = { entity(float ino) Finditem = { local entity tg; - local string st; + //local string st; tg = find(world, classname, "item_tfgoal"); while (tg) { @@ -538,26 +605,31 @@ entity(float ino) Finditem = return (tg); tg = find(tg, classname, "item_tfgoal"); } - dprint("Could not find an item with a goal_no of "); - st = ftos(ino); - dprint(st); - dprint(".\n"); + //dprint("Could not find an item with a goal_no of "); + //st = ftos(ino); + //dprint(st); + //dprint(".\n"); return world; }; entity(float gno) Findgoal = { - local entity tg; - local string st; - - tg = find(world, classname, "info_tfgoal"); - while (tg) { - if (tg.goal_no == gno) - return (tg); - tg = find(tg, classname, "info_tfgoal"); + for (float i = 0; i < goal_class_names.length; i++) + { + string goal_type = goal_class_names[i]; + entity tg = find(world, classname, goal_type); + while (tg) + { + if (tg.goal_no == gno) + { + return tg; + } + tg = find(tg, classname, goal_type); + } } + dprint("Could not find a goal with a goal_no of "); - st = ftos(gno); + string st = ftos(gno); dprint(st); dprint(".\n"); return world; @@ -582,44 +654,72 @@ entity(float gno) Findteamspawn = }; void (entity Goal) InactivateGoal = { - if (Goal.goal_state == 1) { + if (Goal.goal_state == TFGS_ACTIVE) { if (Goal.search_time == 0) { if ((Goal.goal_activation & 8192) && - (Goal.classname == "item_tfgoal")) + (Goal.classname == "item_tfgoal")) Goal.solid = SOLID_BBOX; else Goal.solid = SOLID_TRIGGER; } - Goal.goal_state = 2; - if (Goal.mdl != string_null) - setmodel(Goal, Goal.mdl); + Goal.goal_state = TFGS_INACTIVE; + if (Goal.mdl != string_null && !(duelmode && duel_no_packs)) { + FO_SetModel(Goal, Goal.mdl); + } } }; +void DefaultGoalState (entity Goal) +{ + if ((Goal.goal_activation & 8192) && + (Goal.classname == "item_tfgoal")) { + Goal.solid = SOLID_BBOX; + } else { + Goal.solid = SOLID_TRIGGER; + } + Goal.goal_state = TFGS_NONE; +} + void (entity Goal) RestoreGoal = { - if (Goal.goal_state == 3) { + if (Goal.goal_state == TFGS_REMOVED) { + Goal.dimension_seen = DMN_NOFLASH; + if (Goal.search_time == 0) { - if ((Goal.goal_activation & 8192) && - (Goal.classname == "item_tfgoal")) { - Goal.solid = 2; - } else { - Goal.solid = 1; + if ( + ((Goal.goal_activation & 8192) && (Goal.classname == "item_tfgoal")) + || Goal.classname == "door" + ) + { + Goal.solid = SOLID_BBOX; + } + else + { + Goal.solid = SOLID_TRIGGER; } } else { Goal.nextthink = time + Goal.search_time; } - Goal.goal_state = 2; - if (Goal.mdl != string_null) { - setmodel(Goal, Goal.mdl); + Goal.goal_state = TFGS_INACTIVE; + if (Goal.mdl) { + FO_SetModel(Goal, Goal.mdl); + setsize(Goal, Goal.goal_min, Goal.goal_max); + entity oldself; + oldself = self; + self = Goal; + TF_Item_ApplyGlow(); + self = oldself; } } }; void (entity Goal) RemoveGoal = { + Goal.old_dimension_seen = Goal.dimension_seen; + Goal.dimension_seen = DMN_INVISIBLE; + Goal.solid = 0; - Goal.goal_state = 3; + Goal.goal_state = TFGS_REMOVED; if (Goal.mdl != string_null) - setmodel(Goal, string_null); + FO_SetModel(Goal, string_null); }; float (entity Goal, entity Player, entity AP) IsAffectedBy = { @@ -661,11 +761,11 @@ float (entity Goal, entity Player, entity AP) IsAffectedBy = { } } if ((Goal.maxammo_shells != 0) && - (Player.team_no == Goal.maxammo_shells)) { + (Player.team_no == Goal.maxammo_shells)) { return (1); } if ((Goal.maxammo_nails != 0) && - (Player.team_no != Goal.maxammo_shells)) { + (Player.team_no != Goal.maxammo_shells)) { return (1); } return (0); @@ -683,15 +783,15 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { if (Player == AP) { if (Goal.count > 0) { if (Player.team_no > 0) { + LogEventGoal(Player); TeamFortress_TeamIncreaseScore(Player.team_no, Goal.count); TeamFortress_TeamShowScores(2); - Player.caps = Player.caps + 1; } } } if (addb) { if (Player.health > 0) { - if (Goal.health > 0) { + if (Goal.health > 0 && !(duelmode && duel_no_packs)) { if (stockfull) T_Heal(Player, Player.max_health, 0); else @@ -700,146 +800,233 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { if (Goal.health < 0) { if ((0 - Player.health) > Goal.health) { TF_T_Damage(Player, Goal, Goal, (Player.health + 1), 1, - 0); + 0); } else { TF_T_Damage(Player, Goal, Goal, (0 - Goal.health), 1, - 0); + 0); } } - } - if (Player.health > 0) { - if (stockfull) { - if (Goal.armortype > 0) { - Player.armortype = Player.armor_allowed; - Player.armorvalue = Player.maxarmor; - } else if (Goal.armorvalue > 0) { - Player.armortype = Player.armor_allowed; - Player.armorvalue = Player.maxarmor; + if(!(duelmode && duel_no_packs)) { + if (stockfull) { + if (Goal.armortype > 0) { + Player.armortype = Player.armor_allowed; + Player.armorvalue = Player.maxarmor; + } else if (Goal.armorvalue > 0) { + Player.armortype = Player.armor_allowed; + Player.armorvalue = Player.maxarmor; + } + } else { + if (Goal.armortype > 0) + Player.armortype = Goal.armortype; + else if (Goal.armorvalue > 0) + Player.armortype = Player.armor_allowed; + Player.armorvalue = Player.armorvalue + Goal.armorvalue; } - } else { - if (Goal.armortype > 0) - Player.armortype = Goal.armortype; - else if (Goal.armorvalue > 0) - Player.armortype = Player.armor_allowed; - Player.armorvalue = Player.armorvalue + Goal.armorvalue; - } - if (Goal.armorclass > 0) - Player.armorclass = Goal.armorclass; - - Player.ammo_shells = Player.ammo_shells + Goal.ammo_shells; - Player.ammo_nails = Player.ammo_nails + Goal.ammo_nails; - Player.ammo_rockets = Player.ammo_rockets + Goal.ammo_rockets; - Player.ammo_cells = Player.ammo_cells + Goal.ammo_cells; - Player.ammo_medikit = Player.ammo_medikit + Goal.ammo_medikit; - Player.ammo_detpack = Player.ammo_detpack + Goal.ammo_detpack; - Player.no_grenades_1 = - Player.no_grenades_1 + Goal.no_grenades_1; - Player.no_grenades_2 = - Player.no_grenades_2 + Goal.no_grenades_2; - - if (Player.no_grenades_1 > 4) { - Player.no_grenades_1 = 4; - } - if (Player.no_grenades_2 > 4) { - Player.no_grenades_2 = 4; - } - if ((Player.tp_grenades_1 == GR_TYPE_NAIL) && - (Player.no_grenades_1 > 2)) { - Player.no_grenades_1 = 2; - } - if ((Player.tp_grenades_2 == GR_TYPE_NAIL) && - (Player.no_grenades_2 > 2)) { - Player.no_grenades_2 = 2; - } - if (Player.ammo_detpack > Player.maxammo_detpack) { - Player.ammo_detpack = Player.maxammo_detpack; - } - if (Player.tfstate & TFSTATE_GRENPRIMED) { - te = find(world, classname, "primer"); - while (te) { - if (te.owner == Player) { - if ((te.impulse == TF_GRENADE_2) && - (Player.no_grenades_2 <= 0)) { - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENPRIMED); - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENTHROWING); - dremove(te); - } else if ((te.impulse == TF_GRENADE_1) && - (Player.no_grenades_1 <= 0)) { - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENPRIMED); - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENTHROWING); - dremove(te); + if (Goal.armorclass > 0) + Player.armorclass = Goal.armorclass; + + Player.ammo_shells = Player.ammo_shells + Goal.ammo_shells; + Player.ammo_nails = Player.ammo_nails + Goal.ammo_nails; + Player.ammo_rockets = Player.ammo_rockets + Goal.ammo_rockets; + Player.ammo_cells = Player.ammo_cells + Goal.ammo_cells; + Player.ammo_medikit = Player.ammo_medikit + Goal.ammo_medikit; + Player.ammo_detpack = Player.ammo_detpack + Goal.ammo_detpack; + //refill the clip if stock_reload is 2 + if (stock_reload == 2) { + FO_WeapState ws; + FO_FillWeapState(Player, Player.current_slot, &ws); + FO_WeapInfo* wi = ws->wi; + + + float* ammo_ptr = __NULL__; + if (Goal.ammo_shells && wi->ammo_type == AMMO_SHELLS) + ammo_ptr = &Goal.ammo_shells; + else if (Goal.ammo_rockets && wi->ammo_type == AMMO_ROCKETS) + ammo_ptr = &Goal.ammo_rockets; + else if (Goal.ammo_cells && wi->ammo_type == AMMO_CELLS) + ammo_ptr = &Goal.ammo_cells; + else if (Goal.ammo_cells && wi->ammo_type == AMMO_NAILS) + ammo_ptr = &Goal.ammo_nails; + + if (wi->needs_reload && ammo_ptr != __NULL__) { + Player.tfstate &= ~TFSTATE_RELOADING; + *ws->clip_fired = max(*ws->clip_fired - *ammo_ptr, 0); + } + } else { + //for use in map entities without specifically enabling the general override + FO_WeapState ws; + FO_FillWeapState(Player, Player.current_slot, &ws); + + if ((Goal.reload_shotgun && ws.weapon == WEAP_SHOTGUN) || + (Goal.reload_super_shotgun && ws.weapon == WEAP_SUPER_SHOTGUN) || + (Goal.reload_sniper_rifle && ws.weapon == WEAP_SNIPER_RIFLE) || + (Goal.reload_assault_cannon && ws.weapon == WEAP_ASSAULT_CANNON)) { + Player.tfstate &= ~TFSTATE_RELOADING; + *ws->clip_fired = max(*ws->clip_fired - Goal.ammo_shells, 0); + } + + if ((Goal.reload_grenade_launcher && ws.weapon == WEAP_GRENADE_LAUNCHER) || + (Goal.reload_rocket_launcher && ws.weapon == WEAP_ROCKET_LAUNCHER)) { + Player.tfstate &= ~TFSTATE_RELOADING; + *ws->clip_fired = max(*ws->clip_fired - Goal.ammo_rockets, 0); + } + } + + if (disable_resup_gren & 1 == 0) + Player.no_grenades_1 = min(Player.no_grenades_1 + Goal.no_grenades_1, + Player.max_grenades_1); + if (disable_resup_gren & 2 == 0) + Player.no_grenades_2 = min(Player.no_grenades_2 + Goal.no_grenades_2, + Player.max_grenades_2); + + if (Player.ammo_detpack > Player.maxammo_detpack) { + Player.ammo_detpack = Player.maxammo_detpack; + } + if (Player.tfstate & TFSTATE_GREN_MASK_PRIMED) { + te = find(world, classname, "primer"); + while (te) { + if (te.owner == Player) { + if ((te.impulse == TF_GRENADE_2) && + (Player.no_grenades_2 <= 0)) { + Player.tfstate &= TFSTATE_GREN_MASK_ALL; + dremove(te); + } else if ((te.impulse == TF_GRENADE_1) && + (Player.no_grenades_1 <= 0)) { + Player.tfstate &= TFSTATE_GREN_MASK_ALL; + dremove(te); + } + te = world; + } else { + te = find(te, classname, "primer"); } - te = world; - } else { - te = find(te, classname, "primer"); } } - } - if (Goal.invincible_finished > 0) { - Player.items = Player.items | IT_INVULNERABILITY; - Player.invincible_time = 1; - Player.invincible_finished = - time + Goal.invincible_finished; - if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_INVINCIBLE; - Player.invincible_finished = time + 666; + if (Goal.invincible_finished > 0) { + Player.items = Player.items | IT_INVULNERABILITY; + Player.invincible_time = 1; + Player.invincible_finished = + time + Goal.invincible_finished; + if (Goal.classname == "item_tfgoal") { + Player.pstate |= PSTATE_INVINCIBLE; + Player.invincible_finished = time + 666; + } } - } - if (Goal.invisible_finished > 0) { - Player.items = Player.items | IT_INVISIBILITY; - Player.invisible_time = 1; - Player.invisible_finished = time + Goal.invisible_finished; - if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_INVISIBLE; - Player.invisible_finished = time + 666; + if (Goal.invisible_finished > 0) { + Player.items = Player.items | IT_INVISIBILITY; + Player.invisible_time = 1; + Player.invisible_finished = time + Goal.invisible_finished; + if (Goal.classname == "item_tfgoal") { + Player.pstate |= PSTATE_INVISIBLE; + Player.invisible_finished = time + 666; + } } - } - if (Goal.super_damage_finished > 0) { - Player.items = Player.items | IT_QUAD; - Player.super_time = 1; - Player.super_damage_finished = - time + Goal.super_damage_finished; - if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_QUAD; - Player.super_damage_finished = time + 666; + if (Goal.super_damage_finished > 0) { + Player.items = Player.items | IT_QUAD; + Player.super_time = 1; + Player.super_damage_finished = + time + Goal.super_damage_finished; + if (Goal.classname == "item_tfgoal") { + Player.pstate |= PSTATE_QUAD; + Player.super_damage_finished = time + 666; + } } - } - if (Goal.radsuit_finished > 0) { - Player.items = Player.items | IT_SUIT; - Player.rad_time = 1; - Player.radsuit_finished = time + Goal.radsuit_finished; - if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_RADSUIT; - Player.radsuit_finished = time + 666; + if (Goal.radsuit_finished > 0) { + Player.items = Player.items | IT_SUIT; + Player.rad_time = 1; + Player.radsuit_finished = time + Goal.radsuit_finished; + if (Goal.classname == "item_tfgoal") { + Player.pstate |= PSTATE_RADSUIT; + Player.radsuit_finished = time + 666; + } } } } - Player.lives = Player.lives + Goal.lives; + Player.lives = Player.lives + Goal.lives; + if (Goal.frags) { - if ((Goal.goal_effects == TFGE_AP) || - !(toggleflags & TFLAG_FULLTEAMSCORE)) + if ((Goal.goal_effects == TFGE_AP) || !(toggleflags & TFLAG_FULLTEAMSCORE)) { TF_AddFrags(Player, Goal.frags); + } + + if (stock_on_cap) { + if (Player == AP) { + if (cap_strip) + TeamPlay_Cap(AP.origin, AP); + + if (Player.health > 0) { + T_Heal(Player, Player.max_health, 0); + Player.armortype = Player.armor_allowed; + Player.armorvalue = Player.maxarmor; + + Player.ammo_shells = Player.maxammo_shells; + Player.ammo_nails = Player.maxammo_nails; + Player.ammo_rockets = Player.maxammo_rockets; + Player.ammo_cells = Player.maxammo_cells; + Player.ammo_medikit = Player.maxammo_medikit; + Player.ammo_detpack = Player.maxammo_detpack; + + Player.no_grenades_1 = Player.max_grenades_1; + Player.no_grenades_2 = Player.max_grenades_2; + + te = find(Player, classname, "timer"); + + RemoveConc(Player); + + if (Player.tfstate & TFSTATE_HALLUCINATING) { + if (te.think == HallucinationTimer) { + if (old_grens == TRUE) { + stuffcmd(trace_ent, "v_cshift; wait; bf\n"); + } + dremove(te); + } + } + + if (Player.tfstate & TFSTATE_TRANQUILISED) { + if (te.think == TranquiliserTimer) { + Player.tfstate = + Player.tfstate - + (Player.tfstate & TFSTATE_TRANQUILISED); + TeamFortress_SetSpeed(Player); + dremove(te); + } + } + + if (Player.tfstate & TFSTATE_INFECTED) { + Player.tfstate = + trace_ent.tfstate - + (trace_ent.tfstate & TFSTATE_INFECTED); + } + + if (Player.numflames > 0) { + FO_Sound(Player, CHAN_WEAPON, "items/r_item1.wav", 1, ATTN_NORM); + SetFlameCount(Player, 0); + } + + if (Player.FlashTime > 0) { + Player.FlashTime = 0; + stuffcmd(Player, "v_cshift; wait; bf\n"); + te = find(Player, netname, "flashtimer"); + dremove(te); + } + //Refill the clip while restocking + if (stock_reload) + FO_InstantReloadAllWeapons(Player); + } + } + } } + oldself = self; self = Player; TeamFortress_CheckClassStats(); - W_SetCurrentAmmo(self); - sound(self, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); self = oldself; } if ((Player.playerclass == PC_SPY) && - (Goal.goal_result & TFGR_REMOVE_DISGUISE)) { + (Goal.goal_result & TFGR_REMOVE_DISGUISE)) { self.immune_to_check = time + 10; Spy_RemoveDisguise(Player); } + if ((Goal.items != 0) && (Goal.classname != "item_tfgoal")) { te = Finditem(Goal.items); if ((te != world) && (te != Goal)) @@ -854,7 +1041,7 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { te = find(world, classname, "item_tfgoal"); while (te) { if ((te.group_no == Goal.remove_item_group) && - (te.owner == AP)) { + (te.owner == AP)) { oldte = te; te = find(te, classname, "item_tfgoal"); tfgoalitem_RemoveFromPlayer(oldte, Player, 1); @@ -892,6 +1079,18 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { } if (Goal.goal_result & TFGR_FORCE_RESPAWN) ForceRespawn(Player); + + local float autodisguise = FO_GetUserSetting(Player, "autodisguise", "ad", "off"); + if (Player.playerclass == PC_SPY) { + switch(autodisguise) { + case 1: + FO_Spy_DisguiseLastSpawned(Player, FALSE); + break; + case 2: + FO_Spy_DisguiseLast(Player, FALSE); + break; + } + } }; void (entity Goal, entity Player) RemoveResults = { @@ -924,7 +1123,7 @@ void (entity Goal, entity Player) RemoveResults = { Player.armorclass - (Player.armorclass & Goal.armorclass); if (Goal.frags) { if ((Goal.goal_effects == TFGE_AP) || - !(toggleflags & TFLAG_FULLTEAMSCORE)) + !(toggleflags & TFLAG_FULLTEAMSCORE)) TF_AddFrags(Player, Goal.frags); } Player.ammo_shells = Player.ammo_shells - Goal.ammo_shells; @@ -936,43 +1135,26 @@ void (entity Goal, entity Player) RemoveResults = { Player.no_grenades_1 = Player.no_grenades_1 - Goal.no_grenades_1; Player.no_grenades_2 = Player.no_grenades_2 - Goal.no_grenades_2; - if (Player.no_grenades_1 > 4) - Player.no_grenades_1 = 4; - - if (Player.no_grenades_2 > 4) - Player.no_grenades_2 = 4; + if (Player.no_grenades_1 > Player.max_grenades_1) + Player.no_grenades_1 = Player.max_grenades_1; - if ((Player.tp_grenades_1 == GR_TYPE_NAIL) && - (Player.no_grenades_1 > 2)) - Player.no_grenades_1 = 2; - if ((Player.tp_grenades_2 == GR_TYPE_NAIL) && - (Player.no_grenades_2 > 2)) - Player.no_grenades_2 = 2; + if (Player.no_grenades_2 > Player.max_grenades_2) + Player.no_grenades_2 = Player.max_grenades_2; if (Player.ammo_detpack > Player.maxammo_detpack) Player.ammo_detpack = Player.maxammo_detpack; - if (Player.tfstate & TFSTATE_GRENPRIMED) { + if (Player.tfstate & TFSTATE_GREN_MASK_PRIMED) { te = find(world, classname, "primer"); while (te) { if (te.owner == Player) { if ((te.impulse == TF_GRENADE_2) && - (Player.no_grenades_2 <= 0)) { - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENPRIMED); - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENTHROWING); + (Player.no_grenades_2 <= 0)) { + Player.tfstate &= ~TFSTATE_GREN_MASK_ALL; dremove(te); } else if ((te.impulse == TF_GRENADE_1) && - (Player.no_grenades_1 <= 0)) { - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENPRIMED); - Player.tfstate = - Player.tfstate - - (Player.tfstate & TFSTATE_GRENTHROWING); + (Player.no_grenades_1 <= 0)) { + Player.tfstate &= ~TFSTATE_GREN_MASK_ALL; dremove(te); } te = world; @@ -999,28 +1181,25 @@ void (entity Goal, entity Player) RemoveResults = { te = find(te, classname, "item_tfgoal"); } if ((Goal.invincible_finished > 0) && !puinvin) { - Player.tfstate = - Player.tfstate - (Player.tfstate & TFSTATE_INVINCIBLE); + Player.pstate &= ~PSTATE_INVINCIBLE; Player.items = Player.items | IT_INVULNERABILITY; Player.invincible_time = 1; Player.invincible_finished = time + Goal.invincible_finished; } if ((Goal.invisible_finished > 0) && !puinvis) { - Player.tfstate = - Player.tfstate - (Player.tfstate & TFSTATE_INVISIBLE); + Player.pstate &= ~PSTATE_INVISIBLE; Player.items = Player.items | IT_INVISIBILITY; Player.invisible_time = 1; Player.invisible_finished = time + Goal.invisible_finished; } if ((Goal.super_damage_finished > 0) && !puquad) { - Player.tfstate = Player.tfstate - (Player.tfstate & TFSTATE_QUAD); + Player.pstate &= ~PSTATE_QUAD; Player.items = Player.items | IT_QUAD; Player.super_time = 1; Player.super_damage_finished = time + Goal.super_damage_finished; } if ((Goal.radsuit_finished > 0) && !purad) { - Player.tfstate = - Player.tfstate - (Player.tfstate & TFSTATE_RADSUIT); + Player.pstate &= ~PSTATE_RADSUIT; Player.items = Player.items | IT_SUIT; Player.rad_time = 1; Player.radsuit_finished = time + Goal.radsuit_finished; @@ -1028,7 +1207,6 @@ void (entity Goal, entity Player) RemoveResults = { oldself = self; self = Player; TeamFortress_CheckClassStats(); - W_SetCurrentAmmo(self); self = oldself; }; @@ -1100,7 +1278,7 @@ float (entity Goal, entity AP) APMeetsCriteria = { te = Finditem(Goal.if_item_has_moved); if (te) { if ((te.goal_state != TFGS_ACTIVE) && - (te.origin == te.oldorigin)) + (te.origin == te.oldorigin)) return (0); } } @@ -1108,7 +1286,7 @@ float (entity Goal, entity AP) APMeetsCriteria = { te = Finditem(Goal.if_item_hasnt_moved); if (te) { if ((te.goal_state == TFGS_ACTIVE) || - (te.origin != te.oldorigin)) + (te.origin != te.oldorigin)) return (0); } } @@ -1118,7 +1296,7 @@ float (entity Goal, entity AP) APMeetsCriteria = { te = find(world, classname, "item_tfgoal"); while ((te != world) && !gotone) { if ((te.group_no == Goal.has_item_from_group) && - (te.owner == AP)) + (te.owner == AP)) gotone = 1; te = find(te, classname, "item_tfgoal"); } @@ -1129,7 +1307,7 @@ float (entity Goal, entity AP) APMeetsCriteria = { te = find(world, classname, "item_tfgoal"); while ((te != world) && !gotone) { if ((te.group_no == Goal.hasnt_item_from_group) && - (te.owner == AP)) + (te.owner == AP)) return (0); te = find(te, classname, "item_tfgoal"); } @@ -1195,9 +1373,35 @@ float (entity Goal, entity AP) Activated = { void (entity Goal, entity AP, entity ActivatingGoal) AttemptToActivate = { local entity te; - if (cb_prematch_time > time) + if (cb_prematch) return; + // q3f support + if (Goal.checkstate != "") { + string cname, cstate; + float slen, idx; + entity targ; + targ = world; + + idx = strstrofs(Goal.checkstate, "="); + slen = strlen(Goal.checkstate); + cname = substring(Goal.checkstate, 0, idx); + targ = find(targ, targetname, cname); + cstate = substring(Goal.checkstate, idx + 1, slen); + switch (cstate) + { + case "inactive": + if (targ.active == TFGS_ACTIVE) { + return; + } + break; + case "active": + if (targ.active == TFGS_INACTIVE) { + return; + } + } + } + if (Activated(Goal, AP)) { if (ActivatingGoal == Goal) DoResults(Goal, AP, 1); @@ -1276,7 +1480,7 @@ void (entity Goal, entity AP) DoGoalWork = { } }; -void (entity Goal, entity AP) DoGroupWork = { +void (entity Goal, entity AP, string cname) DoGroupWorkByClass = { local string st; local entity tg; local float allset; @@ -1289,14 +1493,14 @@ void (entity Goal, entity AP) DoGroupWork = { dprint(" has a .all_active specified, but no .last_impulse\n"); } else { allset = 1; - tg = find(world, classname, "info_tfgoal"); + tg = find(world, classname, cname); while (tg) { if (tg.group_no == Goal.all_active) { if (tg.goal_state != 1) { allset = 0; } } - tg = find(tg, classname, "info_tfgoal"); + tg = find(tg, classname, cname); } if (allset) { tg = Findgoal(Goal.last_impulse); @@ -1305,43 +1509,55 @@ void (entity Goal, entity AP) DoGroupWork = { } } } - } + } + if (Goal.activate_group_no != 0) { - tg = find(world, classname, "info_tfgoal"); + tg = find(world, classname, cname); while (tg) { if (tg.group_no == Goal.activate_group_no) { DoResults(tg, AP, 0); } - tg = find(tg, classname, "info_tfgoal"); + tg = find(tg, classname, cname); } } if (Goal.inactivate_group_no != 0) { - tg = find(world, classname, "info_tfgoal"); + tg = find(world, classname, cname); while (tg) { if (tg.group_no == Goal.inactivate_group_no) { InactivateGoal(tg); } - tg = find(tg, classname, "info_tfgoal"); + tg = find(tg, classname, cname); } } if (Goal.remove_group_no != 0) { - tg = find(world, classname, "info_tfgoal"); + tg = find(world, classname, cname); while (tg) { if (tg.group_no == Goal.remove_group_no) { RemoveGoal(tg); } - tg = find(tg, classname, "info_tfgoal"); + tg = find(tg, classname, cname); } } if (Goal.restore_group_no != 0) { - tg = find(world, classname, "info_tfgoal"); + tg = find(world, classname, cname); while (tg) { if (tg.group_no == Goal.restore_group_no) { RestoreGoal(tg); } - tg = find(tg, classname, "info_tfgoal"); + tg = find(tg, classname, cname); + } + } + if (Goal.default_group_no != 0) + { + tg = find(world, classname, cname); + while (tg) { + if (tg.group_no == Goal.default_group_no) { + DefaultGoalState(tg); + } + tg = find(tg, classname, cname); } } + if (Goal.remove_spawngroup != 0) { tg = find(world, classname, "info_player_teamspawn"); while (tg) { @@ -1443,6 +1659,16 @@ void (entity Item, entity AP) DoItemGroupWork = { } }; + + +void (entity Goal, entity AP) DoGroupWork = +{ + for (float i = 0; i < goal_class_names.length; i++) + { + DoGroupWorkByClass(Goal, AP, goal_class_names[i]); + } +}; + void (entity Goal, entity AP) DoTriggerWork = { local entity t; @@ -1477,39 +1703,48 @@ void (entity Goal, entity AP) DoTriggerWork = { }; void () DelayedResult = { + entity oself = self; if (self.enemy.goal_state == TFGS_DELAYED) DoResults(self.enemy, self.owner, self.weapon); - dremove(self); + + dremove(oself); }; -void (entity Goal, entity AP, float addb) DoResults = { +void (entity Goal, entity AP, float addb) DoResults = { local entity te; local float winners; local float gotone; - if (cb_prematch_time > time) { + if (cb_prematch && !(Goal.spawnflags & TFGI_CB_IGNORE)) { return; } - if (Goal.goal_state == 1) { + if (Goal.goal_state == TFGS_ACTIVE) { return; } - if ((Goal.delay_time > 0) && (Goal.goal_state != 4)) { - Goal.goal_state = 4; + if ((Goal.delay_time > 0) && (Goal.goal_state != TFGS_DELAYED)) { + Goal.goal_state = TFGS_DELAYED; te = spawn(); te.think = DelayedResult; te.nextthink = time + Goal.delay_time; te.owner = AP; te.enemy = Goal; te.weapon = addb; + Goal.bubble_count = time + Goal.delay_time; return; } + UpdateAbbreviations(Goal); - Goal.goal_state = 2; + Goal.goal_state = TFGS_INACTIVE; if ((Goal.classname == "info_tfgoal") && (Goal.mdl != string_null)) { setmodel(Goal, string_null); } + if (Goal.noise) { - sound(other, 3, Goal.noise, 1, 1); + if (other == world) { + FO_Sound(Goal, CHAN_ITEM, Goal.noise, 1, 1); + } else { + FO_Sound(other, CHAN_ITEM, Goal.noise, 1, 1); + } } winners = 0; if (Goal.increase_team1 != 0) { @@ -1530,6 +1765,7 @@ void (entity Goal, entity AP, float addb) DoResults = { } if (winners == 1) { TeamFortress_TeamShowScores(2); + LogEventGoal(AP); } if (CTF_Map == 1) { if (AP != world) { @@ -1541,32 +1777,32 @@ void (entity Goal, entity AP, float addb) DoResults = { winners = random(); if (winners < 0.1) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\nFlee!"); + "You got the enemy flag!\n\nFlee!"); } else { if (winners < 0.2) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\nHead for home!"); + "You got the enemy flag!\n\nHead for home!"); } else { if (winners < 0.6) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\nReturn to base!"); + "You got the enemy flag!\n\nReturn to base!"); } else { if (winners < 0.7) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\n"); + "You got the enemy flag!\n\n"); } else { if (winners < 0.8) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\n"); + "You got the enemy flag!\n\n"); } else { if (winners < 0.95) { CenterPrint2(te, - "\n\n\n", - "You got the enemy flag!\n\n"); + "\n\n\n", + "You got the enemy flag!\n\n"); } else { CenterPrint2(te, - "\n\n\n", - "Is that a flag in your pocket\nor a you just happy to see me?"); + "\n\n\n", + "Is that a flag in your pocket\nor are you just happy to see me?"); } } } @@ -1575,15 +1811,15 @@ void (entity Goal, entity AP, float addb) DoResults = { } } else { CenterPrint2(te, "\n\n\n", - "Your team ÇÏÔ the ÅÎÅÍÙ flag!!"); + Q"Your team \sgot\s the \senemy\s flag!!"); } } else { CenterPrint2(te, "\n\n\n", - "Your flag has been ÔÁËÅÎ!!"); + Q"Your flag has been \staken\s!!"); } te = find(te, classname, "player"); } - bprint(PRINT_HIGH, AP.netname, " ÇÏÔ the ÂÌÕÅ flag!\n"); + bprint(PRINT_HIGH, AP.netname, Q" \sgot\s the \sblue\s flag!\n"); AP.items = AP.items | IT_KEY1; } else { if (Goal.goal_no == 2) { @@ -1594,33 +1830,33 @@ void (entity Goal, entity AP, float addb) DoResults = { winners = random(); if (winners < 0.1) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\nFlee!"); + "You got the enemy flag!\n\nFlee!"); } else { if (winners < 0.2) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\nHead for home!"); + "You got the enemy flag!\n\nHead for home!"); } else { if (winners < 0.6) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\nReturn to base!"); + "You got the enemy flag!\n\nReturn to base!"); } else { if (winners < 0.7) { CenterPrint2(te, "\n\n\n", - "You got the enemy flag!\n\n"); + "You got the enemy flag!\n\n"); } else { if (winners < 0.8) { CenterPrint2(te, - "\n\n\n", - "You got the enemy flag!\n\nRed's dead baby, Red's dead..."); + "\n\n\n", + "You got the enemy flag!\n\nRed's dead baby, Red's dead..."); } else { if (winners < 0.95) { CenterPrint2(te, - "\n\n\n", - "You got the enemy flag!\n\n"); + "\n\n\n", + "You got the enemy flag!\n\n"); } else { CenterPrint2(te, - "\n\n\n", - "Is that a flag in your pocket\nor a you just happy to see me?"); + "\n\n\n", + "Is that a flag in your pocket\nor are you just happy to see me?"); } } } @@ -1629,15 +1865,15 @@ void (entity Goal, entity AP, float addb) DoResults = { } } else { CenterPrint2(te, "\n\n\n", - "Your team ÇÏÔ the ÅÎÅÍÙ flag!!"); + Q"Your team \sgot\s the \senemy\s flag!!"); } } else { CenterPrint2(te, "\n\n\n", - "Your flag has been ÔÁËÅÎ!!"); + Q"Your flag has been \staken\s!!"); } te = find(te, classname, "player"); } - bprint(PRINT_HIGH, AP.netname, " ÇÏÔ the ÒÅÄ flag!\n"); + bprint(PRINT_HIGH, AP.netname, Q" \sgot\s the \sred\s flag!\n"); AP.items = AP.items | IT_KEY2; } else { if (Goal.goal_no == 3) { @@ -1646,19 +1882,19 @@ void (entity Goal, entity AP, float addb) DoResults = { if (te.team_no == 2) { if (te == AP) { CenterPrint2(te, "\n\n\n", - "You ÃÁÐÔÕÒÅÄ the flag!!"); + Q"You \scaptured\s the flag!!"); } else { CenterPrint2(te, "\n\n\n", - "Your flag was ÃÁÐÔÕÒÅÄ!!"); + Q"Your flag was \scaptured\s!!"); } } else { CenterPrint2(te, "\n\n\n", - "Your team ÃÁÐÔÕÒÅÄ the flag!!"); + Q"Your team \scaptured\s the flag!!"); } te = find(te, classname, "player"); } bprint(PRINT_HIGH, AP.netname, - " ÃÁÐÔÕÒÅÄ the ÒÅÄ flag!\n"); + Q" \scaptured\s the \sred\s flag!\n"); AP.items = AP.items - (AP.items & IT_KEY2); } else { if (Goal.goal_no == 4) { @@ -1667,19 +1903,19 @@ void (entity Goal, entity AP, float addb) DoResults = { if (te.team_no == 1) { if (te == AP) { CenterPrint2(te, "\n\n\n", - "You ÃÁÐÔÕÒÅÄ the flag!!"); + Q"You \scaptured\s the flag!!"); } else { CenterPrint2(te, "\n\n\n", - "Your flag was ÃÁÐÔÕÒÅÄ!!"); + Q"Your flag was \scaptured\s!!"); } } else { CenterPrint2(te, "\n\n\n", - "Your team ÃÁÐÔÕÒÅÄ the flag!!"); + Q"Your team \scaptured\s the flag!!"); } te = find(te, classname, "player"); } bprint(PRINT_HIGH, AP.netname, - " ÃÁÐÔÕÒÅÄ the ÂÌÕÅ flag!\n"); + Q" \scaptured\s the \sblue\s flag!\n"); AP.items = AP.items - (AP.items & IT_KEY1); } } @@ -1688,12 +1924,16 @@ void (entity Goal, entity AP, float addb) DoResults = { } } gotone = 0; + if ((strtrim(Goal.broadcast_high) != "") && (CTF_Map == 0)) { + bprint(PRINT_HIGH, Goal.broadcast_high); + } te = find(world, classname, "player"); while (te != world) { if ((Goal.broadcast != string_null) && (CTF_Map == 0)) { CenterPrint2(te, "\n\n\n", Goal.broadcast); } - if ((Goal.netname_broadcast != string_null) && (CTF_Map == 0)) { + + if ((strtrim(Goal.netname_broadcast) != "") && (CTF_Map == 0)) { sprint(te, PRINT_HIGH, AP.netname, Goal.netname_broadcast); } if (AP == te) { @@ -1703,7 +1943,7 @@ void (entity Goal, entity AP, float addb) DoResults = { } else { if (AP.team_no == te.team_no) { if ((Goal.owners_team_broadcast != string_null) && - (te.team_no == Goal.owned_by)) { + (te.team_no == Goal.owned_by)) { CenterPrint2(te, "\n\n\n", Goal.owners_team_broadcast); } else { if (Goal.team_broadcast != string_null) { @@ -1711,33 +1951,33 @@ void (entity Goal, entity AP, float addb) DoResults = { } } if ((Goal.netname_owners_team_broadcast != string_null) && - (te.team_no == Goal.owned_by)) { + (te.team_no == Goal.owned_by)) { sprint(te, PRINT_HIGH, AP.netname, - Goal.netname_owners_team_broadcast); + Goal.netname_owners_team_broadcast); } else { if (Goal.netname_team_broadcast != string_null) { sprint(te, PRINT_HIGH, AP.netname, - Goal.netname_team_broadcast); + Goal.netname_team_broadcast); } } } else { if ((Goal.owners_team_broadcast != string_null) && - (te.team_no == Goal.owned_by)) { + (te.team_no == Goal.owned_by)) { CenterPrint2(te, "\n\n\n", Goal.owners_team_broadcast); } else { if (Goal.non_team_broadcast != string_null) { CenterPrint2(te, "\n\n\n", - Goal.non_team_broadcast); + Goal.non_team_broadcast); } } if ((Goal.netname_owners_team_broadcast != string_null) && - (te.team_no == Goal.owned_by)) { + (te.team_no == Goal.owned_by)) { sprint(te, PRINT_HIGH, AP.netname, - Goal.netname_owners_team_broadcast); + Goal.netname_owners_team_broadcast); } else { if (Goal.netname_non_team_broadcast != string_null) { sprint(te, PRINT_HIGH, AP.netname, - Goal.netname_non_team_broadcast); + Goal.netname_non_team_broadcast); } } } @@ -1756,7 +1996,7 @@ void (entity Goal, entity AP, float addb) DoResults = { te = find(te, classname, "player"); } if (Goal.classname != "item_tfgoal") { - Goal.goal_state = 1; + Goal.goal_state = TFGS_ACTIVE; } if (Goal.goal_result & 4) { TeamFortress_TeamShowScores(1); @@ -1790,10 +2030,12 @@ void () tfgoal_touch = { return; if (other.classname != "player") return; - if (cb_prematch_time > time) + if (cb_prematch) return; if (self.goal_state == TFGS_ACTIVE) return; + if(duelmode && duel_no_packs) + return; if (CTF_Map == 1) { if ((self.goal_no == 3) && (other.team_no == 1)) { @@ -1817,8 +2059,17 @@ void () info_tfgoal_use = { void () tfgoal_timer_tick = { if (self.goal_state != 3) { if (APMeetsCriteria(self, world)) + { + //bprint(PRINT_HIGH, "meets criteria, doing results\n"); DoResults(self, world, 1); + + // redo think here too, as quadmode breaks proper timers + // in DoResults due to return after cb_prematch test + self.think = tfgoal_timer_tick; + self.nextthink = time + self.search_time; + } else { + //bprint(PRINT_HIGH, "inactivating timer\n"); InactivateGoal(self); self.think = tfgoal_timer_tick; self.nextthink = time + self.search_time; @@ -1826,63 +2077,48 @@ void () tfgoal_timer_tick = { } }; -void () CF_FlagFollowPlayer = { - if ((self.owner.goal_state == 1) && (self.real_owner.items & (IT_KEY1 | IT_KEY2))) { - makevectors(self.real_owner.angles); - self.owner.origin = self.real_owner.origin - v_forward * 25 - v_right * 0 + v_up * 10; - self.owner.angles = self.real_owner.angles + '0 90 16'; - if (self.owner.mdl == "progs/tf_flag.mdl") { - self.owner.origin_z = self.owner.origin_z + 6; - } else { - self.owner.origin_z = self.owner.origin_z - 6; - } - setorigin(self.owner, self.owner.origin); - setmodel(self.owner, self.owner.mdl); - self.nextthink = time + 0.1; - } else { - dremove(self); - } -}; - void () item_tfgoal_touch = { - local entity te, flw; + // I can't see where exactly, but when a flag ends up in a door it's min + // and maxs are set to (0, 0, -16) thus reducing it to a single point 16 + // units below origin. This restores the correct size. + if (self.classname == "item_tfgoal" && other.classname == "worldspawn") { + setsize(self, self.goal_min, self.goal_max); + return; + } if (other.classname != "player") return; if (other.health <= 0) return; - if (cb_prematch_time > time) + if (cb_prematch) return; - if (other.is_feigning) + if (IsFeigned(other)) return; if (other == self.owner) return; + if (other == self.dropped_by && time < self.dropped_at + 0.75) + return; + local entity te; // check if a wall or something is in the way of the flag traceline(other.origin, self.origin, TRUE, world); if (trace_fraction < 1) return; - flw = spawn(); - flw.real_owner = other; - flw.owner = self; - flw.think = CF_FlagFollowPlayer; - flw.nextthink = time + 0.1; - if (CTF_Map == 1) { if (self.goal_no == 1) { if (self.origin != self.oldorigin) { if (other.team_no == 1) { bprint(2, other.netname); - bprint(2, " ÒÅÔÕÒÎÅÄ the ÂÌÕÅ flag!\n"); + bprint(2, Q" \scaptured\s the \sblue\s flag!\n"); te = find(world, classname, "player"); while (te != world) { if (te.team_no == 1) { CenterPrint2(te, "\n\n\n", - "Your flag was ÒÅÔÕÒÎÅÄ!!"); + Q"Your flag was \scaptured\s!!"); } else { CenterPrint2(te, "\n\n\n", - "The ÅÎÅÍÙ flag was ÒÅÔÕÒÎÅÄ!!"); + Q"The \senemy\s flag was \scaptured\s!!"); } te = find(te, classname, "player"); } @@ -1890,9 +2126,9 @@ void () item_tfgoal_touch = { self.solid = 1; self.touch = item_tfgoal_touch; self.origin = self.oldorigin; - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); setorigin(self, self.origin); - sound(self, 2, "items/itembk2.wav", 1, 1); + FO_Sound(self, CHAN_VOICE, "items/itembk2.wav", 1, 1); return; } } else { @@ -1905,15 +2141,15 @@ void () item_tfgoal_touch = { if (self.origin != self.oldorigin) { if (other.team_no == 2) { bprint(2, other.netname); - bprint(2, " ÒÅÔÕÒÎÅÄ the ÒÅÄ flag!\n"); + bprint(2, Q" \scaptured\s the \sred\s flag!\n"); te = find(world, classname, "player"); while (te != world) { if (te.team_no == 2) { CenterPrint(te, - "\n\n\n Your flag was ÒÅÔÕÒÎÅÄ!!"); + Q"\n\n\n Your flag was \scaptured\s!!"); } else { CenterPrint(te, - "\n\n\n The ÅÎÅÍÙ flag was ÒÅÔÕÒÎÅÄ!!"); + Q"\n\n\n The \senemy\s flag was \scaptured\s!!"); } te = find(te, classname, "player"); } @@ -1921,9 +2157,9 @@ void () item_tfgoal_touch = { self.solid = 1; self.touch = item_tfgoal_touch; self.origin = self.oldorigin; - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); setorigin(self, self.origin); - sound(self, 2, "items/itembk2.wav", 1, 1); + FO_Sound(self, CHAN_VOICE, "items/itembk2.wav", 1, 1); return; } } else { @@ -1945,29 +2181,80 @@ void () item_tfgoal_touch = { if (te) { AttemptToActivate(te, other, self); } + } else { + //Make sure we don't try to follow someone who fails the criteria + return; } } + + if (flag_follow) { + newmis = spawn(); + newmis.owner = other; + newmis.classname = "flagfollow"; + newmis.solid = SOLID_NOT; + newmis.mdl = self.mdl; + FO_SetModel(newmis, self.mdl); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + vector org; + org = other.origin + '-25 0 10'; + setorigin (newmis, org); + newmis.skin = self.skin; + newmis.movetype = MOVETYPE_FOLLOW; + newmis.aiment = other; + newmis.view_ofs = newmis.origin - other.origin; + newmis.nodrawtoclient = other; + } }; +void RemoveFlagFollow(entity player) +{ + if (flag_follow) + { + entity ff = find(world, classname, "flagfollow"); + while (ff) + { + if (ff.owner == player) + { + dremove(ff); + } + ff = find(ff, classname, "flagfollow"); + } + } +} + void (entity Item, entity AP, entity Goal) tfgoalitem_GiveToPlayer = { + Item.effects = 0; + Item.owner = AP; if (Item.mdl != string_null) { setmodel(Item, string_null); } Item.solid = 0; if (Item.goal_activation & TFGI_GLOW) { - AP.effects = AP.effects | EF_DIMLIGHT; + AP.effects = EF_DIMLIGHT; } + if (Item.goal_activation & TFGI_SLOW) TeamFortress_SetSpeed(AP); - if (Item.goal_activation & TFGI_ITEMGLOWS) - Item.effects = Item.effects - (Item.effects | EF_DIMLIGHT); + /* if (Item.goal_activation & TFGI_ITEMGLOWS) */ + /* Item.effects = EF_DIMLIGHT; */ - if (Item.items & IT_KEY1) + if (Item.items & IT_KEY1) { AP.items = AP.items | IT_KEY1; - if (Item.items & IT_KEY2) + if (Item.message == string_null) { + Status_Print(AP, "\n\n\n", "You got the enemy flag!"); + } + // else is taken care of in DoResults() + } + + if (Item.items & IT_KEY2) { AP.items = AP.items | IT_KEY2; + if (Item.message == string_null) { + Status_Print(AP, "\n\n\n", "You got the enemy flag!"); + } + // else is taken care of in DoResults() + } if (Goal != Item) { if (Goal.goal_result & TFGR_NO_ITEM_RESULTS) { @@ -1988,14 +2275,21 @@ void (entity Item, entity AP, entity Goal) tfgoalitem_GiveToPlayer = { Status_Refresh(AP); DoResults(Item, AP, 1); DoItemGroupWork(Item, AP); + AP.goalrunningtime = gametime; + AP.has_flag = TRUE; + UpdateClientFlagPickUp(AP, Item.skin); + LogEventPickupGoal(AP); }; void () ReturnItem = { local entity te; + if (self.enemy.touch == item_tfgoal_hidden_touch) + return; + self.enemy.goal_state = 2; if ((self.enemy.goal_activation & 8192) && - (self.enemy.classname == "item_tfgoal")) + (self.enemy.classname == "item_tfgoal")) self.enemy.solid = 2; else self.enemy.solid = 1; @@ -2004,20 +2298,24 @@ void () ReturnItem = { self.enemy.touch = item_tfgoal_touch; self.enemy.origin = self.enemy.oldorigin; - if (self.enemy.mdl != string_null) - setmodel(self.enemy, self.enemy.mdl); + if (self.enemy.model) + { + entity oldself; + oldself = self; + self = self.enemy; + TF_Item_ApplyGlow(); + self = oldself; + FO_SetModel(self.enemy, self.enemy.mdl); + } + setsize(self.enemy, VEC_HULL_MIN, VEC_HULL_MAX); setorigin(self.enemy, self.enemy.origin); tfgoalitem_checkgoalreturn(self.enemy); - if (self.enemy.origin == self.enemy.oldorigin) - return; - - sound(self.enemy, 2, "items/itembk2.wav", 1, 1); + FO_Sound(self.enemy, CHAN_VOICE, "items/itembk2.wav", 1, 1); if (self.weapon != 2) { - if ((self.enemy.noise3 != string_null) || - (self.enemy.noise4 != string_null)) { + if ((self.enemy.noise3) || (self.enemy.noise4)) { te = find(world, classname, "player"); while (te) { if (te.team_no == self.enemy.owned_by) @@ -2049,6 +2347,14 @@ void (entity Item, entity AP, float method) tfgoalitem_RemoveFromPlayer = { RemoveResults(Item, te); te = find(te, classname, "player"); } + //Flag + if ((Item.goal_activation & 1)) { + local float timecarried = gametime - AP.goalrunningtime; + AP.has_flag = FALSE; + RemoveFlagFollow(AP); + LogEventFumble(AP, timecarried); + } + if ((method == 0) || (method == 2)) { te = find(world, classname, "player"); while (te != world) { @@ -2058,7 +2364,7 @@ void (entity Item, entity AP, float method) tfgoalitem_RemoveFromPlayer = { if (Item.netname_team_drop != string_null) { sprint(te, PRINT_HIGH, AP.netname, - Item.netname_team_drop); + Item.netname_team_drop); } } else { if (Item.non_team_drop != string_null) @@ -2066,7 +2372,7 @@ void (entity Item, entity AP, float method) tfgoalitem_RemoveFromPlayer = { if (Item.netname_non_team_drop != string_null) { sprint(te, PRINT_HIGH, AP.netname, - Item.netname_non_team_drop); + Item.netname_non_team_drop); } } te = find(te, classname, "player"); @@ -2161,8 +2467,8 @@ void (entity Item, entity AP) tfgoalitem_RemoveEffectsFromPlayer = { AP.effects = AP.effects - (AP.effects & 64); AP.effects = AP.effects - (AP.effects & 128); } - if (Item.goal_activation & 512) - Item.effects = Item.effects | 8; + /* if (Item.goal_activation & 512) */ + /* Item.effects = Item.effects | 8; */ if (!spyoff) AP.is_unabletospy = 0; @@ -2182,13 +2488,15 @@ void () tfgoalitem_dropthink = { self.movetype = 6; if (self.pausetime != 0) { pos = pointcontents(self.origin); - if (pos == -4) { + if (pos == CONTENT_SLIME) { self.nextthink = time + (self.pausetime / 4); } else { - if (pos == -5) { + if (pos == CONTENT_LAVA) { self.nextthink = time + 5; + self.think = tfgoalitem_remove; + return; } else { - if ((pos == -2) || (pos == -6)) { + if ((pos == CONTENT_SOLID) || (pos == CONTENT_SKY)) { if (self.camdist < 3) { self.origin = self.camangle; setorigin(self, self.origin); @@ -2203,7 +2511,7 @@ void () tfgoalitem_dropthink = { self.solid = 1; } if (self.mdl != string_null) { - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); } setsize(self, self.goal_min, self.goal_max); self.camdist = self.camdist + 1; @@ -2218,17 +2526,19 @@ void () tfgoalitem_dropthink = { } } } - self.think = tfgoalitem_remove; + if(!noreturn) { + self.think = tfgoalitem_remove; + } } }; -void () tfgoalitem_droptouch = { - self.touch = item_tfgoal_touch; - self.nextthink = time + 4.25; - self.think = tfgoalitem_dropthink; -}; - void (entity Item, float PAlive, entity P) tfgoalitem_drop = { + entity oldself; + oldself = self; + self = Item; + TF_Item_ApplyGlow(); + self = oldself; + Item.origin = Item.owner.origin; setorigin(Item, Item.origin); Item.camangle = Item.owner.origin - '0 0 8'; @@ -2244,7 +2554,7 @@ void (entity Item, float PAlive, entity P) tfgoalitem_drop = { Item.solid = 1; } if (Item.mdl != string_null) { - setmodel(Item, Item.mdl); + FO_SetModel(Item, Item.mdl); } setsize(Item, Item.goal_min, Item.goal_max); if (PAlive == 1) { @@ -2256,13 +2566,20 @@ void (entity Item, float PAlive, entity P) tfgoalitem_drop = { Item.velocity = Item.velocity * 400; Item.velocity_z = 200; } - Item.touch = SUB_Null; - Item.nextthink = time + 0.75; - Item.think = tfgoalitem_droptouch; + Item.touch = item_tfgoal_touch; + Item.dropped_by = Item.owner; + Item.dropped_at = time; + Item.nextthink = time + 4.25; + Item.bubble_count = time + Item.pausetime + 0.75 + 4.25; //used by the return timer + Item.think = tfgoalitem_dropthink; } else { Item.touch = item_tfgoal_touch; Item.nextthink = time + 5; Item.think = tfgoalitem_dropthink; + Item.bubble_count = time + Item.pausetime + 5; //used by the return timer + } + if (Item.goal_activation & TFGI_RETURN_DROP) { + Item.think = tfgoalitem_remove; } Item.owner = world; }; @@ -2273,12 +2590,13 @@ void () tfgoalitem_remove = { if (self.goal_state == 1) { return; } - if (self.goal_activation & 32) { + if (self.goal_activation & 32 || self.goal_activation & TFGI_RETURN_DROP) { te = spawn(); te.enemy = self; te.weapon = 3; te.nextthink = time + 0.1; te.think = ReturnItem; + te.bubble_count = 0; //reset return time counter return; } dremove(self); @@ -2300,7 +2618,7 @@ void (entity Item) tfgoalitem_checkgoalreturn = { void (entity Goal, entity Player, entity Item) DisplayItemStatus = { if (Item.goal_state == 1 && Item.owner != world) { if ((Goal.team_str_carried != string_null) || - (Goal.non_team_str_carried != string_null)) { + (Goal.non_team_str_carried != string_null)) { if (Player.team_no == Item.owned_by) { sprint(Player, PRINT_HIGH, Goal.team_str_carried); } else { @@ -2317,7 +2635,7 @@ void (entity Goal, entity Player, entity Item) DisplayItemStatus = { } else { if (Item.origin != Item.oldorigin) { if ((Goal.team_str_moved != string_null) || - (Goal.non_team_str_moved != string_null)) { + (Goal.non_team_str_moved != string_null)) { if (Player.team_no == Item.owned_by) { sprint(Player, PRINT_HIGH, Goal.team_str_moved); } else { @@ -2327,7 +2645,7 @@ void (entity Goal, entity Player, entity Item) DisplayItemStatus = { } } else { if ((Goal.team_str_home != string_null) || - (Goal.non_team_str_home != string_null)) { + (Goal.non_team_str_home != string_null)) { if (Player.team_no == Item.owned_by) { sprint(Player, PRINT_HIGH, Goal.team_str_home); } else { @@ -2361,10 +2679,10 @@ void () item_flag_team2 = { CTF_Map = 1; UpdateAbbreviations(self); precache_sound("ogre/ogwake.wav"); - precache_sound("boss2/pop2.wav"); + precache_sound("boss1/out1.wav"); self.classname = "item_tfgoal"; self.netname = "Blue Flag"; - self.broadcast = " ÇÏÔ the enemy team's flag!\n"; + self.broadcast = Q" \sgot\s the enemy team's flag!\n"; self.deathtype = "You've got the enemy flag!\n"; self.noise = "ogre/ogwake.wav"; @@ -2376,7 +2694,7 @@ void () item_flag_team2 = { } self.skin = 0; - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); self.goal_no = 1; self.goal_activation = TFGI_GLOW | TFGI_DROP | TFGI_REMOVE | TFGI_RETURN_REMOVE | @@ -2401,9 +2719,9 @@ void () item_flag_team2 = { dp.items_allowed = 2; dp.goal_no = 3; dp.goal_effects = 3; - dp.broadcast = " ÃÁÐÔÕÒÅÄ the enemy flag!\n"; - dp.message = "You ÃÁÐÔÕÒÅÄ the enemy flag!\n"; - dp.noise = "boss2/pop2.wav"; + dp.broadcast = Q" \scaptured\s the enemy flag!\n"; + dp.message = Q"You \scaptured\s the enemy flag!\n"; + dp.noise = "boss1/out1.wav"; dp.goal_result = 2; dp.activate_goal_no = 5; dp.axhitme = 2; @@ -2438,10 +2756,10 @@ void () item_flag_team1 = { CTF_Map = 1; UpdateAbbreviations(self); precache_sound("ogre/ogwake.wav"); - precache_sound("boss2/pop2.wav"); + precache_sound("boss1/out1.wav"); self.classname = "item_tfgoal"; self.netname = "Red Flag"; - self.broadcast = " ÇÏÔ the enemy team's flag!\n"; + self.broadcast = Q" \sgot\s the enemy team's flag!\n"; self.deathtype = "You've got the enemy flag!\n"; self.noise = "ogre/ogwake.wav"; @@ -2452,7 +2770,7 @@ void () item_flag_team1 = { self.origin_z = self.origin_z + 6; } - setmodel(self, self.mdl); + FO_SetModel(self, self.mdl); self.skin = 1; self.goal_no = 2; self.goal_activation = 1 | 4 | 128 | 32 | 16 | 512; @@ -2476,9 +2794,9 @@ void () item_flag_team1 = { dp.items_allowed = 1; dp.goal_no = 4; dp.goal_effects = 3; - dp.broadcast = " ÃÁÐÔÕÒÅÄ the enemy flag!\n"; - dp.message = "You ÃÁÐÔÕÒÅÄ the enemy flag!\n"; - dp.noise = "boss2/pop2.wav"; + dp.broadcast = Q" \scaptured\s the enemy flag!\n"; + dp.message = Q"You \scaptured\s the enemy flag!\n"; + dp.noise = "boss1/out1.wav"; dp.goal_result = 2; dp.activate_goal_no = 6; dp.axhitme = 1; @@ -2557,11 +2875,10 @@ void (entity P) ForceRespawn = { self.origin = spot.origin + '0 0 1'; self.angles = spot.angles; self.fixangle = 1; - CheckClientSpawn(); if (self.playerclass != 0) spawn_tdeath(self.origin, self); if ((spot.classname == "info_player_teamspawn") && - (cb_prematch_time < time)) { + (!cb_prematch)) { if (spot.items != 0) { te = Finditem(spot.items); if (te) { @@ -2601,7 +2918,8 @@ void (entity P) ForceRespawn = { void () DropGoalItems = { local entity te, search; - + local float timecarried; + newmis = spawn(); makevectors(self.v_angle); v_forward = normalize(v_forward) * 64; @@ -2610,20 +2928,43 @@ void () DropGoalItems = { while (te) { if (te.owner == self) { if (old_dropflag) { - if (te.goal_activation & 4096) { + if (te.goal_activation & TFGI_ALLOWTHROW) { tfgoalitem_RemoveFromPlayer(te, self, 2); } } - else if (self.effects & EF_DIMLIGHT) { + else if(te.goal_activation & TFGI_RETURN_DROP) { + tfgoalitem_RemoveFromPlayer(te, self, 0); + } + //Always allow dropping 4096 + else if (self.effects & EF_DIMLIGHT || te.goal_activation & TFGI_ALLOWTHROW) { + timecarried = gametime - self.goalrunningtime; + self.has_flag = FALSE; + RemoveFlagFollow(self); + LogEventFumble(self, timecarried); te.angles = '0 0 0'; tfgoalitem_RemoveEffectsFromPlayer(te, self); tfgoalitem_drop(te, 1, self); - if (self.team_no == 1) - bprint(PRINT_HIGH, self.netname, " äòïððåä òåä§ó æìáç®\n"); - else if (self.team_no == 2) - bprint(PRINT_HIGH, self.netname, " äòïððåä âìõå§ó æìáç®\n"); - else - bprint(PRINT_HIGH, self.netname, " äòïððåä ôèå åîåíù§ó æìáç®\n"); + if (!te.netname_team_drop || !te.netname_non_team_drop) { + if (te.netname) { + bprint(PRINT_HIGH, self.netname, Q" \sdropped ", te.netname ,"!\s\n"); + } else if (te.owned_by > 0) { + if (te.owned_by == 1) + bprint(PRINT_HIGH, self.netname, Q" \sdropped blue's flag!\s\n"); + else if (te.owned_by == 2) + bprint(PRINT_HIGH, self.netname, Q" \sdropped red's flag!\s\n"); + else if (te.owned_by == 3) + bprint(PRINT_HIGH, self.netname, Q" \sdropped yellow's flag!\s\n"); + else if (te.owned_by == 4) + bprint(PRINT_HIGH, self.netname, Q" \sdropped green's flag!\s\n"); + } else { + if (self.team_no == 1) + bprint(PRINT_HIGH, self.netname, Q" \sdropped red's flag!\s\n"); + else if (self.team_no == 2) + bprint(PRINT_HIGH, self.netname, Q" \sdropped blue's flag!\s\n"); + else + bprint(PRINT_HIGH, self.netname, Q" \sdropped the enemy's flag!\s\n"); + } + } Status_Print(self, "\n\n\n", "You dropped the flag!"); search = find(world, classname, "player"); @@ -2634,6 +2975,14 @@ void () DropGoalItems = { } else { Status_Print(search, "\n\n\n", "The enemy dropped your flag!"); } + if (te.owned_by) { + if (te.netname_team_drop && te.netname_non_team_drop && search.team_no == te.owned_by) { + sprint(search, PRINT_HIGH, self.netname, te.netname_team_drop); + } + if (te.netname_team_drop && te.netname_non_team_drop && search.team_no != te.owned_by) { + sprint(search, PRINT_HIGH, self.netname, te.netname_non_team_drop); + } + } search = find(search, classname, "player"); } } @@ -2642,4 +2991,18 @@ void () DropGoalItems = { } dremove(newmis); TeamFortress_SetSpeed(self); + + local float autodisguise = FO_GetUserSetting(self, "autodisguise", "ad", "off"); + if (self.playerclass == PC_SPY) { + switch(autodisguise) { + case 1: + FO_Spy_DisguiseLastSpawned(self, FALSE); + break; + case 2: + FO_Spy_DisguiseLast(self, FALSE); + break; + } + } + + UpdateClientFlagDrop(self); }; diff --git a/tforttm.qc b/ssqc/tforttm.qc similarity index 74% rename from tforttm.qc rename to ssqc/tforttm.qc index 9175ddeeb..9d886e151 100644 --- a/tforttm.qc +++ b/ssqc/tforttm.qc @@ -9,7 +9,7 @@ float (float tno) TeamFortress_TeamGetLives; float (float tno) TeamFortress_TeamGetMaxPlayers; string(float tno) TeamFortress_TeamGetColorString; float (float tno) TeamFortress_TeamGetIllegalClasses; -string(entity p) TeamFortress_GetSkin; +string(float pc) TeamFortress_GetSkin; float () TeamFortress_TeamPutPlayerInTeam = { @@ -39,18 +39,63 @@ float () TeamFortress_TeamPutPlayerInTeam = return (TeamFortress_TeamSet(likely_team)); }; -float (float tno) TeamFortress_TeamGetColor = { - if (tno == 1) - return (DARKBLUE); - if (tno == 2) - return (RED); - if (tno == 3) - return (YELLOW); - if (tno == 4) - return (DARKGREEN); - return (0); +string (float tno) TeamFortress_TeamGetColor = { + local string color = NOTEAMCOLOR; + + switch (tno) { + case 1: + color = BLUETEAMCOLOR; + break; + case 2: + color = REDTEAMCOLOR; + break; + case 3: + color = YELLOWTEAMCOLOR; + break; + case 4: + color = GREENTEAMCOLOR; + break; + } + + return color; }; +string TeamFortress_TeamGetColorFor(entity pov, float tno) { + local string color = NOTEAMCOLOR; + + if (pov.team_no == tno && cvar("teamcolor")) { + color = cvar_string("teamcolor"); + } else if (pov.team_no != tno && cvar("enemycolor")) { + color = cvar_string("enemycolor"); + } else { + switch (tno) { + case 1: + color = infokey(pov, "team1color"); + if (color == "" || color == "off") + color = BLUETEAMCOLOR; + break; + case 2: + color = infokey(pov, "team2color"); + if (color == "" || color == "off") + color = REDTEAMCOLOR; + break; + case 3: + color = infokey(pov, "team3color"); + if (color == "" || color == "off") + color = YELLOWTEAMCOLOR; + break; + case 4: + color = infokey(pov, "team4color"); + if (color == "" || color == "off") + color = GREENTEAMCOLOR; + break; + } + } + + return color; +} + + void (float tno) TeamFortress_TeamSetColor = { if (tno == 1) { team1col = DARKBLUE; @@ -154,9 +199,8 @@ float (float tno) CF_TeamIsValid = { return 1; } -float (float tno) TeamFortress_TeamSet = { +float (entity pe, float tno, float skipclasscheck) TeamFortress_TeamSet_Options = { local string st; - local float tc; local string team; if ((intermission_running != 0) || (intermission_exittime > time)) @@ -175,151 +219,151 @@ float (float tno) TeamFortress_TeamSet = { else team = "green team"; - if (TeamFortress_TeamGetColor(tno) == 0) { - TeamFortress_TeamSetColor(tno); - if (TeamFortress_TeamGetColor(tno) == 0) { - sprint(self, PRINT_HIGH, - "You cannot start a new team with your color, since another "); - sprint(self, PRINT_HIGH, - "already using that color. Change your pants color, then try again.\n"); - return (0); - } - bprint(PRINT_HIGH, self.netname, " has started the "); - bprint(PRINT_HIGH, team, "\n"); - self.immune_to_check = time + 10; - if ((toggleflags & TFLAG_TEAMFRAGS) || - (toggleflags & TFLAG_FULLTEAMSCORE)) { - self.frags = TeamFortress_TeamGetScore(tno); - } - stuffcmd(self, "color "); - tc = TeamFortress_TeamGetColor(tno) - 1; - st = ftos(tc); - stuffcmd(self, st); - stuffcmd(self, "\n"); - self.team_no = tno; - self.lives = TeamFortress_TeamGetLives(tno); - SetTeamName(self); - if (self.playerclass == 0) { - if (TeamFortress_TeamIsCivilian(self.team_no)) { - TeamFortress_ChangeClass(11); - } - } - return (1); + if (tno > 0 && tno == pe.team_no) { + sprint(pe, PRINT_HIGH, "You are already on the ", team, "!\n"); + return 0; } - if (!self.deadflag) { - if (self.health == self.max_health && (self.spawn_time + 10) > time && CloseToSpawnPoint() && self.suicide_time <= time) { - self.has_changedteam = 1; - ClientKill(1, 1); - self.has_changedteam = 0; + + if (!pe.deadflag) { + if (pe.health == pe.max_health && (pe.spawn_time + 10) > time && CloseToSpawnPoint() && pe.suicide_time <= time) { + pe.has_changedteam = 1; + pe.clientkillforce = 1; + pe.clientkillfree = 1; + ClientKill(); + pe.has_changedteam = 0; } else - ClientKill(1, 0); + { + pe.clientkillforce = 1; + pe.clientkillfree = 0; + ClientKill(); + } } // remove engineer buildings - if (self.playerclass == PC_ENGINEER) - Engineer_RemoveBuildings(self); + if (pe.playerclass == PC_ENGINEER) + Engineer_RemoveBuildings(pe); // swap colors for spy last disguise - if (self.playerclass == PC_SPY && self.undercover_team == tno) - self.last_team = self.team_no; + if (pe.playerclass == PC_SPY && pe.undercover_team == tno) + pe.last_team = pe.team_no; // remove existing gas grenades RemoveGasTimers(); - bprint(PRINT_HIGH, self.netname, " has joined the "); + bprint(PRINT_HIGH, pe.netname, " has joined the "); bprint(PRINT_HIGH, team, "\n"); - stuffcmd(self, "color "); - tc = TeamFortress_TeamGetColor(tno) - 1; - st = ftos(tc); - stuffcmd(self, st); - stuffcmd(self, "\n"); - self.team_no = tno; - self.immune_to_check = time + 10; - self.lives = TeamFortress_TeamGetLives(tno); + stuffcmd(pe, "color "); + st = TeamFortress_TeamGetColor(tno); + stuffcmd(pe, st); + stuffcmd(pe, "\n"); + + if(!skipclasscheck) { + if(pe.playerclass == PC_CIVILIAN) { + if (TeamFortress_TeamIsCivilian(tno)) { + pe.team_no = tno; + TeamFortress_ChangeClass(11); + } else { + TeamFortress_ChangeClass(0); + Menu_Class(0); + } + } else { + if (pe.playerclass == 0) { + } else { + //Make sure the current class is still legal on the new team + override_mapclasses = CF_GetSetting("omc", "override_mapclasses", "off"); + if (CF_GetClassRestriction(tno, self.playerclass) == -1 || CF_ClassIsRestricted(tno, self.playerclass)) { + TeamFortress_ChangeClass(0); + Menu_Class(0); + } + + } + } + } + pe.team_no = tno; + pe.immune_to_check = time + 10; + pe.lives = TeamFortress_TeamGetLives(tno); if ((toggleflags & TFLAG_TEAMFRAGS) || (toggleflags & TFLAG_FULLTEAMSCORE)) { - self.frags = TeamFortress_TeamGetScore(tno); + pe.frags = TeamFortress_TeamGetScore(tno); } - TeamFortress_TeamShowMemberClasses(self); - SetTeamName(self); - if (self.playerclass == 0) { - if (TeamFortress_TeamIsCivilian(self.team_no)) { - TeamFortress_ChangeClass(11); + TeamFortress_TeamShowMemberClasses(pe); + SetTeamName(pe); + + if(!skipclasscheck) { + if (pe.playerclass == 0) { + if (TeamFortress_TeamIsCivilian(tno)) { + pe.team_no = tno; + TeamFortress_ChangeClass(11); + } + } else { + // force to change class if class is forbidden on the new team + if (!IsLegalClass(pe.playerclass) && !override_mapclasses) { + TeamFortress_ChangeClass(0); + Menu_Class(0); + } } } + + forceinfokey(pe, "team_no", ftos(tno)); + UpdateReadyStatus(); return (1); }; -void () TeamFortress_CheckTeamCheats = { +float (float tno) TeamFortress_TeamSet = { + if (quadmode && tno >= 1 && tno <= 4 && !self.fo_login && fo_login_required) { + sprint(self, PRINT_HIGH, "You need to sign in first. Get your login token at www.fortressone.org\n"); + return 0; + } + + local float tftso = TeamFortress_TeamSet_Options(self, tno, FALSE); + + SetDimensions(TRUE); + UpdateAllClientsTeamScores(); + UpdateReadyStatus(); + return (tftso); +} + +void (entity player) TeamFortress_CheckTeamCheats = { local string st; local string sk; - local float tc; - if (self.immune_to_check > time) + if (player.immune_to_check > time) return; - if (self.deadflag) + if (player.deadflag) return; - if (self.netname == string_null) { - KickCheater(self); + if (player.netname == string_null) { + KickCheater(player); } else { - if (((self.playerclass != 0) && (self.team_no == 0)) && + if (((player.playerclass != 0) && (player.team_no == 0)) && (teamplay > 0)) { - KickCheater(self); - } - } - if ((self.team_no > 0) && (teamplay > 0)) { - st = infokey(self, "bottomcolor"); - tc = stof(st); - if ((self.playerclass == 8) && (self.undercover_team != 0)) { - if ((TeamFortress_TeamGetColor(self.undercover_team) - 1) != - tc) { - stuffcmd(self, "color "); - tc = TeamFortress_TeamGetColor(self.undercover_team) - 1; - st = ftos(tc); - stuffcmd(self, st); - stuffcmd(self, "\n"); - bprint2(PRINT_MEDIUM, self.netname, - " has been kicked for changing color\n"); - sprint(self, PRINT_HIGH, - "You have been kicked for changing your pants color\n"); - KickCheater(self); - return; - } - } else { - if (tc != (TeamFortress_TeamGetColor(self.team_no) - 1)) { - stuffcmd(self, "color "); - tc = TeamFortress_TeamGetColor(self.team_no) - 1; - st = ftos(tc); - stuffcmd(self, st); - stuffcmd(self, "\n"); - bprint2(PRINT_MEDIUM, self.netname, - " has been kicked for changing color\n"); - sprint(self, PRINT_HIGH, - "You have been kicked for changing your pants color\n"); - KickCheater(self); - return; - } + KickCheater(player); } - if (self.playerclass != 0) { - st = infokey(self, "skin"); - tc = 0; - sk = TeamFortress_GetSkin(self); + } + if ((player.team_no > 0) && (teamplay > 0)) { + st = infokey(player, "bottomcolor"); + if (st != TeamFortress_TeamGetColor(player.team_no)) { + bprint(PRINT_HIGH, sprintf("%s's bottomcolor is wrong\n", player.netname)); + bprint(PRINT_HIGH, sprintf("setinfo: %s\n", st)); + bprint(PRINT_HIGH, sprintf("TF_TGC: %s\n", TeamFortress_TeamGetColor(player.team_no))); + TeamFortress_SetSkin(player); + } + if (player.playerclass != 0) { + st = infokey(player, "skin"); + sk = TeamFortress_GetSkin(player.playerclass); if (st != sk) { - TeamFortress_SetSkin(self); - bprint2(PRINT_MEDIUM, self.netname, + TeamFortress_SetSkin(player); + bprint(PRINT_MEDIUM, player.netname, " has been kicked for changing skin\n"); - sprint(self, PRINT_HIGH, + sprint(player, PRINT_HIGH, "You have been kicked for changing your skin\n"); - KickCheater(self); + KickCheater(player); } - if (tc == 8) - self.playerclass = 8; } - st = GetTeamName(self.team_no); - if (st != infokey(self, "team")) { - SetTeamName(self); - bprint2(PRINT_MEDIUM, self.netname, + st = GetTeamName(player.team_no); + if (st != infokey(player, "team")) { + SetTeamName(player); + bprint(PRINT_MEDIUM, player.netname, " has been kicked for changing team\n"); - sprint(self, PRINT_HIGH, + sprint(player, PRINT_HIGH, "You have been kicked for changing your team\n"); - KickCheater(self); + KickCheater(player); return; } } @@ -343,7 +387,11 @@ void (float tno, float scoretoadd) TeamFortress_TeamIncreaseScore = { if (tno == 4) team4score = team4score + scoretoadd; - if ((toggleflags & TFLAG_TEAMFRAGS) || (toggleflags & 2048)) { + if ((tno == 1) || (tno == 2) || (tno == 3) || (tno == 4)) { + UpdateAllClientsTeamScores(); + } + + if ((toggleflags & TFLAG_TEAMFRAGS) || (toggleflags & TFLAG_FULLTEAMSCORE)) { e = find(world, classname, "player"); while (e) { if (e.team_no == tno) @@ -411,24 +459,32 @@ float (float tno) TeamFortress_TeamGetLives = { }; float (float tno) TeamFortress_TeamGetNoPlayers = { - local float size_team; local entity search; - size_team = 0; + local string cn = tno == 0 ? "observer" : "player"; + local float size_team = 0; - search = find(world, classname, "player"); + search = find(world, classname, cn); while (search != world) { if (search.team_no == tno) size_team = size_team + 1; - search = find(search, classname, "player"); + search = find(search, classname, cn); } - if (tno == 0) { - search = find(world, classname, "observer"); - while ((search != world)) { - if (search.team_no == tno) - size_team = size_team + 1; - search = find(search, classname, "observer"); - } + + return (size_team); +}; + +float (float tno) TeamFortress_TeamGetNoPlayersExcludingAllTime = { + local entity search; + local string cn = tno == 0 ? "observer" : "player"; + local float size_team = 0; + + search = find(world, classname, cn); + while (search != world) { + if (search.team_no == tno && search.all_time == ALL_TIME_COLOUR) + size_team = size_team + 1; + search = find(search, classname, cn); } + return (size_team); }; @@ -477,10 +533,13 @@ float () TeamFortress_GetNoActivePlayers = { search = find(world, classname, "player"); while (search != world) { - if (search.netname != string_null) { - if (search.team_no && search.playerclass) - nump = nump + 1; + if (search.netname != string_null + && search.team_no + && search.playerclass + && !search.has_disconnected) { + nump = nump + 1; } + search = find(search, classname, "player"); } return (nump); @@ -551,7 +610,7 @@ void (float all) TeamFortress_TeamShowScores = { i = 1; if (all == 2) { while (i <= number_of_teams) { - if (TeamFortress_TeamGetColor(i) > 0) { + if (TeamFortress_TeamGetColor(i) != NOTEAMCOLOR) { j = TeamFortress_TeamGetScore(i); st = TeamFortress_TeamGetColorString(i); bprint(PRINT_HIGH, st, ": "); @@ -564,7 +623,7 @@ void (float all) TeamFortress_TeamShowScores = { return; } while (i <= number_of_teams) { - if (TeamFortress_TeamGetColor(i) > 0) { + if (TeamFortress_TeamGetColor(i) != NOTEAMCOLOR) { if (all) { bprint(PRINT_HIGH, "Team "); } else { @@ -609,38 +668,25 @@ void (float all) TeamFortress_TeamShowScores = { } }; -string(float tno) TeamFortress_TeamGetColorString = -{ - local float col; - - col = TeamFortress_TeamGetColor(tno); - if (col == WHITE) - return ("White"); - if (col == BROWN) - return ("Brown"); - if (col == BLUE) - return ("Blue"); - if (col == GREEN) - return ("Green"); - if (col == RED) - return ("Red"); - if (col == TAN) - return ("Tan"); - if (col == PINK) - return ("Pink"); - if (col == ORANGE) - return ("Orange"); - if (col == PURPLE) - return ("Purple"); - if (col == DARKPURPLE) - return ("DarkPurple"); - if (col == GREY) - return ("Grey"); - if (col == DARKGREEN) - return ("DarkGreen"); - if (col == YELLOW) - return ("Yellow"); - return ("Blue"); +string(float tno) TeamFortress_TeamGetColorString = { + local string colour = "White"; + + switch(tno) { + case 1: + colour = "Blue"; + break; + case 2: + colour = "Red"; + break; + case 3: + colour = "Yellow"; + break; + case 4: + colour = "Green"; + break; + } + + return colour; }; void (entity Player) TeamFortress_TeamShowMemberClasses = { @@ -923,9 +969,11 @@ float (float tno) TeamFortress_TeamIsCivilian = { float (float pf_team_no, float pf_class) CF_GetClassRestriction = { local float f_max = 0, f_players = TeamFortress_TeamGetNoPlayers(pf_team_no); - local string s_string; + //local string s_string; + Team_Role * role = GetTeamRole(pf_team_no); // set maximum number based on settings +/* if (pf_class == PC_SCOUT) { s_string = infokey(world, "cr_scout"); f_max = stof(s_string); @@ -959,7 +1007,10 @@ float (float pf_team_no, float pf_class) CF_GetClassRestriction = { s_string = infokey(world, "cr_randompc"); f_max = stof(s_string); } - +*/ + if (pf_class == PC_SPY && spy_off) return -1; + + f_max = rint(role.class_limits[pf_class]); if (f_max == 0 || f_players < f_max) return f_players; diff --git a/ssqc/time.qc b/ssqc/time.qc new file mode 100644 index 000000000..7868a2d9f --- /dev/null +++ b/ssqc/time.qc @@ -0,0 +1,46 @@ +float remote_client_time() { + float adj_ping = max(self.client_ping - SERVER_FRAME_MS, 0); + float offset = min(adj_ping, fo_config.max_rewind_ms); + + float target = self.client_time - offset / 1000.0; + target = max(target, self.last_remote_client_time); // prevent jitter + self.last_remote_client_time = target; + + return target; +} + +// Note: Delta has jitter of up to ~frame +inline float client_to_global_time(float ctime) { + return time + (ctime - self.client_time); +} + +inline float global_to_client_time(float gtime) { + return self.client_time + (gtime - time); +} + +// Ping corrected `time`. +float remote_time() { + return client_to_global_time(remote_client_time()); +} + +float bounded_remote_time(float dt) { + return max(time - dt, remote_time()); +} + +void FO_CheckClientThink() { + if (self.client_nextthink > 0 && self.client_time >= self.client_nextthink) { + float held_client_time = self.client_time; + + self.client_time = self.client_nextthink; + self.client_nextthink = 0; + self.client_think(); + + self.client_time = held_client_time; + } +} + +void FO_UpdateClientTime() { + self.client_time += frametime; + + self.client_ping = infokeyf(self, INFOKEY_P_PING); +} diff --git a/triggers.qc b/ssqc/triggers.qc similarity index 71% rename from triggers.qc rename to ssqc/triggers.qc index 3ea40d61d..f056fc867 100644 --- a/triggers.qc +++ b/ssqc/triggers.qc @@ -29,7 +29,7 @@ void () multi_trigger = { WriteByte(MSG_ALL, SVC_FOUNDSECRET); } if (self.noise) { - sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); } self.takedamage = DAMAGE_NO; activator = self.enemy; @@ -100,6 +100,18 @@ void () trigger_multiple = { } self.use = multi_use; + // q3 fields + switch(self.allowteams) + { + case "blue": + self.team_no = 1; + break; + case "red": + self.team_no = 2; + break; + default: + } + InitTrigger(); if (self.health) { @@ -112,6 +124,8 @@ void () trigger_multiple = { self.solid = SOLID_BBOX; setorigin(self, self.origin); } else if (!(self.spawnflags & SPAWNFLAG_NOTOUCH)) { + if (ForwardDoorsPossible()) + self.flags |= FL_FINDABLE_NONSOLID; self.touch = multi_touch; } }; @@ -217,7 +231,7 @@ void () play_teleport = { else tmpstr = "misc/r_tele5.wav"; - sound(self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); + FO_Sound(self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); remove(self); }; @@ -279,8 +293,9 @@ void (vector org, entity death_owner) spawn_tdeath = { }; void () teleport_touch = { - local entity t, te; + local entity t = world, te; local vector org; + local float targetcount; if (self.targetname) { if (self.nextthink < time) { @@ -299,6 +314,11 @@ void () teleport_touch = { } return; } + + + if (self.goal_state == TFGS_REMOVED) + return; + // only teleport living creatures if ((other.health <= 0) || (other.solid != 3)) return; @@ -308,10 +328,17 @@ void () teleport_touch = { // put a tfog where the player was spawn_tfog(other.origin); - t = find(world, targetname, self.target); + if(self.targetsequence && self.targetsequence != "") { + targetcount = tokenizebyseparator(self.targetsequence, ","); + self.lasttarget = (self.lasttarget + 1) % targetcount; + t = find(world, targetname, argv(self.lasttarget)); + } + if(!t) { + t = find(world, targetname, self.target); + } if (!t) objerror("couldn't find target"); - + // spawn a tfog flash in front of the destination makevectors(t.mangle); org = t.origin + 32 * v_forward; @@ -377,6 +404,7 @@ void () trigger_teleport = { o = (self.mins + self.maxs) * 0.5; ambientsound(o, "ambience/hum1.wav", 0.5, ATTN_STATIC); } + self.lasttarget = -1; }; void () trigger_skill_touch = { @@ -425,7 +453,7 @@ void () trigger_onlyregistered_touch = { remove(self); } else if (self.message != "") { CenterPrint(other, self.message); - sound(other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + FO_Sound(other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); } }; @@ -458,7 +486,7 @@ void () hurt_touch = { } self.solid = SOLID_NOT; deathmsg = DMSG_TRIGGER; - TF_T_Damage(other, self, self, self.dmg, TF_TD_IGNOREARMOUR, + TF_T_Damage(other, self, self, self.dmg, TF_TD_IGNOREARMOR, TF_TD_OTHER); self.think = hurt_on; self.nextthink = time + 1; @@ -476,7 +504,37 @@ void () trigger_hurt = { self.dmg = 5; }; -#define PUSH_ONCE 1 +void () trigger_jumper_touch = { + local entity te; + + if(!Activated(self, other)) { + if((self.else_goal != 0)) { + te = Findgoal(self.else_goal); + if(te) { + DoResults(te, other, (self.goal_result & TFGR_ADD_BONUSES)); + } + } + return; + } + if(((other.classname == "grenade") || (other.classname != "player"))) { + return; + } else { + if(((other.health > 0) && !self.armorclass)) { + other.velocity_z = (self.speed * 7); + if((other.classname == "player")) { + if((other.fly_sound < time)) { + other.fly_sound = (time + 1.5); + if(!(self.spawnflags & 2)) { + FO_Sound(other, CHAN_AUTO, self.noise, 1, ATTN_NORM); + } + } + } + } + } + if((self.spawnflags & 1)) { + dremove (self); + } +}; void () trigger_push_touch = { local entity te; @@ -489,22 +547,95 @@ void () trigger_push_touch = { } return; } - if (other.classname == "grenade") - other.velocity = self.speed * self.movedir * 10; - else if (other.health > 0) { - other.velocity = self.speed * self.movedir * 10; - if (other.classname == "player") { - if (other.fly_sound < time) { - other.fly_sound = time + 1.5; - sound(other, CHAN_AUTO, "ambience/windfly.wav", 1, - ATTN_NORM); + + float push = FALSE; + if (other.classname == "item_tfgoal" || other.classname == "info_tfgoal") + { + if (self.spawnflags & PUSH_INCLUDETFITEM) + { + push = TRUE; + } + } + else if (self.spawnflags & PUSH_EXCLUDEOTHER) + { + return; + } + else if ((self.spawnflags & PUSH_EXCLUDEGRENADE) && other.classname == "grenade") + { + return; + } + else + { + if (other.classname == "grenade" || other.health > 0) + { + push = TRUE; + } + } + + if (push) + { + if (self.target) // this could test for movedir but i want to be explicit, it's a bounce pad + { + if (!(self.spawnflags & PUSH_NONOISE)) + FO_Sound(other, CHAN_AUTO, self.noise, 1, ATTN_NORM); + other.velocity = self.movedir; + } + else + { + other.velocity = self.speed * self.movedir * 10; + if (other.classname == "player") { + if (other.fly_sound < time) { + other.fly_sound = time + 1.5; + if (!(self.spawnflags & PUSH_NONOISE)) + FO_Sound(other, CHAN_AUTO, self.noise, 1, ATTN_NORM); + } } } } + if (self.spawnflags & PUSH_ONCE) dremove(self); }; +void trigger_push_setup() +{ + /* + this is based off of ioq3 gpl code + https://github.com/ioquake/ioq3 + void AimAtTarget( gentity_t *self ) + */ + float height, gravity, airtime, dist, forward; + vector org, targorg, origin2; + + org = (self.absmin + self.absmax) * .5; + + entity ent = find(world, targetname, self.target); + if (!ent) + { + return; + } + + targorg = ent.origin; + height = targorg_z - org_z; + gravity = cvar("sv_gravity"); + airtime = sqrt(height / (.5 * gravity)); + + if (!airtime) + { + return; + } + + origin2 = ent.origin - org; + origin2_z = 0; + dist = vlen(origin2); + origin2 = normalize(origin2); + + forward = dist / airtime; + origin2 = origin2 * forward; + origin2_z = airtime * gravity; + self.movedir = origin2; +} + void () trigger_push = { if (CheckExistence() == FALSE) { dremove(self); @@ -512,11 +643,46 @@ void () trigger_push = { } InitTrigger(); - precache_sound("ambience/windfly.wav"); - self.touch = trigger_push_touch; + self.touch = trigger_push_touch; + if (self.target) + { + // the target ent might not be processed yet, so we do setup after map starts + self.think = trigger_push_setup; + self.nextthink = time + 1; + } + else + { + if (!self.speed) { + self.speed = 1000; + } + if(self.spawnflags & PUSH_MEGAJUMPER) { + self.touch = trigger_jumper_touch; + } + } + + if (!(self.spawnflags & PUSH_NONOISE)) + { + if (!self.noise) + { + if (self.target) + { + // bound pads + self.noise = "world/bouncepad.wav"; + } + else + { + self.noise = "ambience/windfly.wav"; + } + } + + precache_sound(self.noise); + } - if (!self.speed) - self.speed = 1000; +}; + +void () trigger_jumppad = { + self.classname = "trigger_push"; + trigger_push(); }; void () trigger_monsterjump_touch = { @@ -551,3 +717,11 @@ void () trigger_monsterjump = { InitTrigger(); self.touch = trigger_monsterjump_touch; }; + +void () trigger_jumper = { + if(!self.noise) { + self.noise = "world/bounce.wav"; //"misc/boing.wav"; //"world/bouncepad.wav"; + } + self.spawnflags = self.spawnflags | PUSH_MEGAJUMPER; + trigger_push(); +} diff --git a/ssqc/tsoldier.qc b/ssqc/tsoldier.qc new file mode 100644 index 000000000..05d46c609 --- /dev/null +++ b/ssqc/tsoldier.qc @@ -0,0 +1,291 @@ +//======================================================== +// Functions for the SOLDIER class and associated weaponry +//======================================================== + +void () NailGrenadeExplode; +void () ShockGrenadeExplode; +void () BurstGrenadeExplode; +void () NailGrenadeNailEm; +void () NailGrenadeLaunchNail; +void () NailGrenLaser; +void () NailGrenBurst; + +void () NailGrenadeTouch = { + if (other == self.owner) + return; + + // If the Nail Grenade hits a player, it just bounces off + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () ShockGrenadeTouch = { + if (other == self.owner) + return; + + // If the Shock Grenade hits a player, it just bounces off + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () BurstGrenadeTouch = { + if (other == self.owner) + return; + + // If the Burst Grenade hits a player, it just bounces off + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () NailGrenadeExplode = { + local entity te; + + self.owner.no_active_nail_grens = self.owner.no_active_nail_grens + 1; + + if (self.owner.no_active_nail_grens >= max_active_gren2_soldier) { + te = find(world, classname, "grenade"); + + while (te) { + if ((te.owner == self.owner || te.real_owner == self.owner) && (te.no_active_nail_grens == 1)) { + if (!te.owner) + te.owner = te.real_owner; + + te.weapon = 9; + te.think = GrenadeExplode; + te.nextthink = time + 0.1; + } + + te = find(te, classname, "grenade"); + } + } + + self.no_active_nail_grens = self.owner.no_active_nail_grens; + self.movetype = MOVETYPE_FLY; + setorigin(self, self.origin + '0 0 32'); + + if(solid_nailgren) + setsize(self, '-1 -1 -1', '1 1 1'); + + self.avelocity = '0 500 0'; + self.nextthink = time + 0.7; + self.think = NailGrenadeNailEm; +}; + +void () ShockGrenadeExplode = { + local entity te; + + self.owner.no_active_nail_grens = self.owner.no_active_nail_grens + 1; + if (self.owner.no_active_nail_grens >= max_active_gren2_soldier) { + + te = find(world, classname, "grenade"); + while (te) { + if ((te.owner == self.owner || te.real_owner == self.owner) && (te.no_active_nail_grens == 1)) { + if (!te.owner) + te.owner = te.real_owner; + te.weapon = 9; + te.think = GrenadeExplode; + te.nextthink = time + 0.1; + } + te = find(te, classname, "grenade"); + } + } + self.no_active_nail_grens = self.owner.no_active_nail_grens; + self.movetype = MOVETYPE_FLY; + self.velocity = '0 0 0'; + self.avelocity = '0 500 0'; + setorigin(self, self.origin + '0 0 32'); + if(solid_nailgren) { + setsize(self, '-1 -1 -1', '1 1 1'); + } + self.nextthink = time + 0.7; + self.playerclass = 0; + self.think = NailGrenLaser; + self.SendFlags |= FOPP_POS | FOPP_MOVETYPE | FOPP_ANGLES; +}; + +void () BurstGrenadeExplode = { + local entity te; + + self.owner.no_active_nail_grens = self.owner.no_active_nail_grens + 1; + if (self.owner.no_active_nail_grens >= max_active_gren2_soldier) { + + te = find(world, classname, "grenade"); + while (te) { + if ((te.owner == self.owner || te.real_owner == self.owner) && (te.no_active_nail_grens == 1)) { + if (!te.owner) + te.owner = te.real_owner; + te.weapon = 9; + te.think = GrenadeExplode; + te.nextthink = time + 0.1; + } + te = find(te, classname, "grenade"); + } + } + self.no_active_nail_grens = self.owner.no_active_nail_grens; + self.movetype = MOVETYPE_FLY; + setorigin(self, self.origin + '0 0 32'); + if(solid_nailgren) { + setsize(self, '-1 -1 -1', '1 1 1'); + } + self.avelocity = '0 500 0'; + self.nextthink = time + 0.7; + self.playerclass = 0; + self.think = NailGrenBurst; +}; + +void () NailGrenadeNailEm = { + self.velocity = '0 0 0'; + self.nextthink = time + 0.1; + self.think = NailGrenadeLaunchNail; + self.playerclass = 0; +}; + +void () NailGrenadeLaunchNail = { + local float i, j; + local float current_yaw; + + i = 0; + while (i < 3) { + + j = (random() + 2) * 5; + current_yaw = anglemod(self.angles_y + j); + self.angles_y = current_yaw; + self.angles_x = 0; + self.angles_z = 0; + makevectors(self.angles); + + deathmsg = DMSG_GREN_NAIL; + launch_spike(self.origin, v_forward); + newmis.velocity = v_forward * 1000; + newmis.touch = superspike_touch; + newmis.weapon = DMSG_GREN_NAIL; + + i = i + 1; + } + self.playerclass = self.playerclass + 1; + self.nextthink = time + 0.1; + + if (self.playerclass > 50) { + self.weapon = DMSG_GREN_NAIL; + self.think = GrenadeExplode; + } +}; + +void () NailGrenLaser = { //using self.playerclass to count thinks + local float current_yaw; + local vector org; + + if(self.playerclass == 0){ + self.velocity = '0 0 0'; + self.real_owner = self.owner; + self.owner = world; //work-around because traceent wouldnt hit self or self.owner + current_yaw = 0; + lasergren_reqthinks = (lasergren_rotationcount * lasergren_rotationtime) / lasergren_thinktime; + lasergren_angleperthink = (lasergren_rotationcount * 360) / lasergren_reqthinks; + self.v_angle = self.angles; + } else { + current_yaw = anglemod(self.v_angle_y + lasergren_angleperthink); + } + self.v_angle_y = current_yaw; + self.v_angle_x = 0; + self.v_angle_z = 0; + self.angles = self.v_angle; + self.SendFlags |= FOPP_ANGLES; + + makevectors(self.v_angle); + + org = self.origin; + traceline(org, org + v_forward * lasergren_range, 0, self); + + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_LIGHTNING2); + WriteEntity(MSG_MULTICAST, self); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + WriteCoord(MSG_MULTICAST, trace_endpos_x); + WriteCoord(MSG_MULTICAST, trace_endpos_y); + WriteCoord(MSG_MULTICAST, trace_endpos_z); + multicast(org, MULTICAST_PHS); + + if (trace_ent.takedamage){ + + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_LIGHTNINGBLOOD); + WriteCoord(MSG_MULTICAST, trace_endpos_x); + WriteCoord(MSG_MULTICAST, trace_endpos_y); + WriteCoord(MSG_MULTICAST, trace_endpos_z); + multicast(trace_endpos, MULTICAST_PVS); + + deathmsg = DMSG_GREN_SHOCK; + TF_T_Damage(trace_ent, self, self.real_owner, lasergren_damage, TF_TD_NOTTEAM, TF_TD_ELECTRICITY); + } + if (self.t_width < time) { + FO_Sound(self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + self.t_width = time + 0.8; + } + + self.nextthink = time + lasergren_thinktime; + self.playerclass++; + //already did this above + //current_yaw = anglemod(self.angles_y + lasergren_angleperthink); + + if (self.playerclass > lasergren_reqthinks) { //not >= to give an extra think so it hits start again + self.owner = self.real_owner; + self.weapon = DMSG_GREN_SHOCK; + self.think = GrenadeExplode; + } +}; + +void () NailGrenBurst = { + local float i; + local float current_yaw; + current_yaw = 0; + i = 0; + self.velocity = '0 0 0'; + + while (i < 16){ + self.angles_y = current_yaw; + self.angles_x = 0; + self.angles_z = 0; + makevectors(self.angles); + + sound(self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + + deathmsg = DMSG_GREN_BURST; + launch_spike(self.origin, v_forward); + newmis.velocity = v_forward * 1000; + newmis.touch = superspike_touch; + newmis.weapon = DMSG_GREN_BURST; + newmis.nextthink = time + burstgren_range; + i++; + current_yaw = anglemod(self.angles_y + 22.5); + } + self.playerclass++; + self.nextthink = time + burstgren_interval; + + if (self.playerclass >= burstgren_count){ + self.nextthink = time + 0.1; + self.weapon = DMSG_GREN_BURST; + self.think = GrenadeExplode; + } +}; + +void () TeamFortress_NailGrenInfo = { + sprint(self,2,Q"\n\snailgren info - num in brackets are default - num shown are to one dec place - updated on map change\s\n"); + sprint(self,2,Q"\n\s----------------------------------------------------------------------------------------------\s\n"); + sprint(self,2,Q"ngt nailgren_type ",ftos(nailgren_type)," (",ftos(NGR_TYPE_NAIL),") \s// 0 default, 1 laser, 2 burst \n\n"); + sprint(self,2,Q"lgrc lasergren_rotationcount ",ftos(lasergren_rotationcount)," (",ftos(NGR_LASER_DEFAULT_ROTATIONCOUNT),") \s// how many rotations to perform\n"); + sprint(self,2,Q"lgrt lasergren_rotationtime ",ftos(lasergren_rotationtime)," (",ftos(NGR_LASER_DEFAULT_ROTATIONTIME),") \s// time to complete one rotation\n"); + sprint(self,2,Q"lgr lasergren_range ",ftos(lasergren_range)," (",ftos(NGR_LASER_DEFAULT_RANGE),") \s// range\n"); + sprint(self,2,Q"lgd lasergren_damage ",ftos(lasergren_damage)," (",ftos(NGR_LASER_DEFAULT_DAMAGE),") \s// damage\n"); + sprint(self,2,Q"lgtt lasergren_thinktime ",ftos(lasergren_thinktime)," (0.1) \s// time between thinks, lower to smooth rotation \n\n"); + sprint(self,2,Q"bgc burstgren_count ",ftos(burstgren_count)," (",ftos(NGR_BURST_DEFAULT_COUNT),") \s// how many bursts of nails\n"); + sprint(self,2,Q"bgi burstgren_interval ",ftos(burstgren_interval)," (",ftos(NGR_BURST_DEFAULT_INTERVAL),") \s// time between bursts\n"); + sprint(self,2,Q"bgr burstgren_range ",ftos(burstgren_range)," (",ftos(NGR_BURST_DEFAULT_RANGE),") \s// time before nail projectiles destroyself\n"); + sprint(self,2,Q"\s----------------------------------------------------------------------------------------------\s\n"); + self.impulse = 0; +}; diff --git a/vote.qc b/ssqc/vote.qc similarity index 66% rename from vote.qc rename to ssqc/vote.qc index 7c50f1a1e..56f50ca86 100644 --- a/vote.qc +++ b/ssqc/vote.qc @@ -1,5 +1,5 @@ -// MAP VOTING FOR CLASSIC FORTRESS -// =============================== +// MAP VOTING FOR FORTRESSONE +// ========================== // Displays a vote menu during the last few minutes of gameplay on a map. // functions by order of appearance @@ -12,6 +12,7 @@ float () Vote_GetTrickVotes; float () Vote_GetRaceVotes; void (entity pe_player) Vote_ForceNext; void () GotoNextMap; +void () execute_changelevel; float () Vote_GetForceNextVotes; void () Vote_Check; void (float pf_decider) Vote_SetupVote; @@ -35,18 +36,17 @@ float (float pf_from, float pf_to) RandomRange; void () Vote_DropLosers; float (string ps_list) List_Count; string (string ps_list, float pf_idx) List_Index; - -// global variables -float vote1_cnt, vote2_cnt, vote3_cnt, vote4_cnt, vote5_cnt; -string vote1_map, vote2_map, vote3_map, vote4_map, vote5_map, vote_result; -float vote_started, vote_update, vote_winnercount, vote_decider, vote_abort; +string (entity ent, string ps_short, string ps_setting, string ps_default) FO_GetUserSettingString; +void (entity ent) TeamFortress_SetSpeed; +void FO_Sound(entity e, float chan, string samp, float vol, float atten); +void (entity p, string vote) VoteForMap; // opens the map voting early // called from weapons.qc:ImpulseCommands() void (entity pe_player) Vote_NextMap = { local float f_votes, f_votes_needed, f_votes_left; - if (clanbattle) + if (disable_voting || vote_style) return; if (!pe_player.team_no || !pe_player.playerclass || pe_player.vote_next) @@ -87,7 +87,7 @@ void (entity pe_player) Vote_NextMap = { void (entity pe_player) Vote_TrickMap = { local float f_votes, f_votes_needed, f_votes_left; - if (clanbattle) + if (disable_voting || vote_style) return; if (!pe_player.team_no || !pe_player.playerclass || pe_player.vote_trick) @@ -128,7 +128,7 @@ void (entity pe_player) Vote_TrickMap = { void (entity pe_player) Vote_RaceMap = { local float f_votes, f_votes_needed, f_votes_left; - if (clanbattle) + if (disable_voting || vote_style) return; if (!pe_player.team_no || !pe_player.playerclass || pe_player.vote_race) @@ -168,7 +168,7 @@ void (entity pe_player) Vote_RaceMap = { // called from weapons.qc:ImpulseCommands() void (entity pe_player) Vote_ToggleMenu = { - if (clanbattle) + if (disable_voting || vote_style) return; if (vote_started > 0 && !vote_abort) { @@ -240,7 +240,7 @@ float () Vote_GetRaceVotes = { void (entity pe_player) Vote_ForceNext = { local float f_votes, f_votes_needed, f_votes_left; - if (clanbattle) + if (disable_voting || vote_style) return; if (!pe_player.team_no || !pe_player.playerclass) @@ -290,6 +290,10 @@ float () Vote_GetForceNextVotes = { // check if voting should start each frame // called from world.qc:StartFrame() void () Vote_Check = { + + if (disable_voting || vote_style) + return; + local float closetime = timelimit - CF_MAPVOTE_FINISH; local float decidertime_force = closetime - CF_MAPVOTE_FORCESHOW; local float decidertime = closetime - CF_MAPVOTE_DURATION_DECIDER; @@ -509,6 +513,9 @@ void () Vote_MenuClose = { // shows the map vote menu // called from Vote_Input(), Menu_Open() void (entity pe_player) Vote_Menu = { + if (disable_voting) + return; + local string s_choose, s_vote1, s_vote2, s_vote3, s_vote4, s_vote5; local string s_tmp1, s_tmp2, s_tmp3, s_tmp4, s_tmp5; local float f_width = 0, f_timeleft = 0; @@ -529,6 +536,15 @@ void (entity pe_player) Vote_Menu = { else if (time - pe_player.menu_time < CF_MAPVOTE_FORCESHOW) f_timeleft = CF_MAPVOTE_FORCESHOW - (time - pe_player.menu_time); + if(infokeyf(pe_player, INFOKEY_P_CSQCACTIVE)) { + //fte+csqc has its own team menu + //ask the client to activate it + Menu_Close(pe_player); + pe_player.vote_close = 1; + UpdateClientMenu_Vote(pe_player, floor(f_timeleft - 0.1)); + return; + } + s_choose = strzone(strcat("Vote for next map (closes in ", strcat(ftos(floor(f_timeleft - 0.1)), "s):\n\n"))); s_tmp1 = strzone(""); s_tmp2 = strzone(""); @@ -538,7 +554,7 @@ void (entity pe_player) Vote_Menu = { if (vote1_map != string_null) { strunzone(s_tmp1); - s_tmp1 = strcat("“‘ ", vote1_map); + s_tmp1 = strcat(Q"\s[1]\s ", vote1_map); if (vote1_cnt) s_tmp1 = strzone(strcat(s_tmp1, strcat(" (", strcat(ftos(vote1_cnt), " votes)")))); else @@ -548,7 +564,7 @@ void (entity pe_player) Vote_Menu = { } if (vote2_map != string_null) { strunzone(s_tmp2); - s_tmp2 = strcat("”‘ ", vote2_map); + s_tmp2 = strcat(Q"\s[2]\s ", vote2_map); if (vote2_cnt) s_tmp2 = strzone(strcat(s_tmp2, strcat(" (", strcat(ftos(vote2_cnt), " votes)")))); else @@ -559,7 +575,7 @@ void (entity pe_player) Vote_Menu = { if (vote3_map != string_null) { strunzone(s_tmp3); if (!vote_decider || vote3_cnt) - s_tmp3 = strcat("•‘ ", vote3_map); + s_tmp3 = strcat(Q"\s[3]\s ", vote3_map); if (vote3_cnt) s_tmp3 = strzone(strcat(s_tmp3, strcat(" (", strcat(ftos(vote3_cnt), " votes)")))); else @@ -570,7 +586,7 @@ void (entity pe_player) Vote_Menu = { if (vote4_map != string_null) { strunzone(s_tmp4); if (!vote_decider || vote4_cnt) - s_tmp4 = strcat("–‘ ", vote4_map); + s_tmp4 = strcat(Q"\s[4]\s ", vote4_map); if (vote4_cnt) s_tmp4 = strzone(strcat(s_tmp4, strcat(" (", strcat(ftos(vote4_cnt), " votes)")))); else @@ -581,7 +597,7 @@ void (entity pe_player) Vote_Menu = { if (vote5_map != string_null) { strunzone(s_tmp5); if (!vote_decider || vote5_cnt) - s_tmp5 = strcat("—‘ ", vote5_map); + s_tmp5 = strcat(Q"\s[5]\s ", vote5_map); if (vote5_cnt) s_tmp5 = strzone(strcat(s_tmp5, strcat(" (", strcat(ftos(vote5_cnt), " votes)")))); else @@ -952,30 +968,31 @@ string () Vote_GetWinner = { // called from Vote_SetupVote(), Vote_GetWinner() string () Vote_GetWinnerList = { local string s_maplist; + local string s_tmp; s_maplist = strzone(""); if (vote1_cnt) { - local string s_tmp = s_maplist; + s_tmp = s_maplist; s_maplist = strzone(strcat(s_maplist, strcat(vote1_map, " "))); strunzone(s_tmp); } if (vote2_cnt) { - local string s_tmp = s_maplist; + s_tmp = s_maplist; s_maplist = strzone(strcat(s_maplist, strcat(vote2_map, " "))); strunzone(s_tmp); } if (vote3_cnt) { - local string s_tmp = s_maplist; + s_tmp = s_maplist; s_maplist = strzone(strcat(s_maplist, strcat(vote3_map, " "))); strunzone(s_tmp); } if (vote4_cnt) { - local string s_tmp = s_maplist; + s_tmp = s_maplist; s_maplist = strzone(strcat(s_maplist, strcat(vote4_map, " "))); strunzone(s_tmp); } if (vote5_cnt) { - local string s_tmp = s_maplist; + s_tmp = s_maplist; s_maplist = strzone(strcat(s_maplist, vote5_map)); strunzone(s_tmp); } @@ -1029,7 +1046,7 @@ float (string ps_list) List_Count = { for (i = 0; i < f_length; i++) { // set current character - local string s_current = substr(ps_list, i, 1); + local string s_current = substring(ps_list, i, 1); // non-empty space => word if (s_current != " " && s_previous == " ") @@ -1052,7 +1069,7 @@ string (string ps_list, float pf_idx) List_Index = { for (i = 0; i < f_length; i++) { // set current character - local string s_current = substr(ps_list, i, 1); + local string s_current = substring(ps_list, i, 1); // non-empty space => start of word if (s_current != " " && s_previous == " ") { @@ -1063,14 +1080,604 @@ string (string ps_list, float pf_idx) List_Index = { // empty space => end of word if (s_current == " " && f_start > -1) - return strzone(substr(ps_list, f_start, i - f_start)); + return strzone(substring(ps_list, f_start, i - f_start)); s_previous = s_current; } // if f_start is set it means a list item was found if (f_start > -1) - return strzone(substr(ps_list, f_start, i - f_start)); + return strzone(substring(ps_list, f_start, i - f_start)); return strzone(string_null); }; + + +/* ====== New Vote System ======= */ + +/* +"netname" "amth1" //map name +"broadcast" "Duel arena, ctf and Sniper War" //description +"team_broadcast" "Duel/Arena" //group +"ex_skill_min" "2" //min players +"ex_skill_max" "8" //max players +"team_no" "4" //supported teams + +.cnt = number of current votes +*/ + +entity (string name) AddVoteMapGroup = { + if(!name) { + return world; + } + local entity mg = find(world, classname, "map_group"); + while(mg) { + if(mg.netname == name) { + return mg; + } + mg = find(mg, classname, "map_group"); + } + + mg = spawn(); + mg.classname = "map_group"; + mg.netname = name; + return mg; +}; + +entity (string name, string desc, string mapgroup, float num_teams, float min_players, float max_players) AddVoteMap = { + if(!name) { + return world; + } + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(mc.netname == name) { + return mc; + } + mc = find(mc, classname, "map_candidate"); + } + + mc = spawn(); + mc.classname = "map_candidate"; + mc.netname = name; + mc.broadcast = desc; + mc.team_broadcast = mapgroup; + mc.owner = AddVoteMapGroup(mapgroup); + mc.team_no = num_teams?num_teams:2; + mc.ex_skill_min = min_players?min_players:6; + mc.ex_skill_max = max_players?max_players:16; + UpdateClient_VoteMap_Add_Broadcast(mc); + return mc; +}; + +float (string name) RemoveVoteMap = { + if(!name) { + return FALSE; + } + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(mc.netname == name) { + UpdateClient_VoteMap_Delete_Broadcast(mc); + dremove(mc); + return TRUE; + } + mc = find(mc, classname, "map_candidate"); + } + return FALSE; +} + +void() map_candidate_use = { + if(activator && activator.classname == "player") { + VoteForMap(activator, self.netname); + } +} + +void() map_candidate = { + if(self.team_broadcast && self.team_broadcast != "") { + self.owner = AddVoteMapGroup(self.team_broadcast); + } + self.use = map_candidate_use; +}; + +void () InitVoteMaps = { + string votemap = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + //No point adding the vote map if it is the current one, and thus known to be missing the ents + if(votemap != mapname) { + //Otherwise, assume we somehow ended up on the wrong map and offer to vote for the correct one + local entity mc = spawn(); + mc.classname = "map_candidate"; + mc.netname = votemap; + mc.broadcast = "Currently configured vote map"; + } + AddVoteMap("2fort5r", "Basic CTF", "CTF", 2, 8, 20); + AddVoteMap("1on1r", "Basic Duel", "Duel", 2, 2, 2); + AddVoteMap("sq1", "Basic Arena", "Arena", 2, 2, 8); + AddVoteMap("mbasesr", "Futuristic CTF", "CTF", 2, 6, 16); + AddVoteMap("amth1", "Basic Everything", "Duel", 4, 2, 8); + AddVoteMap("well6", "The Well (6)", "CTF", 2, 6, 20); + AddVoteMap("genders2", "Small CTF", "CTF", 2, 6, 20); + AddVoteMap("frozen1", "CTF with buttons", "CTF", 2, 6, 16); +}; + +void (entity p) ListVoteMaps = { + sprint(p, PRINT_HIGH, "Maps eligible for voting:\n"); + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + sprint(p, PRINT_HIGH, "\b", mc.netname, "\b: ", mc.broadcast, "\n"); + mc = find(mc, classname, "map_candidate"); + } + sprint(p, PRINT_HIGH, "Use \bcmd votemap \b to cast your vote.\n"); +}; + +void () anarchy_timer_think = { + local entity e = find(world, classname, "player"); + while(e) { + if(e.vote_map) { + e.items |= IT_QUAD; + e.tfstate &= 128; + e.super_damage_finished = time + 666; + //e.effects = e.effects | EF_BLUE; + } + TeamFortress_SetSpeed(e); + e = find(e, classname, "player"); + } +}; + +void () AnarchyMode = { + if(vote_anarchy_mode || !voting_started || vote_total_votes <= 0) + return; + vote_anarchy_mode = TRUE; + local entity e = find(world, classname, "player"); + while(e) { + // TODO: Add a no weap switch to handle this like it used to. + e.current_slot = SlotMelee; + if(e.vote_map) { + e.items |= IT_QUAD; + e.tfstate &= 128; + e.super_damage_finished = time + 666; + //e.effects = e.effects | EF_BLUE; + } + TeamFortress_SetSpeed(e); + e = find(e, classname, "player"); + } + bprint(PRINT_HIGH, "\bDemocracy has failed!\b Let the result be decided in \bBATTLE\b\n"); + FO_Sound(self, CHAN_AUTO, "boss1/out1.wav", 1, ATTN_NONE); + //e = spawn(); + //e.classname = "anarchy_timer"; + //e.think = anarchy_timer_think; + //e.nextthink = time + 1; +}; + +void () EndVoting = { + voting_started = FALSE; + vote_anarchy_mode = FALSE; + vote_total_votes = 0; + local entity e = find(world, classname, "vote_timer"); + if(e) { + dremove(e); + } + e = find(world, classname, "anarchy_timer"); + if(e) { + dremove(e); + } + e = find(world, classname, "player"); + while(e) { + e.vote_map = world; + e.items = 0; + e.current_slot = SlotMelee; + e.super_damage_finished = 0; + //if(e.effects & EF_BLUE) e.effects = e.effects - EF_BLUE; + TeamFortress_SetSpeed(e); + e.health = 100; + e = find(e, classname, "player"); + } + e = find(world, classname, "map_candidate"); + while(e) { + e.cnt = 0; + UpdateAllClientMapVotes(e); + e = find(e, classname, "map_candidate"); + } +}; + +void (string map_name) ChangeToVotedMap = { + vote_result = map_name; + nextmap = map_name; +// if(!votemode) { +// votemode = 2; +// } + EndVoting(); + //GotoNextMap(); + execute_changelevel(); +}; + +float () CheckVoting = { + if(intermission_running || vote_result) { + return FALSE; + } + local float totalplayers = 0, numplayers = 0, votedplayers = 0, prettythreshold = 0; + local entity e = find(world, classname, "player"); + while(e) { + if(!e.has_disconnected) { + totalplayers++; + if(e.health > 0 || !vote_anarchy_mode) { + numplayers++; + if(e.vote_map) { + votedplayers++; + } + } + } + e = find(e, classname, "player"); + } + + if(numplayers <= 0 || votedplayers <= 0) { + bprint(PRINT_HIGH, "\bVoting reset...\b\n"); + FO_Sound(self, CHAN_AUTO, "items/qpi2.wav", 1, ATTN_NONE); + EndVoting(); + return FALSE; + } + + prettythreshold = ceil(numplayers * vote_threshold); + if(prettythreshold == (numplayers * vote_threshold)) { + //divides evenly, but need a strict excess, but written in integers + prettythreshold++; + } + + if(!votemode) bprint(PRINT_HIGH, "\bCurrent votes (total of \b",ftos(prettythreshold),"\b needed to change):\b\n"); + e = find(world, classname, "map_candidate"); + while(e) { + if(e.cnt > 0 && !votemode) { + bprint(PRINT_HIGH, "", e.netname, ": ", ftos(e.cnt), "\n"); + } + //unanimous - change immediately + if(e.cnt >= totalplayers) { + bprint(PRINT_HIGH, "\bUnanimous choice:\b ", e.netname, "\n"); + FO_Sound(self, CHAN_AUTO, "shalrath/death.wav", 1, ATTN_NONE); + ChangeToVotedMap(e.netname); + return FALSE; + } else if(vote_anarchy_mode) { + if(e.cnt >= numplayers) { + bprint(PRINT_HIGH, "\bAnarchy succeeded!\b Winner by combat: ", e.netname, "\n"); + FO_Sound(self, CHAN_AUTO, "shalrath/attack2.wav", 1, ATTN_NONE); + ChangeToVotedMap(e.netname); + return FALSE; + } + } else if(voting_expires < time || (vote_style && !votemode)) { + // do we want this to trigger during anarchy too when hitting majority? + if(e.cnt > (numplayers * vote_threshold)) { + bprint(PRINT_HIGH, "\bTime's up!\b Majority choice: ", e.netname, "\n"); + FO_Sound(self, CHAN_AUTO, "boss1/sight1.wav", 1, ATTN_NONE); + ChangeToVotedMap(e.netname); + return FALSE; + } else if(votemode) { + AnarchyMode(); + } + if(votemode) break; + } + e = find(e, classname, "map_candidate"); + } + return TRUE; +}; + +void (entity p) PrintVoting = { + if(!voting_started) { + sprint(p, PRINT_HIGH, "No votes.\n"); + return; + } + local float expires = rint(voting_expires - time); + if(expires > 0) { + sprint(p, PRINT_HIGH, ftos(expires), " seconds remain. "); + } + if(vote_anarchy_mode) { + sprint(p, PRINT_HIGH, "\bANRACHY MODE!\b "); + } + sprint(p, PRINT_HIGH, "\bCurrent votes:\b\n"); + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(mc.cnt > 0) { + if(p.vote_map == mc) { + sprint(p, PRINT_HIGH, "", mc.netname, ": ", ftos(mc.cnt), " \b(Your vote)\b\n"); + } else { + sprint(p, PRINT_HIGH, "", mc.netname, ": ", ftos(mc.cnt), "\n"); + } + } + mc = find(mc, classname, "map_candidate"); + } +}; + +void() vote_think = { + if(CheckVoting()) { + if(vote_anarchy_mode) { + bprint(PRINT_HIGH, "\bANARCHY MODE!\b\n"); + FO_Sound(self, CHAN_AUTO, "items/damage.wav", 1, ATTN_NONE); + self.nextthink = time + 5; + } else { + local float expires = ceil(voting_expires - time); + if(expires < 10 || expires % 30 == 0) { + bprint(PRINT_HIGH, "\x10",ftos(expires),"\x11 seconds left\n"); + FO_Sound(self, CHAN_AUTO, "buttons/switch04.wav", 1, ATTN_NONE); + } + self.nextthink = time + 1; + } + } else { + dremove(self); + } +}; + +void () StartVoting = { + if(voting_started || disable_voting) + return; + local float expires = CF_GetSetting("votetime", "vote_time", "60"); + local entity te; + voting_started = TRUE; + vote_anarchy_mode = FALSE; + vote_total_votes = 0; + bprint(PRINT_HIGH, "Voting has started!\n"); + if(!votemode) { + return; + } + FO_Sound(self, CHAN_AUTO, "items/protect.wav", 1, ATTN_NONE); + if(expires) { + bprint(PRINT_HIGH, "Voting closes in ", ftos(expires), " seconds. \bBe quick!\b\n"); + voting_expires = time + expires; + te = spawn(); + te.think = vote_think; + te.classname = "vote_timer"; + te.nextthink = time + 1; + } else { + bprint(PRINT_HIGH, "No voting timelimit configured, so will require unanimous decision\n"); + } +}; + + +void (entity p, string vote) VoteForMap = { + if (disable_voting) + return; + + float filehandle; + filehandle = fopen(strcat("maps/",vote,".bsp"), FILE_READ); + if (filehandle >= 0) { + AddVoteMap(vote,"Custom vote","Custom Vote",0,0,0); + fclose(filehandle); + } else { + sprint(p, PRINT_HIGH, "\bUnfortunately, \b", vote, "\b doesn't appear to be a valid map.\nUse \bcmd listmaps\b to for a list of valid maps.\b\n"); + } + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(mc.netname == vote) { + if(!voting_started) { + StartVoting(); + bprint(PRINT_HIGH, p.netname, " suggests \b", vote, "\b\n"); + vote_total_votes++; + } else { + if(p.vote_map) { + bprint(PRINT_HIGH, p.netname, " changes vote to \b", vote, "\b\n"); + p.vote_map.cnt--; + UpdateAllClientMapVotes(p.vote_map); + } else { + FO_Sound(self, CHAN_AUTO, "items/protect.wav", 1, ATTN_NONE); + bprint(PRINT_HIGH, p.netname, " has voted for \b", vote, "\b\n"); + vote_total_votes++; + } + } + p.vote_map = mc; + mc.cnt++; + UpdateAllClientMapVotes(mc); + CheckVoting(); + return; + } + mc = find(mc, classname, "map_candidate"); + } +}; + +void (entity p) UnvoteForMap = { + if (disable_voting) + return; + + if(p.vote_map) { + if(vote_anarchy_mode) { + bprint(PRINT_HIGH, p.netname, " \brecinds vote for\b ", p.vote_map.netname, " \bin battle\b\n"); + } else { + bprint(PRINT_HIGH, p.netname, " \babandons vote for\b ", p.vote_map.netname, "\n"); + } + p.vote_map.cnt--; + UpdateAllClientMapVotes(p.vote_map); + p.vote_map = world; + vote_total_votes--; + } + CheckVoting(); +}; + +void (entity p) VoteToEndMap = { + if(CF_GetSetting("vs", "vote_style", "1")) { + if(p.vote_next) { + bprint(PRINT_HIGH, p.netname, " desides to keep playing.\n"); + } else { + bprint(PRINT_HIGH, p.netname, " \bvotes to end the current map.\b.\n"); + } + p.vote_next = !p.vote_next; + local float numplayers = 0, numvoted = 0; + local entity te = find (world, classname, "player"); + while (te) { + if (!te.has_disconnected) { + numplayers = numplayers + 1; + if(te.vote_next) { + numvoted++; + } + } + te = find (te, classname, "player"); + } + if(numvoted > (numplayers * vote_threshold)) { + sound(self, CHAN_AUTO, "boss1/out1.wav", 1, ATTN_NONE); + bprint(PRINT_HIGH, "\bMap ended by majority vote.\b.\n"); + if(nextmap == "") { + vote_result = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + votemode = 2; + } + execute_changelevel(); + } else { + sound(self, CHAN_AUTO, "misc/secret.wav", 1, ATTN_NONE); + bprint(PRINT_HIGH, "\x10",ftos((floor(numplayers * vote_threshold) + 1) - numvoted),"\x11 more votes required to end the current map.\n"); + bprint(PRINT_HIGH, "Type \bbreak\b to toggle your vote.\n"); + } + } +}; + +void (entity p) VoteYes = { + if(!voting_started) { + sprint(p, PRINT_HIGH, "No suggestions so far.\n"); + return; + } + if(p.vote_map) { + sprint(p, PRINT_HIGH, "You've already voted for (", p.vote_map.netname, ")!\n"); + return; + } + + local entity mc = find(world, classname, "map_candidate"); + local entity mc2 = world; + local float count = 0; + while(mc) { + if(mc.cnt > 0) { + count++; + mc2 = mc; + } + mc = find(mc, classname, "map_candidate"); + } + if(count > 1) { + sprint(p, PRINT_HIGH, "There are multiple suggestions. Use 'cmd votemap' to vote!\n"); + return; + } + if(count == 1) { + VoteForMap(p, mc2.netname); + } +} + +entity (float index) Vote_Menu_Map_GetMapCandidate = { + local float count = 1; + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + if(count == index) { + return mc; + } + count++; + mc = find(mc, classname, "map_candidate"); + } + return world; +}; + +void (float update) Vote_Menu_Map; +void (float update) Vote_Menu_Map_Action; +void (float inp) Vote_Menu_Map_Action_Input = { + switch (inp) { + case 1: + Menu_Close(self); + VoteForMap(self, self.admin_use.netname); + return; + case 2: + Menu_Close(self); + bprint(PRINT_HIGH, self.netname, " is changing the map to ", self.admin_use.netname, "\n"); + if(self.is_admin) { + ChangeToVotedMap(self.admin_use.netname); + } else { + //rcon, but only from clientside to make sure it's allowed + stuffcmd(self, strcat("rcon map ", self.admin_use.netname,"\n")); + } + return; + case 10: + Vote_Menu_Map(0); + return; + } +}; +void (float update) Vote_Menu_Map_Action = { + // allow toggling team menu using any method to show it + if (!update && self.menu_input == Vote_Menu_Map_Action_Input) { + Menu_Input(0); + return; + } + + //self.admin_use is a map_candidate + if(self.admin_use) { + // prepare menu strings + local string s_title = strcat("\sActions for map\s\n", self.admin_use.netname ,"\n\n"); + local string s_1 = Q"\s[1]\s Vote \n"; + local string s_2 = Q"\s[2]\s Change (Admin)\n"; + local string s_0 = Q"\s[0]\s Back \n"; + + Status_Menu(self, Vote_Menu_Map_Action_Input, s_title, s_1, s_2, "\n", s_0); + + strunzone(s_title); + } else { + sprint(self, PRINT_HIGH, "Something went wrong in the map menu\n"); + Vote_Menu_Map(0); + } + + +}; + +void (float inp) Vote_Menu_Map_Input = { + if(inp < 0 || inp > 10) { + Vote_Menu_Map(1); + return; + } + switch (inp) { + case 8: + self.current_menu_page--; + if(self.current_menu_page < 1) { + self.current_menu_page = 1; + } + Vote_Menu_Map(0); + return; + case 9: + self.current_menu_page++; + Vote_Menu_Map(0); + return; + case 0: + case 10: + Menu_Close(self); + return; + } + local entity mc = Vote_Menu_Map_GetMapCandidate((self.current_menu_page - 1) * 7 + inp); + if(mc) { + self.admin_use = mc; + if(self.classname == "observer") { + Vote_Menu_Map_Action_Input(2); + } else { + Vote_Menu_Map_Action(0); + } + } +}; +void (float update) Vote_Menu_Map = { + // allow toggling team menu using any method to show it + if (!update && self.menu_input == Vote_Menu_Map_Input) { + Menu_Input(0); + return; + } + + if(infokeyf(self, INFOKEY_P_CSQCACTIVE)){ + UpdateClientMenu_Maps(self); + return; + } + + // prepare menu strings + local string s_title = strcat("\sEquations\s\nPage ",ftos(self.current_menu_page),"\n\n"); + local string s_8 = Q"\s[8]\s Previous \n"; + local string s_9 = Q"\s[9]\s Next \n"; + local string s_0 = Q"\s[0]\s Exit \n"; + local string s_choices = ""; + local float count = 0, remainder = 0, page = 0; + local entity mc = find(world, classname, "map_candidate"); + while(mc) { + page = floor(count / 7); + if(page == (self.current_menu_page - 1)) { + remainder = count % 7; + s_choices = strcat(s_choices, "\s[\s", ftos(remainder + 1), "\s]\s ", strpadr(mc.netname, 20), "\n"); + } else if(page >= self.current_menu_page) { + break; + } + count++; + mc = find(mc, classname, "map_candidate"); + } + + Status_Menu(self, Vote_Menu_Map_Input, s_title, s_choices, "\n", s_8, s_9, s_0); + //self.current_menu_type = ADMIN_MENU_TYPE_MAIN; + + strunzone(s_choices); strunzone(s_title); +}; diff --git a/ssqc/weapons.qc b/ssqc/weapons.qc new file mode 100644 index 000000000..5bb9ebdfc --- /dev/null +++ b/ssqc/weapons.qc @@ -0,0 +1,2695 @@ +void () player_run; + +void () TeamFortress_DisplayDetectionItems; +float (vector veca, vector vecb) crossproducttf; + +void (vector org, float damage) SpawnBlood; + +void () SuperDamageSound; + +void (Slot slot) W_ChangeWeaponSlot; +void () W_ChangeToBestWeapon; +void () W_PrintWeaponMessage; + +void () button_fire; + +void (entity pl, float fr) TF_AddFrags; + +void () DropGoalItems; + +void () TeamFortress_DisplayLegalClasses; + +void () TeamFortress_ShowIDs; +void () TeamFortress_ShowTF; + +void TeamFortress_PrimeGrenade(float inp, float is_player); +void () TeamFortress_ThrowGrenade; +void (float inp) TeamFortress_PrimeThrowGrenade; + +void () PipebombTouch; + +void () SniperSight_Create; + +void () Help_Show; +void () TeamFortress_Inventory; +void () TeamFortress_SaveMe; +void (entity pe_player, float f_type) CF_Identify; +void () TeamFortress_ReloadNext; +void () TeamFortress_StatusQuery; +void () CF_Scout_Dash; +void () CF_Spy_DisguiseStop; +void () TeamFortress_DetpackMenu; +void () CF_Medic_AuraToggle; +void () FO_LockToggle; +void () FO_Airblast; +void () TeamFortress_EngineerBuild; +void () TeamFortress_EngineerBuildStop; +void () TeamFortress_Scan; +void () TeamFortress_Discard; +void () TeamFortress_Discard_DropAmmo; +void (float issilent) FO_Spy_FeignCmd; +void (float issilent) FO_Spy_ToggleFeign; +void () FO_Engineer_ToggleDispenser; +void () FO_Engineer_ToggleSentry; +float (float force) TeamFortress_DetonatePipebombs; +void (float timer) TeamFortress_ToggleDetpack; +void (float timer) TeamFortress_SetDetpack; +void () TeamFortress_DetpackStop; +void (entity e) TeamFortress_PlacePracticeSpawn; +void (entity e) TeamFortress_RemovePracticeSpawn; +void () DropKey; +void () UseSpecialSkill; +void () UseSpecialSkill2; +void () RemoveFlare; +void () ScannerSwitch; +float () FO_CheckForReload; + +void (float all) TeamFortress_TeamShowScores; +void (entity Player) TeamFortress_TeamShowMemberClasses; + +void () Admin_CountPlayers; +void () Admin_CycleDeal; +void () Admin_DoKick; +void () Admin_DoBan; +void () Admin_CeaseFire; +void () Admin_UpdateServer; +void () Admin_ListIPs; + +void () fadetoblack; +void () fadefromblack; +void () fadetowhite; +void () fadefromwhite; + +void (entity disp) Engineer_UseDispenser; +void (entity gun) Engineer_UseSentryGun; + +void () TeamFortress_HelpMap; +void () TeamFortress_NailGrenInfo; + +void () BioInfection_Decay; +void () BioInfection_MonsterDecay; + +float (float weap) W_GetSlot; +float (float weap) W_OldGetSlot; +void () W_FireFlame; +void W_FireIncendiaryCannon(vector org, vector angles, float use_ctime=0); +void () W_FireTranq; +void () W_FireRailgun; +void () W_FireImpeller; + +void () HallucinationTimer; +void () TranquiliserTimer; + +void () TeamFortress_CTF_FlagInfo; +void () ClanMode; +void () QuadMode; +void () PlayerReady; +void () PlayerNotReady; +void () Broadcast_Players_NotReady; +void () StartTimer; + +void () ToggleInvincibility; + +float () GetMaxWeaponInput; +float () IsUsingOldImpulses; +float () IsUsingCFImpulses; + +void (entity pl) W_UpdateCurrentWeapon; + +void (entity ent, float num) SetFlameCount; + +.float button3_keydown; +.float button3_keydown_frame; +.float button3_keyup_frame; +.float button4_keydown; +.float button4_keydown_frame; +.float button4_keyup_frame; +.float button5_keydown; +.float button5_keydown_frame; +.float button5_keyup_frame; +.float button6_keydown; +.float button6_keydown_frame; +.float button6_keyup_frame; +.float oldbuttons; + +void () W_Precache = { + precache_sound("weapons/r_exp3.wav"); + precache_sound("weapons/rocket1i.wav"); + precache_sound("weapons/sgun1.wav"); + precache_sound("weapons/guncock.wav"); + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + precache_sound("weapons/spike2.wav"); + precache_sound("weapons/tink1.wav"); + precache_sound("weapons/grenade.wav"); + precache_sound("weapons/bounce.wav"); + precache_sound("weapons/shotgn2.wav"); + precache_sound("wizard/wattack.wav"); + precache_sound("items/r_item1.wav"); + precache_sound("items/r_item2.wav"); + precache_model("progs/flame2.mdl"); + precache_sound("ambience/fire1.wav"); + precache_model2("progs/v_spike.mdl"); + precache_sound("hknight/hit.wav"); + precache_sound("weapons/detpack.wav"); + precache_sound("weapons/turrset.wav"); + precache_sound("weapons/turrspot.wav"); + precache_sound("weapons/turridle.wav"); + precache_sound("weapons/sniper.wav"); + precache_sound("weapons/flmfire2.wav"); + precache_sound("weapons/flmgrexp.wav"); + precache_sound("misc/vapeur2.wav"); + precache_sound("weapons/asscan1.wav"); + precache_sound("weapons/asscan2.wav"); + precache_sound("weapons/asscan3.wav"); + precache_sound("weapons/asscan4.wav"); + precache_sound("weapons/railgun.wav"); + precache_sound("weapons/dartgun.wav"); + precache_sound("misc/hitsound.wav"); + precache_sound("misc/hitsoundcrit.wav"); + precache_sound("misc/hitsoundteam.wav"); + precache_sound("weapons/flashgrenburst.wav"); + precache_sound("misc/killsound.wav"); + precache_sound("misc/killsoundteam.wav"); +}; + +float () crandom = { + return (2 * (random() - 0.5)); +}; + +void (float att_delay) Attack_Finished = { + if (self.tfstate & TFSTATE_TRANQUILISED) + att_delay *= 2; + + self.attack_finished = self.client_time + att_delay; + self.last_attack_ctime = self.client_time; +}; + +float* W_ammo_to_p(entity player, AmmoType ammo_type) { + switch (ammo_type) { + case AMMO_SHELLS: return &player.ammo_shells; + case AMMO_CELLS: return &player.ammo_cells; + case AMMO_NAILS: return &player.ammo_nails; + case AMMO_ROCKETS: return &player.ammo_rockets; + default: + case AMMO_NONE: + return __NULL__; + } + return __NULL__; +} + +float W_ConsumeAmmoIfPossible(float ammo_type, float amount) { + float *ammo = W_ammo_to_p(self, ammo_type); + + if (*ammo < amount) + return FALSE; + *ammo -= amount; + return TRUE; +} + + +int () W_FireAxe = { + local vector source; + local vector org; + local vector def; + + makevectors(self.v_angle); + source = self.origin + '0 0 16'; + traceline(source, source + v_forward * 64, FALSE, self); + if (trace_fraction == 1) + return FALSE; + + org = trace_endpos - v_forward * 4; + if (trace_ent.takedamage) { + trace_ent.axhitme = 1; + SpawnBlood(org, 20); + + deathmsg = DMSG_AXE; + TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); + return TRUE; + } else { // hit wall + FO_Sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 3); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); + return TRUE; + } +}; + +int () W_FireKnife = { + local vector source; + local vector org; + local vector def; + + makevectors(self.v_angle); + source = self.origin + '0 0 16'; + traceline(source, source + v_forward * 64, FALSE, self); + + if (trace_fraction == 1) + return FALSE; + + org = trace_endpos - v_forward * 4; + if (trace_ent.takedamage) { + trace_ent.axhitme = 1; + SpawnBlood(org, 20); + + // TODO: Rework knife bloodying + + // Check direction of Attack + makevectors(trace_ent.v_angle); + def = v_right; + makevectors(self.v_angle); + + // Backstab + if (crossproducttf(def, v_forward) > 0) { + deathmsg = DMSG_BACKSTAB; + TF_T_Damage(trace_ent, self, self, 120, + TF_TD_NOTTEAM | TF_TD_IGNOREARMOR, + TF_TD_OTHER); + } else { + deathmsg = DMSG_KNIFE; + TF_T_Damage(trace_ent, self, self, 40, TF_TD_NOTTEAM, + TF_TD_OTHER); + } + + return TRUE; + } else { // hit wall + FO_Sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 3); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); + + return TRUE; + } +}; + +int () W_FireSpanner = { + local vector source; + local vector org; + local float healam; + local entity te; + + makevectors(self.v_angle); + source = self.origin + '0 0 16'; + traceline(source, source + v_forward * 64, FALSE, self); + + if (trace_fraction == 1) + return FALSE; + + org = trace_endpos - v_forward * 4; + + // It may be a trigger that can be activated by the engineer's spanner + if (trace_ent.goal_activation & TFGA_SPANNER) { + if (Activated(trace_ent, self)) { + DoResults(trace_ent, self, TRUE); + + if (trace_ent.classname == "func_button") { + trace_ent.enemy = self; + other = self; + self = trace_ent; + self.dont_do_triggerwork = TRUE; + button_fire(); + self = other; + } + } else if (trace_ent.else_goal != 0) { + te = Findgoal(trace_ent.else_goal); + if (te) + AttemptToActivate(te, self, trace_ent); + } else { + FO_Sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 3); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); + } + return TRUE; + } + + if (trace_ent.takedamage) { + if (trace_ent.classname == "building_dispenser") { + Engineer_UseDispenser(trace_ent); + return TRUE; + + } else if (trace_ent.classname == "building_sentrygun") { + Engineer_UseSentryGun(trace_ent); + return TRUE; + + } else if (trace_ent.classname == "building_sentrygun_base") { + if (trace_ent.oldenemy) + Engineer_UseSentryGun(trace_ent.oldenemy); + return TRUE; + + } else if (trace_ent.classname == "player") { + if ((((trace_ent.team_no == self.team_no) && (self.team_no != 0)) && teamplay) || coop) { + + healam = WEAP_SPANNER_REPAIR; + + if (self.ammo_cells < healam) + healam = self.ammo_cells; + + if (trace_ent.armortype == 0) + return TRUE; + + if ((trace_ent.maxarmor - trace_ent.armorvalue) < (healam * 4)) + healam = ceil((trace_ent.maxarmor - trace_ent.armorvalue) / 4); + + if (healam > 0) { + trace_ent.armorvalue = trace_ent.armorvalue + healam * 4; + + if (trace_ent.armorvalue > trace_ent.maxarmor) + trace_ent.armorvalue = trace_ent.maxarmor; + + self.ammo_cells = self.ammo_cells - healam; + + FO_Sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, ATTN_NORM); + + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 3); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); + } + + return TRUE; + } + + trace_ent.axhitme = 1; + SpawnBlood(org, 20); + deathmsg = DMSG_SPANNER; + TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); + } else { + TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); + } + return TRUE; + } else { + FO_Sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 3); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); + return TRUE; + } +}; + +void RemoveConc(entity player, float remove_timer = TRUE); +entity FindConcTimer(entity player); + +int () W_FireMedikit = { + local vector source; + local vector org; + local float healam; + local entity te; + local entity BioInfection; + + source = self.origin + '0 0 16'; + traceline(source, (source + (v_forward * 64)), 0, self); + + if (trace_fraction == 1) + return FALSE; + + org = trace_endpos - v_forward * 4; + + if (trace_ent.takedamage) { + if (trace_ent.classname == "player") { + + if (((trace_ent.team_no == self.team_no) && + (self.team_no != 0)) || coop) { + + healam = 200; + if (trace_ent.tfstate & TFSTATE_CONC) { + SpawnBlood(org, 20); + + bprint(PRINT_MEDIUM, self.netname, " cured ", + trace_ent.netname, "'s concussion\n"); + + entity te = FindConcTimer(trace_ent); + if (te.team_no != self.team_no) + TF_AddFrags(self, 1); + + RemoveConc(trace_ent); + } + + if (trace_ent.tfstate & TFSTATE_HALLUCINATING) { + + te = find(world, classname, "timer"); + while (((te.owner != trace_ent) || + (te.think != HallucinationTimer)) && + (te != world)) + te = find(te, classname, "timer"); + + if (te != world) { + + trace_ent.tfstate = + trace_ent.tfstate - + (trace_ent.tfstate & TFSTATE_HALLUCINATING); + SpawnBlood(org, 20); + + bprint(PRINT_MEDIUM, self.netname, " healed ", + trace_ent.netname, + " of his hallucinations\n"); + + if (old_grens == TRUE) + stuffcmd(trace_ent, "v_cshift; wait; bf\n"); + + if (te.team_no != self.team_no) + TF_AddFrags(self, 1); + + dremove(te); + } else + dprint + ("Warning: Error in Hallucination Timer logic.\n"); + } + if (trace_ent.tfstate & TFSTATE_TRANQUILISED) { + + te = find(world, classname, "timer"); + while (((te.owner != trace_ent) || + (te.think != TranquiliserTimer)) && + (te != world)) + te = find(te, classname, "timer"); + + if (te != world) { + + trace_ent.tfstate &= ~TFSTATE_TRANQUILISED; + SpawnBlood(org, 20); + + bprint(PRINT_MEDIUM, self.netname, " healed ", + trace_ent.netname, "'s tranquilisation\n"); + + if (te.team_no != self.team_no) + TF_AddFrags(self, 1); + + dremove(te); + } else + dprint + ("Warning: Error in Tranquilisation Timer logic.\n"); + } + if (trace_ent.FlashTime > 0) { + + te = find(world, netname, "flashtimer"); + while (((te.owner != trace_ent) || + (te.classname != "timer")) && (te != world)) + te = find(te, netname, "flashtimer"); + + if (te != world) { + + trace_ent.FlashTime = 0; + SpawnBlood(org, 20); + + bprint(PRINT_MEDIUM, self.netname, " cured ", + trace_ent.netname, "'s blindness\n"); + + if (te.team_no != self.team_no) + TF_AddFrags(self, 1); + + dremove(te); + } else { + dprint("Warning: Error in Flash Timer logic.\n"); + trace_ent.FlashTime = 0; + } + } + if (trace_ent.tfstate & TFSTATE_INFECTED) { + + healam = rint(trace_ent.health / 2); + trace_ent.tfstate = + trace_ent.tfstate - + (trace_ent.tfstate & TFSTATE_INFECTED); + deathmsg = DMSG_MEDIKIT; + T_Damage(trace_ent, self, self, healam); + SpawnBlood(org, 30); + + if (self.classname == "player") { + + bprint(PRINT_MEDIUM, self.netname, " cured ", + trace_ent.netname, "'s infection\n"); + + if (trace_ent.infection_team_no != self.team_no) + TF_AddFrags(self, 1); + } + return TRUE; + } + if (trace_ent.numflames > 0) { + + FO_Sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, + ATTN_NORM); + SetFlameCount(trace_ent, 0); + + if (self.classname == "player") { + bprint(PRINT_MEDIUM, self.netname, " put out ", + trace_ent.netname, "'s fire.\n"); + } + return TRUE; + } + if (trace_ent.health < (trace_ent.max_health + 50)) { + + if (self.ammo_cells >= ceil(PC_MEDIC_MAXAMMO_CELL / 2)) { + healam = trace_ent.max_health - trace_ent.health + 50; + self.ammo_cells = 0; + if (trace_ent.saveme_time <= (time - PC_MEDIC_SAVEME_GRACE)) + self.regen_cooldown = time + PC_MEDIC_CELL_REGEN_CD; + FO_Sound(trace_ent, CHAN_ITEM, "items/r_item2.wav", 1, ATTN_NORM); + } + else { + healam = min(10, ((trace_ent.max_health + 50) - trace_ent.health)); + self.ammo_cells = self.ammo_cells - min(self.ammo_cells, ceil((PC_MEDIC_MAXAMMO_CELL / 20))); + FO_Sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, ATTN_NORM); + } + + Status_Refresh(self); + T_Heal(trace_ent, healam, 1); + + if (trace_ent.health > trace_ent.max_health) { + if (!(trace_ent.items & IT_SUPERHEALTH)) { + trace_ent.items = trace_ent.items | IT_SUPERHEALTH; + newmis = spawn(); + newmis.nextthink = time + 5; + newmis.think = item_megahealth_rot; + newmis.owner = trace_ent; + } + } + } + } else { + // musn't be on their team, so we infect them + trace_ent.axhitme = 1; + SpawnBlood(org, 20); + deathmsg = DMSG_BIOWEAPON_ATT; + T_Damage(trace_ent, self, self, 10); + + if (trace_ent.playerclass == PC_MEDIC) + return TRUE; + if (cb_prematch) + return TRUE; + if (trace_ent.tfstate & TFSTATE_INFECTED) + return TRUE; + + trace_ent.tfstate = trace_ent.tfstate | TFSTATE_INFECTED; + BioInfection = spawn(); + BioInfection.classname = "timer"; + BioInfection.owner = trace_ent; + BioInfection.nextthink = time + 1; + BioInfection.think = BioInfection_Decay; + BioInfection.enemy = self; + LogEventAffliction(self, trace_ent, TFSTATE_INFECTED); + trace_ent.infection_team_no = self.team_no; + } + } else { + TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); + } + return TRUE; + + } else { + // hit wall + FO_Sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, 3); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); + return TRUE; + } +}; + +vector () wall_velocity = +{ + local vector vel; + + vel = normalize(self.velocity); + vel = + normalize(vel + v_up * (random() - 0.5) + + v_right * (random() - 0.5)); + vel = vel + 2 * trace_plane_normal; + vel = vel * 200; + return (vel); +}; + +void (vector org, vector vel) SpawnMeatSpray = { + local entity missile; + + missile = spawn(); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_NOT; + makevectors(self.angles); + missile.velocity = vel; + missile.velocity_z = missile.velocity_z + 250 + 50 * random(); + missile.avelocity = '3000 1000 2000'; + missile.nextthink = time + 1; + missile.think = SUB_Remove; + FO_SetModel(missile, "progs/zom_gib.mdl"); + setsize(missile, '0 0 0', '0 0 0'); + setorigin(missile, org); +}; + +void (vector org, float damage) SpawnBlood = { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_BLOOD); + WriteByte(MSG_MULTICAST, 1); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + multicast(org, MULTICAST_PVS); +}; + +void (float damage) spawn_touchblood = { + local vector vel; + + vel = wall_velocity() * 0.2; + SpawnBlood(self.origin + vel * 0.01, damage); + +}; + +void (vector org, vector vel) SpawnChunk = { + particle(org, vel * 0.02, 0, 10); +}; + +entity multi_ent; +float multi_damage; +vector blood_org; +float blood_count; +vector puff_org; +float puff_count; + +void () ClearMultiDamage = { + multi_ent = world; + multi_damage = 0; + blood_count = 0; + puff_count = 0; +}; + +void () ApplyMultiDamage = { + if (!multi_ent) + return; + + TF_T_Damage(multi_ent, self, self, multi_damage, TF_TD_NOTTEAM, + TF_TD_SHOT); +}; + +void (entity hit, float damage) AddMultiDamage = { + if (!hit) + return; + + if (hit != multi_ent) { + ApplyMultiDamage(); + multi_damage = damage; + multi_ent = hit; + } else + multi_damage = multi_damage + damage; +}; + +void () Multi_Finish = { + if (puff_count) { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_GUNSHOT); + WriteByte(MSG_MULTICAST, puff_count); + WriteCoord(MSG_MULTICAST, puff_org_x); + WriteCoord(MSG_MULTICAST, puff_org_y); + WriteCoord(MSG_MULTICAST, puff_org_z); + multicast(puff_org, MULTICAST_PVS); + } + if (blood_count) { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_BLOOD); + WriteByte(MSG_MULTICAST, blood_count); + WriteCoord(MSG_MULTICAST, blood_org_x); + WriteCoord(MSG_MULTICAST, blood_org_y); + WriteCoord(MSG_MULTICAST, blood_org_z); + multicast(puff_org, MULTICAST_PVS); + } +}; + +void (float damage, vector dir) TraceAttack = { + local vector vel; + local vector org; + + vel = normalize(dir + v_up * crandom() + v_right * crandom()); + vel = vel + 2 * trace_plane_normal; + vel = vel * 200; + org = trace_endpos - dir * 4; + if (trace_ent.takedamage) { + + blood_count = blood_count + 1; + blood_org = org; + AddMultiDamage(trace_ent, damage); + + } else + puff_count = puff_count + 1; +}; + +void (float shotcount, vector dir, vector spread) FireBullets = { + local vector direction; + local vector src; + + makevectors(self.v_angle); + + src = self.origin + v_forward * 10; + src_z = self.absmin_z + self.size_z * 0.7; + + ClearMultiDamage(); + + traceline(src, src + dir * 2048, MOVE_LAGGED, self); + puff_org = trace_endpos - dir * 4; + + while (shotcount > 0) { + + direction = + dir + crandom() * spread_x * v_right + + crandom() * spread_y * v_up; + + traceline(src, (src + (direction * 2048)), MOVE_LAGGED, self); + if (trace_fraction != 1) { + if (FO_CurrentWeapon() != WEAP_ASSAULT_CANNON) + TraceAttack(4, direction); + else + TraceAttack(8, direction); + } + shotcount = shotcount - 1; + + if (FO_CurrentWeapon() == WEAP_ASSAULT_CANNON) { + puff_org = trace_endpos + direction; + Multi_Finish(); + } + } + ApplyMultiDamage(); + if (FO_CurrentWeapon() != WEAP_ASSAULT_CANNON) + Multi_Finish(); +}; + +void () W_FireShotgun = { + local vector dir; + + Pred_Sound(SND_SG); + + KickPlayer(-2, self); + self.ammo_shells = self.ammo_shells - 1; + dir = aim(self, 100000); + deathmsg = DMSG_SHOTGUN; + FireBullets(6, dir, '0.04 0.04 0'); +}; + +void () W_FireSuperShotgun = { + local vector dir; + + if (self.ammo_shells == 1) { + W_FireShotgun(); + return; + } + Pred_Sound(SND_SSG); + + KickPlayer(-4, self); + self.ammo_shells = self.ammo_shells - 2; + dir = aim(self, 100000); + deathmsg = DMSG_SSHOTGUN; + FireBullets(14, dir, '0.14 0.08 0'); +}; + +void (vector direction, float damage) FireSniperBullet = { + local vector src; + + makevectors(self.v_angle); + src = self.origin + v_forward * 10; + src_z = self.absmin_z + self.size_z * 0.7; + ClearMultiDamage(); + + KickPlayer(-3, self); + + traceline(src, src + direction * 4096, MOVE_LAGGED, self); + if (trace_fraction != 1) + TraceAttack(damage, direction); + + FireBullets(1, direction, '0 0 0'); + + ApplyMultiDamage(); +}; + +void () W_FireSniperRifle = { + local vector dir; + local vector src; + local float dam_mult; + local float zdif; + local float use_this; + local float x; + local vector f; + local vector g; + local vector h; + dir = '0 0 0'; + + Pred_Sound(SND_SNIPER_RIFLE); + KickPlayer(-2, self); + self.ammo_shells = self.ammo_shells - 1; + + makevectors(self.v_angle); + src = self.origin + v_forward * 10; + src_z = self.absmin_z + self.size_z * 0.7; + + KickPlayer(-4, self); + + dir = aim(self, 10000); + float range = old_sniperrange ? 3072 : 9192; // Super arbitrary but ok. + traceline(src, src + dir * range, MOVE_LAGGED, self); + + deathmsg = DMSG_SNIPERRIFLE; + dam_mult = 1; + if (trace_fraction < 1 && trace_ent && trace_ent.classname == "player") { + f = trace_endpos - src; + + g_x = trace_endpos_x; + g_y = trace_endpos_y; + g_z = 0; + + h_x = trace_ent.origin_x; + h_y = trace_ent.origin_y; + h_z = 0; + + x = vlen(g - h); + f = (normalize(f) * x) + trace_endpos; + + zdif = f_z - trace_ent.origin_z; + deathmsg = DMSG_SNIPERRIFLE; + + trace_ent.head_shot_vector = '0 0 0'; + if (zdif < 0) { + + dam_mult = 0.5; + trace_ent.leg_damage = trace_ent.leg_damage + 1; + TeamFortress_SetSpeed(trace_ent); + deathmsg = DMSG_SNIPERLEGSHOT; + TF_T_Damage(trace_ent, self, self, self.heat * dam_mult, TF_TD_NOTTEAM, + TF_TD_SHOT); + + if (trace_ent.health > 0) { + sprint(trace_ent, PRINT_LOW, "Leg injury!\n"); + sprint(self, PRINT_MEDIUM, + "Leg shot - that'll slow him down!\n"); + } + return; + + } else if (zdif > 20) { + + dam_mult = 2; + stuffcmd(trace_ent, "bf\n"); + trace_ent.head_shot_vector = + trace_ent.origin - self.origin; + deathmsg = DMSG_SNIPERHEADSHOT; + TF_T_Damage(trace_ent, self, self, (self.heat * dam_mult), + TF_TD_NOTTEAM, TF_TD_SHOT); + + if (trace_ent.health > 0) { + sprint(trace_ent, PRINT_LOW, "Head injury!\n"); + sprint(self, PRINT_MEDIUM, + "Head shot - that's gotta hurt!\n"); + } + + } else { + deathmsg = DMSG_SNIPERRIFLE; + } + } + + if (trace_fraction < 1) { + ClearMultiDamage(); + TraceAttack(self.heat * dam_mult, dir); + ApplyMultiDamage(); + } else { + FireBullets(1, dir, '0 0 0'); + } +}; + +void () W_FireAutoRifle = { + local vector dir; + + FO_Sound(self, CHAN_WEAPON, "weapons/sniper.wav", 1, ATTN_NORM); + + KickPlayer(-1, self); + self.ammo_shells = self.ammo_shells - 1; + makevectors(self.v_angle); + dir = v_forward; + deathmsg = DMSG_AUTORIFLE; + FireSniperBullet(dir, 8); +}; + +void () W_FireAssaultCannon = { + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + + FO_CheckForReload(); + + *ws->clip_fired += 1; + *ws->ammo_remaining -= (ws->wi)->ammo_per_shot; + Status_Refresh(self); + + local vector dir; + + KickPlayer(-4, self); + + dir = aim(self, 100000); + deathmsg = DMSG_ASSAULTCANNON; + + WeapPred_FireAssCan(self.v_angle, 5, '0.04 0.04 0'); + + LogEventAttack(self); +}; + +void (entity pe_flame) CF_ExtinguishFlame = { + local float rn; + + rn = random(); + if (rn < 0.5) { + FO_Sound(pe_flame, CHAN_VOICE, "misc/water1.wav", 1, ATTN_NORM); + } else { + FO_Sound(pe_flame, CHAN_VOICE, "misc/water2.wav", 1, ATTN_NORM); + } + dremove(pe_flame); +}; + +void () s_explode1 =[0, s_explode2] { + if (!server_faithful && self.classname == "flamerflame") { + if (pointcontents(self.origin) < -1) { + CF_ExtinguishFlame(self); + } + } +}; + +void () s_explode2 =[1, s_explode3] { + if (!server_faithful && self.classname == "flamerflame") { + if (pointcontents(self.origin) < -1) { + CF_ExtinguishFlame(self); + } + } +}; + +void () s_explode3 =[2, s_explode4] { + if (!server_faithful && self.classname == "flamerflame") { + if (pointcontents(self.origin) < -1) { + CF_ExtinguishFlame(self); + } + } +}; + +void () s_explode4 =[3, s_explode5] { + if (!server_faithful && self.classname == "flamerflame") { + if (pointcontents(self.origin) < -1) { + CF_ExtinguishFlame(self); + } + } +}; + +void () s_explode5 =[4, s_explode6] { + if (!server_faithful && self.classname == "flamerflame") { + if (pointcontents(self.origin) < -1) { + CF_ExtinguishFlame(self); + } + } +}; + +void () s_explode6 =[5, SUB_Remove] { + if (!server_faithful && self.classname == "flamerflame") { + if (pointcontents(self.origin) < -1) { + CF_ExtinguishFlame(self); + } + } +}; + +void () BecomeExplosion = { + dremove(self); +}; + +static void Antilag_Knock(entity e) { + T_RadiusDamage(e, e.owner, e.heat, __NULL__, __NULL__, e.owner); + dremove(e); +} + +void T_Knock_Antilag() { + Antilag_Knock(self); +} + +float AntilagKnock(entity e, float dmg) { + e.forward_knock = -1; + // Only applies during initial antilag forward + if (e.flags & FL_FORWARD_KNOCK == 0) + return FALSE; + + entity knock_e = spawn(); + + setorigin(knock_e, e.origin); + setsize(knock_e, '0 0 0', '0 0 0'); + knock_e.s_origin = e.s_origin; + knock_e.owner = e.owner; + knock_e.classname = e.classname; + knock_e.oldenemy = other; + knock_e.heat = dmg; + knock_e.filter_ent = e; + + vector diff = e.origin - e.s_origin; + float ttime = vlen(diff) / vlen(e.velocity); + + if (ttime > SERVER_FRAME_DT) { + knock_e.think = T_Knock_Antilag; + knock_e.nextthink = time + ttime; + e.forward_knock = time + ttime; + } else { + Antilag_Knock(knock_e); + } + + return TRUE; +} + +void () T_MissileTouch = { + local float damg; + + if (self.voided) + return; + self.voided = 1; + + damg = 92 + random() * 20; + + deathmsg = self.weapon; + + if (other.health) + TF_T_Damage(other, self, self.owner, damg, TF_TD_NOTTEAM, + TF_TD_OTHER); + + float dmg = (self.owner.classname == "building_sentrygun") ? 150 : 92; + entity ignore_self = AntilagKnock(self, dmg) ? self.owner : __NULL__; + T_RadiusDamage(self, self.owner, dmg, other, ignore_self); + + self.origin = self.origin - 8 * normalize(self.velocity); + + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_EXPLOSION); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PHS); + + dremove_sent(self); +}; + +void W_FireRocket(vector org, vector v_ang, float use_ctime=0) = { + entity proj = FOProj_Create(FPP_ROCKET); + proj.owner = self; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.solid = SOLID_BBOX; + + makevectors(v_ang); + proj.velocity = v_forward * FPP_Get(FPP_ROCKET)->speed; + proj.angles = vectoangles(proj.velocity); + + proj.touch = T_MissileTouch; + proj.voided = 0; + + proj.nextthink = time + 5; + proj.think = SUB_Remove; + + proj.weapon = DMSG_ROCKETL; + proj.classname = "proj_rocket"; + proj.origin = org + v_forward * 8 + '0 0 16'; + + FOProj_Finalize(proj, use_ctime); + + KickPlayer(-2, self); +}; + +void (entity from, float damage) LightningHit = { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_LIGHTNINGBLOOD); + WriteCoord(MSG_MULTICAST, trace_endpos_x); + WriteCoord(MSG_MULTICAST, trace_endpos_y); + WriteCoord(MSG_MULTICAST, trace_endpos_z); + multicast(trace_endpos, MULTICAST_PVS); + + TF_T_Damage(trace_ent, from, from, damage, TF_TD_NOTTEAM, + TF_TD_ELECTRICITY); +}; + +void (vector p1, vector p2, entity from, float damage) LightningDamage = { + local entity e1; + local entity e2; + local vector f; + + f = p2 - p1; + normalize(f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f * 16; + e2 = world; + e1 = world; + + traceline(p1, p2, 0, self); + if (trace_ent.takedamage) { + + LightningHit(from, damage); + if (self.classname == "player") + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + e1 = trace_ent; + traceline(p1 + f, p2 + f, 0, self); + if ((trace_ent != e1) && trace_ent.takedamage) + LightningHit(from, damage); + + e2 = trace_ent; + traceline((p1 - f), (p2 - f), 0, self); + if (((trace_ent != e1) && (trace_ent != e2)) && trace_ent.takedamage) + LightningHit(from, damage); +}; + +void () W_FireLightning = { + local vector org; + local float cells; + + if (self.ammo_cells < 1) { + W_ChangeToBestWeapon(); + return; + } + if (self.waterlevel > 1) { + cells = self.ammo_cells; + self.ammo_cells = 0; + W_UpdateCurrentWeapon(self); + deathmsg = DMSG_LIGHTNING; + T_RadiusDamage(self, self, 35 * cells, world); + return; + } + if (self.t_width < time) { + FO_Sound(self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + self.t_width = time + 0.6; + } + KickPlayer(-2, self); + + self.ammo_cells = self.ammo_cells - 1; + + org = self.origin + '0 0 16'; + traceline(org, org + v_forward * 600, 1, self); + + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_LIGHTNING2); + WriteEntity(MSG_MULTICAST, self); + WriteCoord(MSG_MULTICAST, org_x); + WriteCoord(MSG_MULTICAST, org_y); + WriteCoord(MSG_MULTICAST, org_z); + WriteCoord(MSG_MULTICAST, trace_endpos_x); + WriteCoord(MSG_MULTICAST, trace_endpos_y); + WriteCoord(MSG_MULTICAST, trace_endpos_z); + multicast(org, MULTICAST_PHS); + + LightningDamage(self.origin, trace_endpos + v_forward * 4, self, 30); +}; + +void RenderExplosion(vector origin) { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_EXPLOSION); + WriteCoord(MSG_MULTICAST, origin_x); + WriteCoord(MSG_MULTICAST, origin_y); + WriteCoord(MSG_MULTICAST, origin_z); + multicast(origin, MULTICAST_PHS); +} +void T_RadiusBounce(entity inflictor, entity attacker, float bounce, + entity ignore); + +// REQUIRES: e.gren_type is set and supported by FO_GrenGetExp +void FO_GrenExplode(entity e) { + FO_GrenExp exp; + ASSERTF_TRUE(FO_GrenGetExp(e.fpp.gren_type, exp)); + + deathmsg = exp.deathmsg; + if (exp.type == kRadiusDamage) + T_RadiusDamage(self, self.owner, exp.dmg, world); + else if (exp.type == kRadiusBounce) + T_RadiusBounce(self, self.owner, exp.dmg, world); + + RenderExplosion(e.origin); +} + +void FO_T_GrenExplode() { + FO_GrenExplode(self); + dremove(self); +} + +void () GrenadeExplode = { + local entity te; + float use_antilag = FALSE; + + if (self.voided) + return; + self.voided = 1; + + if (self.owner.has_disconnected != 1) { + deathmsg = self.weapon; + T_RadiusDamage(self, self.owner, 120, world); + } + + if (self.no_active_nail_grens != 0) { + + self.no_active_nail_grens = 0; + self.owner.no_active_nail_grens = + self.owner.no_active_nail_grens - 1; + + te = find(world, classname, "grenade"); + while (te) { + if ((te.owner == self.owner) && (te.no_active_nail_grens > 0)) + te.no_active_nail_grens = te.no_active_nail_grens - 1; + te = find(te, classname, "grenade"); + } + } + + RenderExplosion(self.origin); + dremove(self); +}; + +void () GrenadeTouch = { + if (other == self.owner) + return; + + if (other.takedamage == DAMAGE_AIM) { + GrenadeExplode(); + return; + } + FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +void () ExplodeOldestPipebomb = { + local entity pipe; + local entity oldest = world; + local float numpipes = 0; + local float numpipes_total = 0; + local float detpipe_limit = rint(GetTeamRole(self.team_no)->detpipe_limit); + Team_Role * r = GetTeamRole(self.team_no); + + pipe = find(world, classname, "pipebomb"); + while (pipe != world) { + if (pipe.owner == self) { + numpipes = numpipes + 1; + if (oldest == world || oldest.nextthink > pipe.nextthink) + oldest = pipe; + } + if (pipe.owner.team_no == self.team_no) + numpipes_total = numpipes_total + 1; + pipe = find(pipe, classname, "pipebomb"); + } + + if (((numpipes >= detpipe_limit && detpipe_limit >= 0) + || (numpipes_total >= detpipe_limit_world && detpipe_limit_world >= 0)) + && oldest != world) { + oldest.nextthink = time + 0.5; + } +}; + +void W_FireGrenade(vector org, vector v_ang, float use_ctime=0) { + Pred_Sound(SND_GREN); + KickPlayer(-2, self); + + entity proj = FOProj_Create(FPP_GRENADE); + proj.voided = 0; + proj.owner = self; + proj.movetype = MOVETYPE_BOUNCE; + proj.solid = SOLID_BBOX; + if (FO_CurrentWeapon() == WEAP_GRENADE_LAUNCHER || cb_prematch) { + proj.weapon = 5; + proj.classname = "grenade"; + proj.touch = GrenadeTouch; + proj.nextthink = time + 2.5; + proj.fpp.gren_type = GREN_RED; + } else { + self.pipecooldown = time + pipecooldown_time; + ExplodeOldestPipebomb(); + proj.classname = "pipebomb"; + proj.touch = PipebombTouch; + proj.nextthink = time + 120; + proj.weapon = 11; + proj.team_no = self.team_no; + proj.fpp.gren_type = GREN_PIPE; + } + makevectors(v_ang); + if (self.v_angle_x) { + proj.velocity = (v_forward * 600) + + (200 + shared_crandom(PRNG_WEAP) * 10) * v_up + + (shared_crandom(PRNG_WEAP) * 10 * v_right); + } else { + proj.velocity = aim(self, 10000); + proj.velocity = proj.velocity * 600; + proj.velocity_z = 200; + } + proj.angles = vectoangles(proj.velocity); + proj.think = GrenadeExplode; + setorigin(proj, org); + + FOProj_Finalize(proj, use_ctime); +}; + +void () spike_touch; +void () superspike_touch; + +entity (int fpp_type, vector offset) W_FireNail = { + entity proj = FOProj_Create(fpp_type); + proj.voided = 0; + proj.owner = self; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.solid = SOLID_BBOX; + + proj.angles = vectoangles(aim(self, 1000)); + proj.classname = "spike"; + proj.think = SUB_Remove; + proj.nextthink = time + 6; + setorigin(proj, self.origin + offset + '0 0 16'); + + proj.velocity = aim(self, 1000) * FPP_Get(fpp_type)->speed; + + if (fpp_type == FPP_NAIL) { + proj.weapon = DMSG_NAILGUN; + proj.touch = spike_touch; + } else { // fpp_type == FPP_SUPER_NAIL + proj.weapon = DMSG_SNAILGUN; + proj.touch = superspike_touch; + } + + FOProj_Finalize(proj); + + return proj; +}; + +void (float ox) W_FireSpikes = { + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + FO_WeapInfo* wi = ws->wi; + + makevectors(self.v_angle); + + if (self.ammo_nails < wi->ammo_per_shot) { + W_ChangeToBestWeapon(); + return; + } + + *ws->ammo_remaining -= wi->ammo_per_shot; + if (wi->needs_reload) + *ws->clip_fired += wi->ammo_per_shot; + + if (wi->weapon == WEAP_NAILGUN) + W_FireNail(FPP_NAIL, v_right * ox); + else + W_FireNail(FPP_SUPER_NAIL, '0 0 0'); + + LogEventAttack(self); + KickPlayer(-3, self); + Attack_Finished(wi->attack_time); +}; + +void () t_climb = { + + if (time < self.heat) + return; + + self.heat = time + 0.1; + + if (other.classname != "player") + return; + + if (spurs_enabled == 0) + return; + + if (other.items & IT_KEY1 && spurs_flag == 0 ) + return; + + if (other.items & IT_KEY2 && spurs_flag == 0 ) + return; + + if(other == self.owner || spurs_enabled == 3 || ((self.owner.team_no == other.team_no) && spurs_enabled == 2)) + { + if (self.origin_z > other.origin_z){ + other.velocity_z = spurs_boost; + + } + + if(spurs_consume) + dremove(self); + } + +}; + +void (entity e) ConvertToSpurs = +{ + e.origin = self.origin + ( normalize(self.velocity) * -5); + e.movetype = MOVETYPE_NONE; + e.velocity = '0 0 0'; + e.solid = SOLID_TRIGGER; + e.touch = t_climb; + e.think = SUB_Remove; + e.nextthink = time + spurs_duration; + e.heat = time; +}; + +.float hit_z; + +void () spike_touch = { + if (self.voided) + return; + self.voided = 1; + + if (other.solid == SOLID_TRIGGER) + return; + + if (other.takedamage) { + spawn_touchblood(9); + deathmsg = self.weapon; + if (self.owner.classname == "grenade") { + TF_T_Damage(other, self, self.owner.owner, 9, TF_TD_NOTTEAM, + TF_TD_NAIL); + } else { + TF_T_Damage(other, self, self.owner, ng_damage, TF_TD_NOTTEAM, + TF_TD_NAIL); + } + + dremove_sent(self); + } else { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + if (self.classname == "wizspike") + WriteByte(MSG_MULTICAST, TE_WIZSPIKE); + else if (self.classname == "knightspike") + WriteByte(MSG_MULTICAST, TE_KNIGHTSPIKE); + else + WriteByte(MSG_MULTICAST, TE_SPIKE); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PHS); + + if(other.classname == "worldspawn" && spurs_enabled > 0) { + if(self.owner.playerclass == PC_SCOUT && spurs_scout == 1 || + self.owner.playerclass == PC_SPY && spurs_spy == 1) { + ConvertToSpurs(self); + return; + } + } + + dremove_sent(self); + } +}; + +void () superspike_touch = { + local float ndmg; + + if (self.voided) + return; + self.voided = 1; + + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; + + if (other.takedamage) { + spawn_touchblood(18); + deathmsg = self.weapon; + if (deathmsg == DMSG_GREN_NAIL || deathmsg == DMSG_GREN_BURST) { + ndmg = 40; + } + else { + ndmg = sng_damage; + } + + if (self.owner.classname == "grenade") + TF_T_Damage(other, self, self.owner.owner, ndmg, TF_TD_NOTTEAM, + TF_TD_NAIL); + else + TF_T_Damage(other, self, self.owner, ndmg, TF_TD_NOTTEAM, + TF_TD_NAIL); + } else { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, TE_SUPERSPIKE); + WriteCoord(MSG_MULTICAST, self.origin_x); + WriteCoord(MSG_MULTICAST, self.origin_y); + WriteCoord(MSG_MULTICAST, self.origin_z); + multicast(self.origin, MULTICAST_PHS); + } + dremove_sent(self); +}; + +void W_UpdateWeaponModel(entity pl) { + FO_WeapState ws; + FO_FillWeapState(pl, pl.current_slot, &ws); + FO_WeapInfo* wi = ws.wi; + + if (pl.tfstate & (TFSTATE_RELOADING | TFSTATE_NO_WEAPON) == 0) + pl.weaponmodel = (wi->models)->model; + else + pl.weaponmodel = ""; +} + +void (entity pl) W_UpdateCurrentWeapon = { + entity oldself; + + if ((pl.health <= 0) || IsSlotNull(pl.current_slot)) + return; + + FO_WeapState ws; + FO_FillWeapState(pl, pl.current_slot, &ws); + FO_WeapInfo* wi = ws.wi; + + pl.items &= ~(IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS); + // TODO: Shouldn't this always be union? Carrying prior behavior for now. + pl.items |= (wi->items)->ammo_mask; + pl.weapon = (wi->items)->it_weapon; + + W_UpdateWeaponModel(pl); + pl.weaponframe = 0; + + // refresh engineer build menu when ammo updated + if (pl.menu_input == Menu_Engineer_Input) + Menu_Engineer(pl); + else if (pl.menu_input == Menu_Drop_Input) { + oldself = self; + self = pl; + Menu_Drop(); + self = oldself; + } +}; + +Slot W_BestWeaponSlot() { + FO_WeapState ws; + for (float i = 1; i <= TF_NUM_SLOTS; i++) { + Slot slot = MakeSlot(i); + FO_FillWeapState(self, slot, &ws); + FO_WeapInfo* wi = ws->wi; + + // AC also takes cells to fire. + if (ws->weapon == WEAP_ASSAULT_CANNON && self.ammo_cells < 7) + continue; + + // We don't need to handle out of ammo medi/spanner here because of + // fallback below. + if (ws.weapon != WEAP_NONE && + (wi->ammo_type == AMMO_NONE || (*ws.ammo_remaining >= wi->ammo_per_shot))) + return slot; + } + return SlotMelee; +}; + +void () W_ChangeToBestWeapon = { + W_ChangeWeaponSlot(W_BestWeaponSlot()); +} + +void () player_axe1; +void () player_axeb1; +void () player_spanner1; +void () player_knife1; +void () player_knifeb1; +void () player_shot1; +void () player_nail1; +void () player_light1; +void () player_rocket1; +void () player_autorifle1; + +void () player_asscan_up1; + +void () player_medikit1; +void () player_medikitb1; + +float AssCanTryBeginFire(); + +void () W_Attack = { + FO_WeapState ws; + FO_FillCurrentWeapState(&ws); + FO_WeapInfo* wi = ws->wi; + + if (self.has_disconnected == TRUE) + return; + + if (self.tfstate & TFSTATE_RELOADING || + self.tfstate & TFSTATE_NO_WEAPON) + return; + + if (no_fire_mode) + return; + + // Out of ammo? + if (wi->ammo_type != AMMO_NONE && + *ws->ammo_remaining < wi->ammo_per_shot) { + W_ChangeToBestWeapon(); + return; + } + + // Fired into forced reload? + if (FO_CheckForReload()) + return; + + if ((self.is_undercover || (self.undercover_team != 0)) || + (self.undercover_skin != 0)) + Spy_RemoveDisguise(self); + + makevectors(self.v_angle); + self.show_hostile = time + 1; + + if (ws.weapon == WEAP_AXE) { + Pred_Sound(SND_AXE); + if (shared_prng(PRNG_WEAP) < 0.5) + player_axe1(); + else + player_axeb1(); + } else if (ws.weapon == WEAP_KNIFE) { + Pred_Sound(SND_AXE); + if (shared_prng(PRNG_WEAP) < 0.5) + player_knife1(); + else + player_knifeb1(); + } else if (ws.weapon == WEAP_SPANNER) { + Pred_Sound(SND_AXE); + player_spanner1(); + } else if (ws.weapon == WEAP_SHOTGUN) { + player_shot1(); + W_FireShotgun(); + Status_Refresh(self); + } else if (ws.weapon == WEAP_SUPER_SHOTGUN) { + player_shot1(); + W_FireSuperShotgun(); + Status_Refresh(self); + } else if (ws.weapon == WEAP_NAILGUN) { + player_nail1(); + } else if (ws.weapon == WEAP_SUPER_NAILGUN) { + player_nail1(); + } else if (ws.weapon == WEAP_GRENADE_LAUNCHER) { + player_rocket1(); + self.ammo_rockets = self.ammo_rockets - 1; + W_FireGrenade(self.origin, self.v_angle); + Status_Refresh(self); + } else if (ws.weapon == WEAP_PIPE_LAUNCHER) { + player_rocket1(); + self.ammo_rockets = self.ammo_rockets - 1; + W_FireGrenade(self.origin, self.v_angle); + Status_Refresh(self); + } else if (ws.weapon == WEAP_ROCKET_LAUNCHER) { + player_rocket1(); + self.ammo_rockets = self.ammo_rockets - 1; + W_FireRocket(self.origin, self.v_angle); + Status_Refresh(self); + } else if (ws.weapon == WEAP_LIGHTNING) { + player_light1(); + FO_Sound(self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + } else if (ws.weapon == WEAP_SNIPER_RIFLE) { + if (self.flags & FL_ONGROUND) { + player_shot1(); + W_FireSniperRifle(); + } + } else if (ws.weapon == WEAP_AUTO_RIFLE) { + player_autorifle1(); + W_FireAutoRifle(); + } else if (ws.weapon == WEAP_ASSAULT_CANNON) { + self.immune_to_check = time + 5; + if (!AssCanTryBeginFire()) + W_ChangeToBestWeapon(); + } else if (ws.weapon == WEAP_FLAMETHROWER) { + player_flamethrower1(); + W_FireFlame(); + if (self.waterlevel > 2) + Attack_Finished(1); + else + Attack_Finished(0.15); + } else if (ws.weapon == WEAP_INCENDIARY) { + if (self.ammo_rockets >= 3) { + player_rocket1(); + self.ammo_rockets = self.ammo_rockets - 3; + W_FireIncendiaryCannon(self.origin, self.v_angle); + } else if (time >= self.antispam_incendiary_cannon) { + sprint(self, PRINT_HIGH, "Not enough ammo\n"); + self.antispam_incendiary_cannon = time + 3; + } + } else if (ws.weapon == WEAP_MEDIKIT) { + Pred_Sound(SND_AXE); + if (shared_prng(PRNG_WEAP) < 0.5) + player_medikit1(); + else + player_medikitb1(); + } else if (ws.weapon == WEAP_TRANQ) { + player_shot1(); + W_FireTranq(); + } else if (ws.weapon == WEAP_RAILGUN) { + player_shot1(); + W_FireRailgun(); + } else if (ws.weapon == WEAP_IMPELLER) { + if (!new_balance || time < self.special_next) + return; + + player_shot1(); + W_FireImpeller(); + } + + if (!wi->fire_in_anim) { + Attack_Finished(wi->attack_time); + if (wi->needs_reload) { + *ws->clip_fired += wi->ammo_per_shot; + FO_CheckForReload(); + } + } + + //These weapons have to log each projectile launched/bullet fired + // TODO: probably equivalent to fire_in_anim... + if (ws.weapon != WEAP_ASSAULT_CANNON || ws.weapon != WEAP_NAILGUN || + ws.weapon != WEAP_SUPER_NAILGUN) + LogEventAttack(self); +}; + +float WeaponReady() { + if (self.client_time >= self.attack_finished && + !(self.tfstate & (TFSTATE_RELOADING | TFSTATE_NO_WEAPON))) + return TRUE; + + return FALSE; +} + +void () W_PrintWeaponMessage = { + switch (FO_CurrentWeapon()) + { + case WEAP_GRENADE_LAUNCHER: + sprint(self, PRINT_MEDIUM, "Normal grenade mode\n"); + break; + case WEAP_PIPE_LAUNCHER: + if (cb_prematch) + sprint(self, PRINT_MEDIUM, "Pipebomb mode not available in prematch\n"); + else + sprint(self, PRINT_MEDIUM, "Pipebomb mode\n"); + break; + case WEAP_SNIPER_RIFLE: + sprint(self, PRINT_MEDIUM, "Sniper rifle ready\n"); + break; + case WEAP_AUTO_RIFLE: + sprint(self, PRINT_MEDIUM, "Sniper rifle on fully automatic mode\n"); + break; + } +}; + +float (float weap) W_GetSlot = { + local float cf_pyro_impulses = FO_GetUserSetting(self, "cf_pyro_impulses", "cfpi", "off"); + if (weap == WEAP_ROCKET_LAUNCHER + || weap == WEAP_SUPER_NAILGUN + || weap == WEAP_SNIPER_RIFLE + || (!cf_pyro_impulses && weap == WEAP_INCENDIARY) + || (cf_pyro_impulses && weap == WEAP_FLAMETHROWER) + || weap == WEAP_TRANQ + || weap == WEAP_RAILGUN + || weap == WEAP_ASSAULT_CANNON) { + return 1; + } else if (weap == WEAP_SUPER_SHOTGUN + || weap == WEAP_AUTO_RIFLE + || (!cf_pyro_impulses && weap == WEAP_FLAMETHROWER) + || (cf_pyro_impulses && weap == WEAP_INCENDIARY)) { + return 2; + } else if (weap == WEAP_SHOTGUN) { + if (self.playerclass == PC_SCOUT) + return 2; + else + return 3; + } else if (weap == WEAP_NAILGUN) { + if (self.playerclass == PC_SCOUT) + return 1; + else + return 3; + } + return 0; +}; + +float (float weap) W_OldGetSlot = { + if (weap == WEAP_AXE + || weap == WEAP_KNIFE + || weap == WEAP_MEDIKIT + || weap == WEAP_SPANNER) + return 1; + else if (weap == WEAP_SHOTGUN + || weap == WEAP_SNIPER_RIFLE + || weap == WEAP_TRANQ + || weap == WEAP_RAILGUN) + return 2; + else if (weap == WEAP_SUPER_SHOTGUN + || weap == WEAP_AUTO_RIFLE) + return 3; + else if (weap == WEAP_NAILGUN) + return 4; + else if (weap == WEAP_SUPER_NAILGUN + || weap == WEAP_FLAMETHROWER) + return 5; + else if (weap == WEAP_FLAMETHROWER) + return 6; + else if (weap == WEAP_ROCKET_LAUNCHER + || weap == WEAP_ASSAULT_CANNON + || weap == WEAP_INCENDIARY) + return 7; + else if (weap == WEAP_GRENADE_LAUNCHER) + return 6; + else if (weap == WEAP_PIPE_LAUNCHER) + return 7; + + return 0; +}; + +static void W_AmmoError() { + if (self.noammo == 1) + sprint(self, PRINT_HIGH, "Not enough ammo\n"); + else if (self.noammo == 2 && time >= self.antispam_assault_cannon) { + sprint(self, PRINT_HIGH, "Not enough cells to power the assault cannon\n"); + self.antispam_assault_cannon = time + 3; + } +} + +void W_ChangeWeaponSlot(Slot slot) { + if (self.playerclass == 0) + return; + + if (IsSlotNull(slot)) + slot = SlotMelee; // Make Nulls noticeable... + + FO_WeapState ws; + FO_FillWeapState(self, slot, &ws); + if (ws.weapon == WEAP_NONE || !FO_WeapAvailable(ws.weapon)) + return; + + // queue next weapon if queue is not empty or has changed + if (IsSlotNull(self.queue_slot) || !IsSameSlot(slot, self.queue_slot)) + self.queue_slot = slot; + + // halt if weapon is not ready to be fired + if (!WeaponReady()) + return; + + self.queue_slot = SlotNull; + + // We still allow swapping to medikit/spanner with no ammo. + if (*ws->ammo_remaining == 0 && !IsSlotMelee(slot)) { + W_AmmoError(); + return; + } + + if (IsSameSlot(slot, self.current_slot)) + return; + + self.last_slot = self.current_slot; + self.current_slot = slot; + + W_UpdateCurrentWeapon(self); + W_PrintWeaponMessage(); + Status_Refresh(self); +}; + +void W_ChangeQueuedWeaponIfReady() { + if (!IsSlotNull(self.queue_slot) && WeaponReady()) + W_ChangeWeaponSlot(self.queue_slot); +} + +void W_ChangeWeaponLast() { + // Clearing queued counts as swap to whatever was pre-queued. + if (!IsSlotNull(self.queue_slot)) + self.queue_slot = SlotNull; + else if (!IsSlotNull(self.last_slot)) + W_ChangeWeaponSlot(self.last_slot); +}; + +void W_ChangeWeaponNext() { + Slot slot = IsSlotNull(self.current_slot) ? SlotMelee : self.current_slot; + W_ChangeWeaponSlot(FO_FindPrevNextWeaponSlot(self.playerclass, slot, FALSE)); +} + +void W_ChangeWeaponPrev() { + Slot slot = IsSlotNull(self.current_slot) ? SlotMelee : self.current_slot; + W_ChangeWeaponSlot(FO_FindPrevNextWeaponSlot(self.playerclass, slot, TRUE)); +} + +void () PreMatchImpulses; +void () DeadImpulses; + +void () ImpulseCommands = { + float clearImpulse; // related to impulse_queue + clearImpulse = TRUE; + + if ((self.last_impulse == TF_DETPACK) && self.impulse) + TeamFortress_SetDetpack(self.impulse); + + if (!IsFeigned(self)) { + if (self.impulse == TF_GRENADE_1) + TeamFortress_PrimeGrenade(1, TRUE); + else if (self.impulse == TF_GRENADE_2) + TeamFortress_PrimeGrenade(2, TRUE); + else if (self.impulse == TF_GRENADE_T) + TeamFortress_ThrowGrenade(); + else if (self.impulse == TF_GRENADE_PT_1) + TeamFortress_PrimeThrowGrenade(1); + else if (self.impulse == TF_GRENADE_PT_2) + TeamFortress_PrimeThrowGrenade(2); + } + + if (cb_prematch || cease_fire) { + PreMatchImpulses(); + DeadImpulses(); + self.impulse = 0; + return; + } + + if (self.impulse == TF_SPECIAL_SKILL) + UseSpecialSkill(); + + if (self.impulse == TF_SPECIAL_SKILL_2) + UseSpecialSkill2(); + + if (self.impulse == TF_NEXTTIP) { + self.display_tip = 0; + self.tip_type = 0; + self.tip_time = time; + Status_Refresh(self); + } + + if (self.impulse == TF_WEAPLAST) { + if (self.playerclass == PC_SPY && self.is_undercover == 2) + CF_Spy_DisguiseStop(); + else if (self.playerclass == PC_ENGINEER && self.is_building) { + TeamFortress_EngineerBuildStop(); + W_ChangeWeaponLast(); + } else if (self.playerclass == PC_DEMOMAN && self.is_detpacking) { + TeamFortress_DetpackStop(); + W_ChangeWeaponLast(); + } + } else if (self.impulse == TF_TOGGLEVOTE) + Vote_ToggleMenu(self); + + if (self.tfstate & TFSTATE_NO_WEAPON == 0) { + if (self.impulse == TF_WEAPNEXT) + W_ChangeWeaponNext(); + else if (self.impulse == TF_WEAPPREV) + W_ChangeWeaponPrev(); + else if (self.impulse == TF_RELOAD) + FO_ReloadSlot(self.current_slot, FALSE); + else if (self.impulse == TF_RELOAD_SLOT1) + FO_ReloadSlot(MakeSlot(1), FALSE); + else if (self.impulse == TF_RELOAD_SLOT2) + FO_ReloadSlot(MakeSlot(2), FALSE); + else if (self.impulse == TF_RELOAD_SLOT3) + FO_ReloadSlot(MakeSlot(3), FALSE); + else if (self.impulse == TF_RELOAD_NEXT) + TeamFortress_ReloadNext(); + else if (self.impulse == TF_DETPACK_5) + TeamFortress_SetDetpack(5); + else if (self.impulse == TF_DETPACK_20) + TeamFortress_SetDetpack(20); + else if (self.impulse == TF_DETPACK_50) + TeamFortress_SetDetpack(50); + else if (self.impulse == TF_DROP_AMMO) { + Menu_Drop(); + } else if (self.impulse == TF_PRACSPAWN_PLACE) + TeamFortress_PlacePracticeSpawn(self); + else if (self.impulse == TF_PRACSPAWN_REMOVE) + TeamFortress_RemovePracticeSpawn(self); + } + + if (self.impulse == TF_INVENTORY) + TeamFortress_Inventory(); + else if ((self.playerclass != 0) && (self.impulse == TF_MEDIC_HELPME)) + TeamFortress_SaveMe(); + else if (self.impulse == TF_GRENADE_T) + TeamFortress_ThrowGrenade(); + else if (self.impulse == TF_ID) + CF_Identify(self, 1); + else if (self.impulse == TF_ID_TEAM) + CF_Identify(self, 2); + else if (self.impulse == TF_ID_ENEMY) + CF_Identify(self, 3); + else if (self.impulse == TF_SHOW_IDS) + TeamFortress_ShowIDs(); + else if ((self.playerclass != 0) && (self.impulse == TF_DROPFLAG)) + DropGoalItems(); + else if (self.impulse == TF_DISCARD) + TeamFortress_Discard(); + else if (self.impulse == TF_DISCARD_DROP_AMMO) + TeamFortress_Discard_DropAmmo(); + else if (self.impulse == TF_DASH) + CF_Scout_Dash(); + else if (self.impulse == TF_PB_DETONATE) + clearImpulse = TeamFortress_DetonatePipebombs(0); + else if (self.impulse == TF_DETPACK_STOP) { + TeamFortress_DetpackStop(); + } else if (self.impulse == TF_MEDIC_AURA_TOGGLE) + CF_Medic_AuraToggle(); + else if (self.impulse == TF_HVYWEAP_LOCK_TOGGLE && self.playerclass == PC_HVYWEAP) + FO_LockToggle(); + else if (self.impulse == TF_AIRBLAST && self.playerclass == PC_PYRO) + FO_Airblast(); + else if ((self.impulse == TF_ENGINEER_DETSENTRY) && + (self.playerclass == PC_ENGINEER)) + DestroyBuilding(self, "building_sentrygun"); + else if ((self.impulse == TF_ENGINEER_DETDISP) && + (self.playerclass == PC_ENGINEER)) + DestroyBuilding(self, "building_dispenser"); + else if ((self.impulse == TF_ENGINEER_TOGGLEDISPENSER) && (self.playerclass == PC_ENGINEER)) + FO_Engineer_ToggleDispenser(); + else if ((self.impulse == TF_ENGINEER_TOGGLESENTRY) && (self.playerclass == PC_ENGINEER)) + FO_Engineer_ToggleSentry(); + else if ((self.impulse == 196) && (self.playerclass == PC_ENGINEER)) + DestroyBuilding(self, "building_teleporter_exit"); + else if ((self.impulse == 197) && (self.playerclass == PC_ENGINEER)) + DestroyBuilding(self, "building_teleporter_entrance"); + else if ((self.impulse == TF_SPY_SPY) && (self.playerclass == PC_SPY)) { + if (invis_only) + CF_Spy_Invisible(); + else + Menu_Spy_Skin(); + } else if ((self.impulse == TF_SPY_DIE) && (self.playerclass == PC_SPY)) + FO_Spy_FeignCmd(0); + else if ((self.impulse == TF_SPY_DIE_ON) && (self.playerclass == PC_SPY)) + FO_Spy_FeignOnNextDamage(); + else if ((self.impulse == TF_SPY_SILENT_DIE) && (self.playerclass == PC_SPY)) + FO_Spy_FeignCmd(1); + else if (self.impulse == TF_DISGUISE_RESET && self.playerclass == PC_SPY) { + CF_Spy_ChangeSkin(self, 8, TRUE); + CF_Spy_ChangeColor(self, self.team_no, TRUE); + } else if (self.impulse == TF_DISGUISE_SCOUT && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 1, TRUE); + else if (self.impulse == TF_DISGUISE_SNIPER && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 2, TRUE); + else if (self.impulse == TF_DISGUISE_SOLDIER && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 3, TRUE); + else if (self.impulse == TF_DISGUISE_DEMOMAN && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 4, TRUE); + else if (self.impulse == TF_DISGUISE_MEDIC && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 5, TRUE); + else if (self.impulse == TF_DISGUISE_HWGUY && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 6, TRUE); + else if (self.impulse == TF_DISGUISE_PYRO && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 7, TRUE); + else if (self.impulse == TF_DISGUISE_ENGINEER && self.playerclass == PC_SPY) + CF_Spy_ChangeSkin(self, 9, TRUE); + else if (self.impulse == TF_DISGUISE_BLUE && self.playerclass == PC_SPY) + CF_Spy_ChangeColor(self, 1, TRUE); + else if (self.impulse == TF_DISGUISE_RED && self.playerclass == PC_SPY) + CF_Spy_ChangeColor(self, 2, TRUE); + else if (self.impulse == TF_DISGUISE_YELLOW && self.playerclass == PC_SPY) + CF_Spy_ChangeColor(self, 3, TRUE); + else if (self.impulse == TF_DISGUISE_GREEN && self.playerclass == PC_SPY) + CF_Spy_ChangeColor(self, 4, TRUE); + else if (self.impulse == TF_DISGUISE_ENEMY && self.playerclass == PC_SPY) { + if (number_of_teams > 2) + Menu_Spy_Color(); + else if (self.team_no == 1) + CF_Spy_ChangeColor(self, 2, TRUE); + else + CF_Spy_ChangeColor(self, 1, TRUE); + } else if (self.impulse == TF_DISGUISE_LAST && self.playerclass == PC_SPY) + FO_Spy_DisguiseLast(self, TRUE); + else if (self.impulse == TF_DISGUISE_LAST_SPAWNED && self.playerclass == PC_SPY) + FO_Spy_DisguiseLastSpawned(self, TRUE); + else if ((self.impulse == TF_DEMOMAN_DETPACK) && + (self.playerclass == PC_DEMOMAN)) + TeamFortress_DetpackMenu(); + else if ((self.impulse == TF_ENGINEER_BUILD) && + (self.playerclass == PC_ENGINEER)) + TeamFortress_EngineerBuild(); + else if (self.impulse == FLAG_INFO) { + if (CTF_Map == 1) + TeamFortress_CTF_FlagInfo(); + else + TeamFortress_DisplayDetectionItems(); + } else if (self.impulse == TF_DISPLAYLOCATION) + display_location(); + else { + DeadImpulses(); + } + + if (self.impulse == TF_DETPACK) + self.last_impulse = self.impulse; + + if (clearImpulse) + { + self.impulse = 0; + } + +}; + +void () PreMatchImpulses = { + if (self.impulse == TF_WEAPNEXT) + W_ChangeWeaponNext(); + else if (self.impulse == TF_WEAPPREV) + W_ChangeWeaponPrev(); + + if (self.impulse == TF_INVENTORY) + TeamFortress_Inventory(); + else if (self.impulse == TF_ID) + CF_Identify(self, 0); + else if (self.impulse == FLAG_INFO) { + if (CTF_Map == TRUE) + TeamFortress_CTF_FlagInfo(); + else + TeamFortress_DisplayDetectionItems(); + } else if (self.impulse == TF_DISPLAYLOCATION) + display_location(); + + if (self.impulse == TF_SPECIAL_SKILL) { + ToggleInvincibility(); + } +}; + +void () DeadImpulses = { + if (self.impulse == TF_SHOWTF) + TeamFortress_ShowTF(); + else if (self.impulse == TF_CLASSHELP) + Help_Show(); + else if (self.impulse == TF_SHOWLEGALCLASSES) + TeamFortress_DisplayLegalClasses(); + else if ((self.impulse >= TF_CHANGEPC_SCOUT) && (self.impulse <= TF_CHANGEPC_RANDOM)) { + TeamFortress_ChangeClass(self.impulse - TF_CHANGEPC_SCOUT + 1); + } else if ((self.playerclass != 0) && (self.impulse == TF_CHANGETEAM) + && (deathmatch == 3) && (!cb_prematch)) { + if (loginRequired && !self.login) { + sprint (self, 2, "Login required, please use \"cmd login \" before joining the game\n"); + return; + } + Menu_Team(0); + } else if ((self.playerclass != 0) && (self.impulse == TF_CHANGECLASS) + && (deathmatch == 3) && (!cb_prematch)) { + Menu_Class(0); + } else if (self.is_admin) { + if (self.impulse == TF_ADMIN_CEASEFIRE) + Admin_CeaseFire(); + else if (self.impulse == TF_ADMIN_COUNTPLAYERS) + Admin_CountPlayers(); + else if (self.impulse == TF_ADMIN_CYCLEDEAL) + Admin_CycleDeal(); + else if ((self.impulse == TF_ADMIN_KICK) && (self.admin_mode == 1)) + Admin_DoKick(); + else if ((self.impulse == TF_ADMIN_BAN) && (self.admin_mode == 1)) + Admin_DoBan(); + else if ((self.impulse == TF_ADMIN_NEXT) && (self.admin_mode == 1)) + Admin_CycleDeal(); + else if (self.impulse == TF_ADMIN_LISTIPS) + Admin_ListIPs(); + else if (self.impulse == TF_ADMIN_CLANMODE) + ClanMode(); + else if (self.impulse == TF_ADMIN_QUADMODE) + QuadMode(); + else if (self.impulse == TF_ADMIN_DUELMODE) + DuelMode(); + else if (self.impulse == TF_ADMIN_ADMINMENU) { + self.current_menu_page = 1; + Menu_Admin(); + } + else if (self.impulse == TF_ADMIN_FORCESTARTMATCH) { + StartTimer(); + } + else if (self.impulse == TF_ADMIN_READYSTATUS) + Broadcast_Players_NotReady(); + } + if (self.impulse == TF_HELP_MAP) + TeamFortress_HelpMap(); + else if (self.impulse == TF_NAILGREN_INFO) + TeamFortress_NailGrenInfo(); + else if (self.impulse == TF_STATUS_QUERY) + TeamFortress_StatusQuery(); + else if (self.impulse == TF_TEAM_1 && number_of_teams > 0) + TeamFortress_TeamSet(1); + else if (self.impulse == TF_TEAM_2 && number_of_teams > 1) + TeamFortress_TeamSet(2); + else if (self.impulse == TF_TEAM_3 && number_of_teams > 2) + TeamFortress_TeamSet(3); + else if (self.impulse == TF_TEAM_4 && number_of_teams > 3) + TeamFortress_TeamSet(4); + else if (self.impulse == TF_TEAM_SCORES) + TeamFortress_TeamShowScores(0); + else if (self.impulse == TF_TEAM_CLASSES) + TeamFortress_TeamShowMemberClasses(self); + else if ((self.playerclass == PC_SCOUT) && (self.impulse == TF_SCAN)) + ScannerSwitch(); + else if ((self.playerclass == PC_SCOUT) && + (self.impulse == TF_SCAN_SOUND)) { + sprint(self, PRINT_HIGH, "Scanner sound: "); + if (self.tf_items_flags & 4) { + self.tf_items_flags = self.tf_items_flags - 4; + sprint(self, PRINT_HIGH, "off\n"); + } else { + self.tf_items_flags = self.tf_items_flags | 4; + sprint(self, PRINT_HIGH, "on\n"); + } + } else if ((self.playerclass == PC_SCOUT) && (self.impulse == TF_SCAN_ENEMY)) { + sprint(self, PRINT_HIGH, "Scanning for: "); + if (self.tf_items_flags & NIT_SCANNER_ENEMY) { + self.tf_items_flags = self.tf_items_flags - NIT_SCANNER_ENEMY; + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { + sprint(self, PRINT_HIGH, "Friendlies only\n"); + } else { + sprint(self, PRINT_HIGH, "Nothing\n"); + } + } else { + self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_ENEMY; + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { + sprint(self, PRINT_HIGH, "Friendlies and enemies\n"); + } else { + sprint(self, PRINT_HIGH, "Enemies only\n"); + } + Status_Refresh(self); + } + } else if ((self.playerclass == PC_SCOUT) && (self.impulse == TF_SCAN_FRIENDLY)) { + sprint(self, PRINT_HIGH, "Scanning for: "); + if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { + self.tf_items_flags = self.tf_items_flags - NIT_SCANNER_FRIENDLY; + if (self.tf_items_flags & NIT_SCANNER_ENEMY) { + sprint(self, PRINT_HIGH, "Enemies only\n"); + } else { + sprint(self, PRINT_HIGH, "Nothing\n"); + } + } else { + self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_FRIENDLY; + if (self.tf_items_flags & NIT_SCANNER_ENEMY) { + sprint(self, PRINT_HIGH, "Friendlies and enemies\n"); + } else { + sprint(self, PRINT_HIGH, "Friendlies only\n"); + } + } + Status_Refresh(self); + } + if (self.impulse == TF_PLAYER_READY) { + if (clanbattle == 1) { + if (!cb_prematch) { + sprint (self, 2, "Match already in progress...\n"); + self.impulse = 0; + return; + } + PlayerReady(); + self.impulse = 0; + return; + } + } + else if (self.impulse == TF_PLAYER_NOT_READY) { + if (clanbattle == 1) { + if (!cb_prematch) { + sprint (self, 2, "Match already in progress...\n"); + self.impulse = 0; + return; + } + PlayerNotReady(); + self.impulse = 0; + return; + } + } +}; + +void () ButtonFrame = { + local float changed_buttons = input_buttons ^ self.oldbuttons; + self.oldbuttons = input_buttons; + + local float keydowns = changed_buttons & input_buttons; + local float keyups = changed_buttons & ~input_buttons; + + // +special every frame + if (input_buttons & BUTTON3) { + if (!cb_prematch && !cease_fire) { + switch (self.playerclass) { + case PC_SCOUT: + CF_Scout_Dash(); + break; + case PC_DEMOMAN: + TeamFortress_DetonatePipebombs(0); + break; + case PC_PYRO: + FO_Airblast(); + break; + } + } + } + + // -special every frame + if (!(input_buttons & BUTTON3)) { + if (FO_GetUserSetting(self, "hold_feign", "hf", "off")) { + FO_Spy_Unfeign(); + } + } + + // +special keydown frame + if (keydowns & BUTTON3) { + if (cb_prematch || cease_fire) { + ToggleInvincibility(); + } else { + switch (self.playerclass) { + case PC_MEDIC: + CF_Medic_AuraToggle(); + break; + case PC_HVYWEAP: + FO_LockToggle(); + break; + case PC_SPY: + FO_Spy_ToggleFeign(0); + break; + case PC_ENGINEER: + FO_Engineer_ToggleDispenser(); + break; + } + } + } + + // +special2 every frame + if (input_buttons & BUTTON4) { + switch (self.playerclass) { + case PC_SOLDIER: + self.impulse = TF_SLOT1; + // Intercepted by InterceptRocketJump() + break; + case PC_PYRO: + if (IsUsingCFImpulses()) { + self.impulse = TF_SLOT2; + } else { + self.impulse = TF_SLOT1; + } + // Intercepted by InterceptRocketJump() + break; + } + } + + // +special2 keydown frame + if (keydowns & BUTTON4) { + if (!cb_prematch && !cease_fire) { + switch (self.playerclass) { + case PC_SCOUT: + ScannerSwitch(); + break; + case PC_DEMOMAN: + TeamFortress_ToggleDetpack(5); + break; + case PC_SPY: + FO_Spy_DisguiseLastSpawned(self, TRUE); + break; + case PC_ENGINEER: + FO_Engineer_ToggleSentry(); + break; + } + } + } + + // +special2 keyup frame + if (keyups & BUTTON4) { + switch (self.playerclass) { + case PC_DEMOMAN: + if(FO_GetUserSetting(self, "hold_detpack", "hd", "off")) { + TeamFortress_DetpackStop(); + } + break; + } + } + + local float hold_grens = FO_GetUserSetting(self, "hold_grens", "hg", "off"); + + // +grenade1 keydown frame + if (keydowns & BUTTON5) { + if (hold_grens) { + TeamFortress_PrimeGrenade(1, TRUE); + } else { + TeamFortress_PrimeThrowGrenade(1); + } + } + + // +grenade1 keyup frame + if (keyups & BUTTON5) { + if (hold_grens) { + TeamFortress_ThrowGrenade(); + } + } + + // +grenade2 keydown frame + if (keydowns & BUTTON6) { + if (hold_grens) { + TeamFortress_PrimeGrenade(2, TRUE); + } else { + TeamFortress_PrimeThrowGrenade(2); + } + } + + // +grenade2 keyup frame + if (keyups & BUTTON6) { + if (hold_grens) { + TeamFortress_ThrowGrenade(); + } + } + + // +dropflag every frame + if (input_buttons & BUTTON7) { + DropGoalItems(); + } + + // // +quick1 keydown frame + // if (keydowns & BUTTON8) { + // self.impulse = TF_SLOT1; + // } + + // // +quick2 keydown frame + // if (keydowns & BUTTON9) { + // self.impulse = TF_SLOT2; + // } + + // // +quick3 keydown frame + // if (keydowns & BUTTON10) { + // self.impulse = TF_SLOT3; + // } + + // // +quick4 keydown frame + // if (keydowns & BUTTON11) { + // self.impulse = TF_SLOT4; + // } +} + +void () W_WeaponFrame = { + if (self.login_in_progress) { + CenterPrint(self, "Trying to log in..."); + } + + if (loginRequired && !self.login) { + PrintLoginMessage(); + self.impulse = 0; + return; + } + + if (self.menu_input) { + if (self.impulse > 0 && self.impulse <= 10) { + Menu_Input(self.impulse); + return; + } + } + + if (self.impulse == TF_CLASSMENU && self.playerclass != PC_MEDIC) { + switch (self.playerclass) { + case PC_ENGINEER: Menu_Engineer(self); break; + case PC_SCOUT: Menu_Scout(); break; + case PC_SPY: Menu_Spy(self); break; + case PC_DEMOMAN: TeamFortress_DetpackMenu(); break; + } + + self.impulse = 0; + return; + } + + if (IsFeigned(self) && + (self.impulse != TF_SPECIAL_SKILL) && + (self.impulse != TF_SPECIAL_SKILL_2) && + (self.impulse != TF_MEDIC_HELPME) && + (self.impulse != TF_SPY_DIE) && + (self.impulse != TF_SPY_SILENT_DIE)) + return; + + if (self.impulse == TF_WEAPLAST && self.is_undercover != 2) { + W_ChangeWeaponLast(); + self.impulse = 0; + } + + // slot 1-4 (or 1-7) binds + if (self.impulse >= 1 && self.impulse <= GetMaxWeaponInput() || + self.impulse >= TF_SLOT1 && self.impulse <= TF_SLOT4) { + Slot slot = FO_SlotByInput(self.playerclass, self.impulse); + if (!IsSlotNull(slot)) + W_ChangeWeaponSlot(slot); + self.impulse = 0; + } + W_ChangeQueuedWeaponIfReady(); + + if (self.impulse == TF_CHANGETEAM) { + Menu_Team(0); + self.impulse = 0; + return; + } else if (self.impulse == TF_CHANGECLASS) { + Menu_Class(0); + self.impulse = 0; + return; + } + if (intermission_running) + return; + + // hwguy assault cannon special + if (self.playerclass == PC_HVYWEAP) { + if (cannon_lock && + ((self.impulse == TF_LOCKON) || + (self.impulse == TF_LOCKOFF) || + (self.impulse == TF_HVYWEAP_LOCK_TOGGLE))) { + if (self.impulse == TF_LOCKON) + self.tfstate = self.tfstate | TFSTATE_LOCK; + else if (self.impulse == TF_LOCKOFF) + self.tfstate = self.tfstate - (self.tfstate & TFSTATE_LOCK); + else if (self.impulse == TF_HVYWEAP_LOCK_TOGGLE) + FO_LockToggle(); + Status_Refresh(self); + self.impulse = 0; + return; + } + } + + // whitelist of commands that can be used even when reloading + switch (self.impulse) + { + case TF_SPECIAL_SKILL: + case TF_SPECIAL_SKILL_2: + case TF_PB_DETONATE: + case TF_MEDIC_HELPME: + case TF_DASH: + case TF_DISCARD: + case TF_DROPFLAG: + case TF_CHANGEPC_SCOUT: + case TF_CHANGEPC_SNIPER: + case TF_CHANGEPC_SOLDIER: + case TF_CHANGEPC_DEMOMAN: + case TF_CHANGEPC_MEDIC: + case TF_CHANGEPC_HVYWEAP: + case TF_CHANGEPC_PYRO: + case TF_CHANGEPC_SPY: + case TF_CHANGEPC_ENGINEER: + case TF_CHANGEPC_RANDOM: + case TF_INVENTORY: + case FLAG_INFO: + case TF_SPY_DIE: + case TF_SPY_DIE_ON: + case TF_SPY_DIE_OFF: + case TF_SPY_SILENT_DIE: + case TF_PLAYER_READY: + case TF_PLAYER_NOT_READY: + ImpulseCommands(); + return; + // allows setting detpack while reloading on toggle, defaults to off + case TF_DEMOMAN_DETPACK: + case TF_DETPACK: + case TF_DETPACK_STOP: + case TF_DETPACK_5: + case TF_DETPACK_20: + case TF_DETPACK_50: + if (detpack_when_reloading) { + ImpulseCommands(); + return; + } + } + + // grenade impulses always possible + if ((self.impulse >= TF_GRENADE_1 && self.impulse <= TF_GRENADE_PT_2)) { + ImpulseCommands(); + return; + } + + if (!WeaponReady()) + return; + + if (self.impulse != 0 && self.has_disconnected == 0) + ImpulseCommands(); + if (cease_fire) + return; + + if (self.button0) { + if (FO_CurrentWeapon() == WEAP_SNIPER_RIFLE) { + if (self.tfstate & TFSTATE_AIMING) { + if (self.heat < PC_SNIPER_MAXDAM) { + self.heat = min(self.heat + 3, PC_SNIPER_MAXDAM); + if ((sniperpower) && (self.power_time < (time - 0.3))) { + self.power_time = time; + Status_Refresh(self); + } + } + } else { + local vector tv = self.velocity; + tv_z = 0; + if (vlen(tv) <= 50) { + SniperSight_Create(); + self.heat = 50; + self.tfstate |= TFSTATE_AIMING; + } + } + } else if (FO_CurrentWeapon() == WEAP_ASSAULT_CANNON) { + if (self.flags & FL_ONGROUND || cannon_air) { + SuperDamageSound(); + W_Attack(); + } else if (self.antispam_cannon_air < time) { + sprint(self, PRINT_MEDIUM, "You cannot fire the assault cannon without your feet on the ground...\n"); + self.antispam_cannon_air = time + 3; + } + } else { + SuperDamageSound(); + W_Attack(); + } + } else if (self.tfstate & TFSTATE_AIMING) { + W_Attack(); + self.tfstate &= ~TFSTATE_AIMING; + self.heat = 0; + } +}; + +void () SuperDamageSound = { + if (self.super_damage_finished > time) { + if (self.super_sound < time) { + self.super_sound = time + 1; + FO_Sound(self, CHAN_BODY, "items/damage3.wav", 1, ATTN_NORM); + } + } + return; +}; + +void () ToggleInvincibility = { + if(self.pstate & PSTATE_INVINCIBLE) { + self.items &= ~IT_INVULNERABILITY; + self.invincible_time = 0; + self.invincible_finished = 0; + self.pstate &= ~PSTATE_INVINCIBLE; + self.effects &= ~(EF_RED | EF_DIMLIGHT | EF_BRIGHTLIGHT); + } else { + self.items |= IT_INVULNERABILITY; + self.invincible_time = 1; + self.pstate |= PSTATE_INVINCIBLE; + self.invincible_finished = time + 666; + } +}; + +// TODO: trust the client less, validate more. +// TODO: Could support hitscan (e.g. sg/ssg) +void (int weap, float ctime, vector pos, vector angles) CSEv_Attack_ifvv = { + if (!RewindFlagEnabled(REWIND_SENDEVENT)) + return; + + // Only applies to attacks before the last death. We can't use + // attack_finished here as there's an 0.3s lockout on spawn that obscures. + float lockout = 15*MSEC; // Be a tiny bit conservative + if (ctime >= self.last_death_ctime - lockout || + ctime < self.last_attack_ctime - lockout) + return; + + switch (weap) { + case WEAP_ROCKET_LAUNCHER: + W_FireRocket(pos, angles, ctime); + break; + case WEAP_INCENDIARY: + W_FireIncendiaryCannon(pos, angles, ctime); + break; + case WEAP_GRENADE_LAUNCHER: + W_FireGrenade(pos, angles, ctime); + break; + } +}; diff --git a/world.qc b/ssqc/world.qc similarity index 55% rename from world.qc rename to ssqc/world.qc index ca4997677..f40a74bf0 100644 --- a/world.qc +++ b/ssqc/world.qc @@ -1,4 +1,8 @@ -void () InitBodyQue; +void () TerminateStaleServer; + +string (entity ent, string ps_short, string ps_setting, string ps_default) FO_GetUserSettingString; + +void InitFppProjectiles(); void () main = { dprint("main function\n"); @@ -6,11 +10,6 @@ void () main = { precache_file("gfx.wad"); precache_file("quake.rc"); precache_file("default.cfg"); - precache_file("end1.bin"); - precache_file2("end2.bin"); - precache_file("demo1.dem"); - precache_file("demo2.dem"); - precache_file("demo3.dem"); precache_file("gfx/palette.lmp"); precache_file("gfx/colormap.lmp"); precache_file2("gfx/pop.lmp"); @@ -73,55 +72,35 @@ void () main = { precache_sound("misc/menu3.wav"); precache_sound("ambience/water1.wav"); precache_sound("ambience/wind2.wav"); - precache_file("maps/start.bsp"); - precache_file("maps/e1m1.bsp"); - precache_file("maps/e1m2.bsp"); - precache_file("maps/e1m3.bsp"); - precache_file("maps/e1m4.bsp"); - precache_file("maps/e1m5.bsp"); - precache_file("maps/e1m6.bsp"); - precache_file("maps/e1m7.bsp"); - precache_file("maps/e1m8.bsp"); - precache_file2("gfx/pop.lmp"); - precache_file2("maps/e2m1.bsp"); - precache_file2("maps/e2m2.bsp"); - precache_file2("maps/e2m3.bsp"); - precache_file2("maps/e2m4.bsp"); - precache_file2("maps/e2m5.bsp"); - precache_file2("maps/e2m6.bsp"); - precache_file2("maps/e2m7.bsp"); - precache_file2("maps/e3m1.bsp"); - precache_file2("maps/e3m2.bsp"); - precache_file2("maps/e3m3.bsp"); - precache_file2("maps/e3m4.bsp"); - precache_file2("maps/e3m5.bsp"); - precache_file2("maps/e3m6.bsp"); - precache_file2("maps/e3m7.bsp"); - precache_file2("maps/e4m1.bsp"); - precache_file2("maps/e4m2.bsp"); - precache_file2("maps/e4m3.bsp"); - precache_file2("maps/e4m4.bsp"); - precache_file2("maps/e4m5.bsp"); - precache_file2("maps/e4m6.bsp"); - precache_file2("maps/e4m7.bsp"); - precache_file2("maps/e4m8.bsp"); - precache_file2("maps/end.bsp"); - precache_file2("maps/dm1.bsp"); - precache_file2("maps/dm2.bsp"); - precache_file2("maps/dm3.bsp"); - precache_file2("maps/dm4.bsp"); - precache_file2("maps/dm5.bsp"); - precache_file2("maps/dm6.bsp"); }; entity lastspawn; +// use this for post world being setup (and entities loaded) +void WorldSpawnPost() +{ + loadloc(); + FO_FlashDimension = -1; + + dremove(self); +} + void () worldspawn = { + whichpack("textures/skins/fo_scout.png", 1); // ensures that fo_skins.pk3 is sent + + // Set this variable on connect so the client knows it has access to + // FortressOne aliases. + WriteByte(MSG_INIT, 9/*svc_stufftext*/); + WriteString(MSG_INIT, "set fo_serverscripts 1\n"); + + round_end_time = 0; + + vote_started = -1; local string st; lastspawn = world; number_of_teams = 0; - InitBodyQue(); + if (self.model == "maps/e1m8.bsp") cvar_set("sv_gravity", "100"); else @@ -132,9 +111,16 @@ void () worldspawn = { cvar_set("sv_waterfriction", "1"); if (teamplay == 0) cvar_set("teamplay", "1"); - localcmd("serverinfo cf_rev \""); + + if (infokey(world, "starttime") == string_null) { + string timestamp = strftime(FALSE, "%Y%m%d%H%M%S"); + localcmd(strcat("serverinfo starttime \"", timestamp, "\"\n")); + } + + localcmd("serverinfo fo_rev \""); localcmd(REV); localcmd("\"\n"); + localcmd("serverinfo gametype \"fortressone\"\n"); st = infokey(world, "*sv_gamedir"); if ((st != string_null) && (st != "fortress")) @@ -162,11 +148,13 @@ void () worldspawn = { Vote_Reset(); W_Precache(); + mcp_Precache(); precache_sound("edge/backpack.wav"); precache_sound("demon/dland2.wav"); precache_sound("misc/h2ohit1.wav"); precache_sound("items/itembk2.wav"); precache_sound("player/plyrjmp8.wav"); + precache_sound("dash.wav"); precache_sound("player/land.wav"); precache_sound("player/land2.wav"); precache_sound("player/drown1.wav"); @@ -226,6 +214,7 @@ void () worldspawn = { precache_model("progs/gib3.mdl"); precache_model("progs/s_bubble.spr"); precache_model("progs/s_explod.spr"); + precache_model("progs/s_light.spr"); precache_model("progs/v_axe.mdl"); precache_model("progs/v_tranq.mdl"); precache_model("progs/v_shot.mdl"); @@ -251,7 +240,6 @@ void () worldspawn = { precache_model2("progs/laser.mdl"); precache_sound2("enforcer/enfire.wav"); precache_sound2("enforcer/enfstop.wav"); - precache_sound2("hknight/attack1.wav"); precache_model2("progs/sight.spr"); precache_model2("progs/caltrop.mdl"); precache_model2("progs/cross1.mdl"); @@ -259,6 +247,8 @@ void () worldspawn = { precache_model2("progs/tf_stan.mdl"); precache_model2("progs/v_medi.mdl"); precache_model2("progs/hgren2.mdl"); + precache_model2("progs/blastgren.mdl"); + precache_model2("progs/flashgren.mdl"); precache_model2("progs/biggren.mdl"); precache_model2("progs/flare.mdl"); precache_model2("progs/v_srifle.mdl"); @@ -283,6 +273,8 @@ void () worldspawn = { precache_model2("progs/grenade2.mdl"); precache_model2("progs/v_grap.mdl"); precache_model2("progs/caltrop.mdl"); + precache_model2("progs/deathbag.mdl"); + precache_model2("progs/discard.mdl"); precache_sound("doors/baseuse.wav"); precache_sound("doors/medtry.wav"); precache_sound2("speech/saveme1.wav"); @@ -290,6 +282,41 @@ void () worldspawn = { precache_model2("progs/detpack2.mdl"); precache_model2("progs/grenade3.mdl"); precache_sound("grentimer.wav"); + precache_sound("mcp_grentimer1.wav"); + precache_sound("mcp_grentimer2.wav"); + precache_sound("mcp_grentimer3.wav"); + precache_sound("mcp_grentimer4.wav"); + precache_sound("mcp_grentimer5.wav"); + precache_sound("mcp_grentimer6.wav"); + precache_sound("mcp_grentimer7.wav"); + precache_sound("mcp_grentimer8.wav"); + precache_sound("mcp_grentimer9.wav"); + precache_sound("mcp_grentimer10.wav"); + precache_sound("mcp_grentimer11.wav"); + precache_sound("mcp_grentimer12.wav"); + precache_sound("mcp_grentimer13.wav"); + precache_sound("mcp_grentimer14.wav"); + precache_sound("mcp_grentimer15.wav"); + precache_sound("mcp_grentimer16.wav"); + precache_sound("mcp_grentimer17.wav"); + precache_sound("mcp_grentimer18.wav"); + precache_sound("mcp_grentimer19.wav"); + precache_sound("mcp_grentimer20.wav"); + precache_sound("mcp_grentimer21.wav"); + precache_sound("mcp_grentimer22.wav"); + precache_sound("mcp_grentimer23.wav"); + precache_sound("mcp_grentimer24.wav"); + precache_sound("mcp_grentimer25.wav"); + precache_sound("mcp_grentimer26.wav"); + precache_sound("buttons/switch04.wav"); + precache_sound("boss1/out1.wav"); + precache_sound("items/qpi2.wav"); + precache_sound("shalrath/death.wav"); + precache_sound("shalrath/attack2.wav"); + precache_sound("boss1/sight1.wav"); + precache_sound("misc/secret.wav"); + precache_sound("weapons/airblast.wav"); + precache_sound("weapons/airblastshoot.wav"); lightstyle(0, "m"); lightstyle(1, "mmnmmommommnonmmonqnmmo"); lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); @@ -303,6 +330,147 @@ void () worldspawn = { lightstyle(10, "mmamammmmammamamaaamammma"); lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); lightstyle(63, "a"); + + clientstat(STAT_TEAMNO, EV_FLOAT, team_no); + clientstat(STAT_ALL_TIME, EV_FLOAT, all_time); + clientstat(STAT_FLAGS, EV_FLOAT, stat_flags); + clientstat(STAT_SPAWN_GEN, EV_FLOAT, spawn_gen); + clientstat(STAT_HAS_SENTRY, EV_FLOAT, has_sentry); + + globalstat(STAT_ROUND_END, EV_FLOAT, "round_end_time"); + globalstat(STAT_PAUSED, EV_FLOAT, "cs_paused"); + globalstat(STAT_NOFIRE, EV_FLOAT, "no_fire_mode"); + globalstat(STAT_TEAMNO_ATTACK, EV_FLOAT, "team_no_attack"); + + // login required cache + local float flr = CF_GetSetting("flr", "fologinrequired", "1"); + backend_address = CF_GetSettingRaw("ba", "backend_address", ""); + fo_login_required = (flr && (backend_address != "")); + + entity worldspawnent; + worldspawnent = spawn(); + worldspawnent.think = WorldSpawnPost; + worldspawnent.nextthink = time + .01; + votemode = CF_GetSetting("vm", "votemode", "off"); + vote_total_votes = 0; + vote_style = CF_GetSetting("vs", "vote_style", "1"); + string votemap = FO_GetUserSettingString(world, "vote_map", "votemap", "se2"); + + //No map candidates in map! Add some defaults... + if(!find(world, classname, "map_candidate")) { + InitVoteMaps(); + } + if(votemode) { + if(world.model != strcat("maps/",votemap,".bsp")) { + bprint(PRINT_HIGH, "\bDisabling vote mode as map is not\b ",votemap,"\n"); + //changelevel(votemap); + localcmd ("localinfo votemode 0\n"); + votemode = FALSE; + } else { + } + } else if(!(vote_style & 4)) { + if(world.model == strcat("maps/",votemap,".bsp")) { + bprint(PRINT_HIGH, "\bEnabling vote mode as map is\b ",votemap,"\n"); + localcmd ("localinfo votemode on\n"); + votemode = TRUE; + //changelevel(votemap); + } else { + } + + } + + dimension_send = DMN_NOFLASH; + Predict_Init(); +}; + +#if 0 +// Alternate implementation, drop if simple version is sufficient. +static const float BODYQUEUE_MAX = 32; + +static struct { + entity queue[BODYQUEUE_MAX]; + float num; +} bodies; + + +void ClearBodyQueue() { + for (float i = 0; i < bodies.nun; i++) + remove(bodies.queue[i]); + bodies.num = 0; +} + + +void FilterBodyQueue(entity filter) { + float i = 0, j = 0, num = 0; + + while (j < bodies.num) { + while (j < bodies.num && (bodies[j].heat > time || bodies[j].owner == filter)) + j++; + if (j >= bodies.num) + break; + + if (i != j) { + remove(bodies[i]); + bodies[i] = bodies[j]; + } + num++; + i++; + j++; + } + + bodies.num = num; +} +#endif + +static const float BODYQUEUE_TIMEOUT = 15; +.entity bodyqueue; + +void RemoveDeadBody(entity ent) { + if (ent.bodyqueue == __NULL__) + return; + + remove(ent.bodyqueue); + ent.bodyqueue = __NULL__; +} + +void FilterBodyQueue(float clear) { + int count, i; + entity* bodies = find_list(classname, "bodyqueue", EV_STRING, count); + for (i = 0; i < count; i++) + if (time > bodies[i].heat || clear) + RemoveDeadBody(bodies[i].owner); +} + +void AddToBodyQueue(entity ent) { + if (cb_prematch) + return; + + if (ent.model != "progs/player.mdl") + return; // in particular, filters out suicides + + if (ent.bodyqueue) + RemoveDeadBody(ent); + + entity nb = spawn(); + nb.owner = ent; + nb.heat = time + BODYQUEUE_TIMEOUT; + nb.classname = "bodyqueue"; + + nb.angles = ent.angles; + nb.model = ent.model; + nb.skin = ent.skin; + nb.modelindex = ent.modelindex; + nb.frame = ent.frame; + nb.colormap = ent.colormap; + nb.movetype = ent.movetype; + nb.velocity = ent.velocity; + nb.flags = 0; + setorigin(nb, ent.origin); + setsize(nb, ent.mins, ent.maxs); + + nb.dimension_seen = DMN_NOFLASH; + + ent.bodyqueue = nb; }; void () StartFrame = { @@ -310,42 +478,48 @@ void () StartFrame = { timelimit = cvar("timelimit") * 60; fraglimit = cvar("fraglimit"); deathmatch = cvar("deathmatch"); - framecount = framecount + 1; - Vote_Check(); -}; -entity bodyque_head; + static float next_stale_check; + if (time > next_stale_check) { + TerminateStaleServer(); + next_stale_check = time + 60; + } -void () bodyque = { + static float next_filter_bodyqueue; + if (time > next_filter_bodyqueue) { + FilterBodyQueue(FALSE); + next_filter_bodyqueue = time + 0.5; + } }; -void () InitBodyQue = { - bodyque_head = spawn(); - bodyque_head.classname = "bodyque"; - bodyque_head.owner = spawn(); - bodyque_head.owner.classname = "bodyque"; - bodyque_head.owner.owner = spawn(); - bodyque_head.owner.owner.classname = "bodyque"; - bodyque_head.owner.owner.owner = spawn(); - bodyque_head.owner.owner.owner.classname = "bodyque"; - bodyque_head.owner.owner.owner.owner = bodyque_head; - +// this is used for spawning world objects on map load.. not player, projectiles etc - also disables the engine's conditional spawning based on spawnflags, you're expected to do that part yourself +void(void() fnc)CheckSpawn = { + if(fnc) + { + if (self.dimension_seen != DMN_INVISIBLE) + self.dimension_seen = self.dimension_seen | DMN_NOFLASH | DMN_FLASH; + //bprint(PRINT_HIGH, strcat("spawning: ", self.classname, "\n")); + fnc(); + } + else + { + remove(self); + } }; -void (entity ent) CopyToBodyQue = { - if ((cb_prematch_time + 3) > time) +static const float STALE_TIME_HOURS = 6; +void () TerminateStaleServer = { + if (gettime() < STALE_TIME_HOURS * 3600) return; - bodyque_head.angles = ent.angles; - bodyque_head.model = ent.model; - bodyque_head.skin = ent.skin; - bodyque_head.modelindex = ent.modelindex; - bodyque_head.frame = ent.frame; - bodyque_head.colormap = ent.colormap; - bodyque_head.movetype = ent.movetype; - bodyque_head.velocity = ent.velocity; - bodyque_head.flags = 0; - setorigin(bodyque_head, ent.origin); - setsize(bodyque_head, ent.mins, ent.maxs); - bodyque_head = bodyque_head.owner; -}; + for (entity e = world; (e = find(e, classname, "player")); ) { + if (!e.has_disconnected) + return; //someone is around + } + + Admin_UpdateServer(); + + dprint(sprintf("Terminating empty server with more than %d hours uptime\n", + STALE_TIME_HOURS)); + localcmd("quit\n"); +} diff --git a/tforthlp.qc b/tforthlp.qc deleted file mode 100644 index 909326f50..000000000 --- a/tforthlp.qc +++ /dev/null @@ -1,195 +0,0 @@ -//======================================================== -// Functions handling all the help displaying for TeamFortress. -//======================================================== -// - -// so you can select a team (blindly) while reading the MOTD -void (float inp) MOTD_Input = { - Menu_Team_Input(inp); -}; - -void () TeamFortress_MOTD = { - local string st1; - local string st2; - local string ya; - - if (self.motd == 5) { - sprint(self, PRINT_HIGH, "\nClassic Fortress ", VER, "\n\n"); - - st1 = infokey(world, "motd1"); - if (st1 != string_null) { - st2 = infokey(world, "motd2"); - if (st2 != string_null) { - st1 = strcat(strcat(st1, "\n"), st2); - } - } else { - st1 = "Welcome to Classic Fortress\nby Empezar & hifi\n==================================\ngithub.com/classic-fortress"; - } - - sprint(self, PRINT_HIGH, st1); - sprint(self, PRINT_HIGH, "\n\n\n"); - Status_Menu(self, MOTD_Input, st1); - } - - if (self.motd == 20) { - if ((teamplay != 0) && (self.team_no == 0)) { - stuffcmd(self, "color "); - ya = ftos(0); - stuffcmd(self, ya); - stuffcmd(self, "\n"); - } - - // will also skip motd - if (self.got_aliases == TRUE) { - self.motd = 400; - return; - } - - TeamFortress_Alias("menu", TF_CLASSMENU, 0); - TeamFortress_Alias("slot1", TF_SLOT1, 0); - TeamFortress_Alias("slot2", TF_SLOT2, 0); - TeamFortress_Alias("slot3", TF_SLOT3, 0); - TeamFortress_Alias("slot4", TF_SLOT4, 0); - TeamFortress_AliasString("+slot1", "impulse 20;+attack"); - TeamFortress_AliasString("-slot1", "-attack;impulse 24"); - TeamFortress_AliasString("+slot2", "impulse 21;+attack"); - TeamFortress_AliasString("-slot2", "-attack;impulse 24"); - TeamFortress_AliasString("+slot3", "impulse 22;+attack"); - TeamFortress_AliasString("-slot3", "-attack;impulse 24"); - TeamFortress_AliasString("+slot4", "impulse 23;+attack"); - TeamFortress_AliasString("-slot4", "-attack;impulse 24"); - } else if (self.motd == 30) { - TeamFortress_Alias("changeteam", TF_CHANGETEAM, 0); - TeamFortress_Alias("teamblue", TF_TEAM_1, 0); - TeamFortress_Alias("teamred", TF_TEAM_2, 0); - TeamFortress_Alias("teamyellow", TF_TEAM_3, 0); - TeamFortress_Alias("teamgreen", TF_TEAM_4, 0); - TeamFortress_Alias("changeclass", TF_CHANGECLASS, 0); - TeamFortress_Alias("showclasses", TF_TEAM_CLASSES, 0); - TeamFortress_Alias("legalclasses", TF_SHOWLEGALCLASSES, 0); - TeamFortress_Alias("classhelp", TF_CLASSHELP, 0); - TeamFortress_Alias("scout", TF_CHANGEPC_SCOUT, 0); - TeamFortress_Alias("sniper", TF_CHANGEPC_SNIPER, 0); - TeamFortress_Alias("soldier", TF_CHANGEPC_SOLDIER, 0); - TeamFortress_Alias("demoman", TF_CHANGEPC_DEMOMAN, 0); - TeamFortress_Alias("medic", TF_CHANGEPC_MEDIC, 0); - TeamFortress_Alias("hwguy", TF_CHANGEPC_HVYWEAP, 0); - TeamFortress_Alias("pyro", TF_CHANGEPC_PYRO, 0); - TeamFortress_Alias("spy", TF_CHANGEPC_SPY, 0); - TeamFortress_Alias("engineer", TF_CHANGEPC_ENGINEER, 0); - TeamFortress_Alias("randompc", TF_CHANGEPC_RANDOM, 0); - } else if (self.motd == 40) { - TeamFortress_Alias("query", TF_STATUS_QUERY, 0); - TeamFortress_Alias("inv", TF_INVENTORY, 0); - TeamFortress_Alias("showtf", TF_SHOWTF, 0); - TeamFortress_Alias("showscores", TF_TEAM_SCORES, 0); - TeamFortress_Alias("flaginfo", FLAG_INFO, 0); - TeamFortress_Alias("maphelp", TF_HELP_MAP, 0); - TeamFortress_Alias("showids", TF_SHOW_IDS, 0); - TeamFortress_Alias("id", TF_ID, 0); - TeamFortress_Alias("idteam", TF_ID_TEAM, 0); - TeamFortress_Alias("idenemy", TF_ID_ENEMY, 0); - TeamFortress_Alias("is_aliased", TF_ALIAS_CHECK, 0); - } else if (self.motd == 50) { - TeamFortress_Alias("nexttip", TF_NEXTTIP, 0); - TeamFortress_Alias("votenext", TF_VOTENEXT, 0); - TeamFortress_Alias("votetrick", TF_VOTETRICK, 0); - TeamFortress_Alias("voterace", TF_VOTERACE, 0); - TeamFortress_Alias("forcenext", TF_FORCENEXT, 0); - TeamFortress_Alias("togglevote", TF_TOGGLEVOTE, 0); - TeamFortress_Alias("dropkey", TF_DROPKEY, 0); - TeamFortress_Alias("dropammo", TF_DROP_AMMO, 0); - TeamFortress_Alias("dropflag", TF_DROPFLAG, 0); - TeamFortress_Alias("showloc", TF_DISPLAYLOCATION, 0); - TeamFortress_Alias("special", TF_SPECIAL_SKILL, 0); - TeamFortress_Alias("saveme", TF_MEDIC_HELPME, 0); - TeamFortress_Alias("discard", TF_DISCARD, 0); - } else if (self.motd == 60) { - TeamFortress_Alias("weapnext", TF_WEAPNEXT, 0); - TeamFortress_Alias("weapprev", TF_WEAPPREV, 0); - TeamFortress_Alias("weaplast", TF_WEAPLAST, 0); - TeamFortress_Alias("reload", TF_RELOAD, 0); - TeamFortress_Alias("reload1", TF_RELOAD_SLOT1, 0); - TeamFortress_Alias("reload2", TF_RELOAD_SLOT2, 0); - TeamFortress_Alias("reload3", TF_RELOAD_SLOT3, 0); - TeamFortress_Alias("reloadnext", TF_RELOAD_NEXT, 0); - } else if (self.motd == 70) { - TeamFortress_Alias("primeone", TF_GRENADE_1, 0); - TeamFortress_Alias("primetwo", TF_GRENADE_2, 0); - TeamFortress_Alias("throwgren", TF_GRENADE_T, 0); - TeamFortress_Alias("grenswitch", TF_GRENADE_SWITCH, 0); - TeamFortress_Alias("+gren1", TF_GRENADE_1, 0); - TeamFortress_Alias("+gren2", TF_GRENADE_2, 0); - TeamFortress_Alias("-gren1", TF_GRENADE_T, 0); - TeamFortress_Alias("-gren2", TF_GRENADE_T, 0); - TeamFortress_Alias("gren1", TF_GRENADE_PT_1, 0); - TeamFortress_Alias("gren2", TF_GRENADE_PT_2, 0); - } else if (self.motd == 80) { - TeamFortress_Alias("dash", TF_DASH, 0); - TeamFortress_Alias("autoscan", TF_SCAN, 0); - TeamFortress_Alias("scansound", TF_SCAN_SOUND, 0); - TeamFortress_Alias("scanf", TF_SCAN_FRIENDLY, 0); - TeamFortress_Alias("scane", TF_SCAN_ENEMY, 0); - TeamFortress_Alias("zoomtoggle", TF_ZOOMTOGGLE, 0); - TeamFortress_Alias("zoomin", TF_ZOOMIN, 0); - TeamFortress_Alias("zoomout", TF_ZOOMOUT, 0); - } else if (self.motd == 90) { - TeamFortress_Alias("detpipe", TF_PB_DETONATE, 0); - TeamFortress_Alias("+det5", TF_DETPACK_5, 0); - TeamFortress_Alias("-det5", TF_DETPACK_STOP, 0); - TeamFortress_Alias("+det20", TF_DETPACK_20, 0); - TeamFortress_Alias("-det20", TF_DETPACK_STOP, 0); - TeamFortress_Alias("+det50", TF_DETPACK_50, 0); - TeamFortress_Alias("-det50", TF_DETPACK_STOP, 0); - TeamFortress_Alias("+det255", TF_DETPACK, 255); - TeamFortress_Alias("-det255", TF_DETPACK_STOP, 0); - } else if (self.motd == 100) { - TeamFortress_Alias("aura", TF_MEDIC_AURA_TOGGLE, 0); - TeamFortress_Alias("lock", TF_LOCKON, 0); - TeamFortress_Alias("unlock", TF_LOCKOFF, 0); - TeamFortress_Alias("+lock", TF_LOCKON, 0); - TeamFortress_Alias("-lock", TF_LOCKOFF, 0); - TeamFortress_Alias("disguise", TF_SPY_SPY, 0); - TeamFortress_Alias("feign", TF_SPY_DIE, 0); - TeamFortress_Alias("sfeign", TF_SPY_SILENT_DIE, 0); - } else if (self.motd == 110) { - TeamFortress_Alias("dreset", TF_DISGUISE_RESET, 0); - TeamFortress_Alias("dscout", TF_DISGUISE_SCOUT, 0); - TeamFortress_Alias("dsniper", TF_DISGUISE_SNIPER, 0); - TeamFortress_Alias("dsoldier", TF_DISGUISE_SOLDIER, 0); - TeamFortress_Alias("ddemoman", TF_DISGUISE_DEMOMAN, 0); - TeamFortress_Alias("dmedic", TF_DISGUISE_MEDIC, 0); - TeamFortress_Alias("dhwguy", TF_DISGUISE_HWGUY, 0); - TeamFortress_Alias("dpyro", TF_DISGUISE_PYRO, 0); - } else if (self.motd == 120) { - TeamFortress_Alias("dengineer", TF_DISGUISE_ENGINEER, 0); - TeamFortress_Alias("dblue", TF_DISGUISE_BLUE, 0); - TeamFortress_Alias("dred", TF_DISGUISE_RED, 0); - TeamFortress_Alias("dyellow", TF_DISGUISE_YELLOW, 0); - TeamFortress_Alias("dgreen", TF_DISGUISE_GREEN, 0); - TeamFortress_Alias("denemy", TF_DISGUISE_ENEMY, 0); - TeamFortress_Alias("dlast", TF_DISGUISE_LAST, 0); - TeamFortress_Alias("build", TF_ENGINEER_BUILD, 0); - TeamFortress_Alias("detsentry", TF_ENGINEER_DETSENTRY, 0); - TeamFortress_Alias("detdispenser", TF_ENGINEER_DETDISP, 0); - } else if (self.motd == 130) { - stuffcmd(self, "is_aliased\n"); - } else if (self.motd == 400 && self.team_no == 0) { - Menu_Team(1); - } - - self.motd = self.motd + 1; -}; - -void () TeamFortress_HelpMap = { - local entity te; - - te = find(world, classname, "info_tfdetect"); - if (te) { - if (te.non_team_broadcast != string_null) { - sprint(self, PRINT_HIGH, te.non_team_broadcast); - return; - } - } - sprint(self, PRINT_HIGH, "There is no help for this map\n"); -}; diff --git a/tsoldier.qc b/tsoldier.qc deleted file mode 100644 index b404c9a26..000000000 --- a/tsoldier.qc +++ /dev/null @@ -1,78 +0,0 @@ -//======================================================== -// Functions for the SOLDIER class and associated weaponry -//======================================================== - -void () NailGrenadeExplode; -void () NailGrenadeNailEm; -void () NailGrenadeLaunchNail; - -void () NailGrenadeTouch = { - if (other == self.owner) - return; - - // If the Nail Grenade hits a player, it just bounces off - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); - if (self.velocity == '0 0 0') - self.avelocity = '0 0 0'; -}; - -void () NailGrenadeExplode = { - local entity te; - - self.owner.no_active_nail_grens = self.owner.no_active_nail_grens + 1; - if (self.owner.no_active_nail_grens > 2) { - - te = find(world, classname, "grenade"); - while (te) { - if ((te.owner == self.owner) && (te.no_active_nail_grens == 1)) { - te.weapon = 9; - te.think = GrenadeExplode; - te.nextthink = time + 0.1; - } - te = find(te, classname, "grenade"); - } - } - self.no_active_nail_grens = self.owner.no_active_nail_grens; - self.movetype = MOVETYPE_FLY; - setorigin(self, self.origin + '0 0 32'); - self.avelocity = '0 500 0'; - self.nextthink = time + 0.7; - self.think = NailGrenadeNailEm; -}; - -void () NailGrenadeNailEm = { - self.velocity = '0 0 0'; - self.nextthink = time + 0.1; - self.think = NailGrenadeLaunchNail; - self.playerclass = 0; -}; - -void () NailGrenadeLaunchNail = { - local float i, j; - local float current_yaw; - - i = 0; - while (i < 3) { - - j = (random() + 2) * 5; - current_yaw = anglemod(self.angles_y + j); - self.angles_y = current_yaw; - self.angles_x = 0; - self.angles_z = 0; - makevectors(self.angles); - - deathmsg = DMSG_GREN_NAIL; - launch_spike(self.origin, v_forward); - newmis.touch = superspike_touch; - newmis.weapon = DMSG_GREN_NAIL; - - i = i + 1; - } - self.playerclass = self.playerclass + 1; - self.nextthink = time + 0.1; - - if (self.playerclass > 50) { - self.weapon = DMSG_GREN_NAIL; - self.think = GrenadeExplode; - } -}; diff --git a/version.sh b/version.sh index f92ac3e94..4e67ff79c 100755 --- a/version.sh +++ b/version.sh @@ -4,7 +4,7 @@ if [ -f VERSION ]; then ver=$(cat VERSION) rev=$(sed -e 's/^r\([0-9]\+\).*$/\1/' VERSION) elif [ -x "$(command -v git)" -a -d ".git" ]; then - rev=$(git rev-list HEAD | wc -l | tr -d -c 0-9) + rev=$(git rev-parse --short HEAD) ver="r$rev~$(git rev-parse --short HEAD)" else echo "WARNING: Couldn't detect ezQuake version." >&2 diff --git a/weapons.qc b/weapons.qc deleted file mode 100644 index 4c3dedf59..000000000 --- a/weapons.qc +++ /dev/null @@ -1,2873 +0,0 @@ -void () player_run; - -void () TeamFortress_DisplayDetectionItems; -float (vector veca, vector vecb) crossproduct; - -void (vector org, float damage) SpawnBlood; - -void () SuperDamageSound; - -void () ConcussionGrenadeTimer; -void () OldConcussionGrenadeTimer; - -void (float inp) W_ChangeWeapon; -void () W_ChangeToBestWeapon; -void (float slot) W_WeaponSlot; -void (entity pl) W_WeaponState_Save; -void () W_PrintWeaponMessage; - -void () button_fire; - -void (entity pl, float fr) TF_AddFrags; - -void () DropGoalItems; - -void () TeamFortress_DisplayLegalClasses; - -void () TeamFortress_ShowIDs; -void () TeamFortress_ShowTF; - -void () TeamFortress_AssaultWeapon; -void () TeamFortress_IncendiaryCannon; -void () TeamFortress_FlameThrower; -void (float inp) TeamFortress_PrimeGrenade; -void () TeamFortress_ThrowGrenade; -void (float inp) TeamFortress_PrimeThrowGrenade; -void () TeamFortress_GrenadeSwitch; - -void () PipebombTouch; - -void () SniperSight_Create; - -void (float zoom_to) Sniper_Zoom; - -void () Help_Show; -void () TeamFortress_Inventory; -void () TeamFortress_SaveMe; -void (entity pe_player, float f_type) CF_Identify; -void () TeamFortress_ReloadCurrentWeapon; -void (float slot) TeamFortress_ReloadSlot; -void () TeamFortress_ReloadNext; -void () Sniper_ZoomToggle; -void (float zoom_in) Sniper_ZoomAdjust; -void () TeamFortress_StatusQuery; -void () CF_Scout_Dash; -void () CF_Spy_DisguiseStop; -void () TeamFortress_DetpackMenu; -void () CF_Medic_AuraToggle; -void () TeamFortress_EngineerBuild; -void () TeamFortress_EngineerBuildStop; -void () TeamFortress_Scan; -void () TeamFortress_Discard; -void (float force) TeamFortress_DetonatePipebombs; -void (float timer) TeamFortress_SetDetpack; -void () TeamFortress_DetpackStop; - -void () DropKey; -void () UseSpecialSkill; -void () RemoveFlare; -void () ScannerSwitch; - -void (float all) TeamFortress_TeamShowScores; -void (entity Player) TeamFortress_TeamShowMemberClasses; - -void () Admin_CountPlayers; -void () Admin_CycleDeal; -void () Admin_DoKick; -void () Admin_DoBan; -void () Admin_CeaseFire; -void () Admin_ListIPs; - -void () fadetoblack; -void () fadefromblack; -void () fadetowhite; -void () fadefromwhite; - -void (entity disp) Engineer_UseDispenser; -void (entity gun) Engineer_UseSentryGun; - -void () TeamFortress_MOTD; -void () TeamFortress_HelpMap; - -void () BioInfection_Decay; -void () BioInfection_MonsterDecay; - -void (entity pl, float swap) W_WeaponState_Load; -float (float weap) W_GetSlot; -float () W_BestWeaponSlot; -void () W_FireFlame; -void () W_FireIncendiaryCannon; -void () W_FireTranq; -void () W_FireLaser; - -void () HallucinationTimer; -void () TranquiliserTimer; - -void () TeamFortress_CTF_FlagInfo; - -void () W_Precache = { - precache_sound("weapons/r_exp3.wav"); - precache_sound("weapons/rocket1i.wav"); - precache_sound("weapons/sgun1.wav"); - precache_sound("weapons/guncock.wav"); - precache_sound("weapons/ric1.wav"); - precache_sound("weapons/ric2.wav"); - precache_sound("weapons/ric3.wav"); - precache_sound("weapons/spike2.wav"); - precache_sound("weapons/tink1.wav"); - precache_sound("weapons/grenade.wav"); - precache_sound("weapons/bounce.wav"); - precache_sound("weapons/shotgn2.wav"); - precache_sound("wizard/wattack.wav"); - precache_sound("items/r_item1.wav"); - precache_sound("items/r_item2.wav"); - precache_model("progs/flame2.mdl"); - precache_sound("ambience/fire1.wav"); - precache_sound2("blob/land1.wav"); - precache_model2("progs/v_spike.mdl"); - precache_sound("hknight/hit.wav"); - precache_sound("weapons/detpack.wav"); - precache_sound("weapons/turrset.wav"); - precache_sound("weapons/turrspot.wav"); - precache_sound("weapons/turridle.wav"); - precache_sound("weapons/sniper.wav"); - precache_sound("weapons/flmfire2.wav"); - precache_sound("weapons/flmgrexp.wav"); - precache_sound("misc/vapeur2.wav"); - precache_sound("weapons/asscan1.wav"); - precache_sound("weapons/asscan2.wav"); - precache_sound("weapons/asscan3.wav"); - precache_sound("weapons/asscan4.wav"); - precache_sound("weapons/railgun.wav"); - precache_sound("weapons/dartgun.wav"); -}; - -float () crandom = { - return (2 * (random() - 0.5)); -}; - -void (float att_delay) Attack_Finished = { - if (self.tfstate & 32768) - self.attack_finished = time + att_delay * 2; - else - self.attack_finished = time + att_delay; -}; - -void () W_FireAxe = { - local vector source; - local vector org; - local vector def; - - makevectors(self.v_angle); - source = self.origin + '0 0 16'; - traceline(source, source + v_forward * 64, FALSE, self); - if (trace_fraction == 1) - return; - - org = trace_endpos - v_forward * 4; - if (trace_ent.takedamage) { - - trace_ent.axhitme = 1; - SpawnBlood(org, 20); - - if ((self.playerclass != PC_SPY) || - (trace_ent.classname != "player")) { - deathmsg = DMSG_AXE; - TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, - TF_TD_OTHER); - } else { - self.weaponmode = 1; - self.weaponmodel = "progs/v_knife2.mdl"; - - // Check direction of Attack - makevectors(trace_ent.v_angle); - def = v_right; - makevectors(self.v_angle); - - // Backstab - if (crossproduct(def, v_forward) > 0) { - deathmsg = DMSG_BACKSTAB; - TF_T_Damage(trace_ent, self, self, 120, - TF_TD_NOTTEAM | TF_TD_IGNOREARMOUR, - TF_TD_OTHER); - } else { - deathmsg = DMSG_AXE; - TF_T_Damage(trace_ent, self, self, 40, TF_TD_NOTTEAM, - TF_TD_OTHER); - } - } - } else { // hit wall - sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_GUNSHOT); - WriteByte(MSG_MULTICAST, 3); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - multicast(org, MULTICAST_PVS); - } -}; - -void () W_FireSpanner = { - local vector source; - local vector org; - local float healam; - local entity te; - - makevectors(self.v_angle); - source = self.origin + '0 0 16'; - traceline(source, source + v_forward * 64, FALSE, self); - if (trace_fraction == 1) - return; - - org = trace_endpos - v_forward * 4; - - // It may be a trigger that can be activated by the engineer's spanner - if (trace_ent.goal_activation & TFGA_SPANNER) { - - if (Activated(trace_ent, self)) { - - DoResults(trace_ent, self, TRUE); - - if (trace_ent.classname == "func_button") { - trace_ent.enemy = self; - other = self; - self = trace_ent; - self.dont_do_triggerwork = TRUE; - button_fire(); - self = other; - } - } else if (trace_ent.else_goal != 0) { - - te = Findgoal(trace_ent.else_goal); - if (te) - AttemptToActivate(te, self, trace_ent); - - } else { - sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_GUNSHOT); - WriteByte(MSG_MULTICAST, 3); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - multicast(org, MULTICAST_PVS); - } - return; - } - if (trace_ent.takedamage) { - - if (trace_ent.classname == "building_dispenser") { - - Engineer_UseDispenser(trace_ent); - return; - - } else if (trace_ent.classname == "building_sentrygun") { - - Engineer_UseSentryGun(trace_ent); - return; - - } else if (trace_ent.classname == "building_sentrygun_base") { - - if (trace_ent.oldenemy) - Engineer_UseSentryGun(trace_ent.oldenemy); - return; - - } else if (trace_ent.classname == "player") { - - if ((((trace_ent.team_no == self.team_no) && - (self.team_no != 0)) && teamplay) || coop) { - - healam = WEAP_SPANNER_REPAIR; - if (self.ammo_cells < healam) - healam = self.ammo_cells; - if (trace_ent.armortype == 0) - return; - - if ((trace_ent.maxarmor - trace_ent.armorvalue) < - (healam * 4)) - healam = - ceil((trace_ent.maxarmor - - trace_ent.armorvalue) / 4); - - if (healam > 0) { - - trace_ent.armorvalue = - trace_ent.armorvalue + healam * 4; - if (trace_ent.armorvalue > trace_ent.maxarmor) - trace_ent.armorvalue = trace_ent.maxarmor; - - self.ammo_cells = self.ammo_cells - healam; - - sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, - ATTN_NORM); - - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_GUNSHOT); - WriteByte(MSG_MULTICAST, 3); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - multicast(org, MULTICAST_PVS); - - W_SetCurrentAmmo(self); - } - return; - } - trace_ent.axhitme = 1; - SpawnBlood(org, 20); - deathmsg = DMSG_SPANNER; - TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); - } else { - TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); - } - } else { - sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_GUNSHOT); - WriteByte(MSG_MULTICAST, 3); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - multicast(org, MULTICAST_PVS); - } -}; - -void () W_FireMedikit = { - local vector source; - local vector org; - local float healam; - local entity te; - local entity BioInfection; - - source = self.origin + '0 0 16'; - traceline(source, (source + (v_forward * 64)), 0, self); - if ((trace_fraction == 1)) { - - return; - - } - org = trace_endpos - v_forward * 4; - if (trace_ent.takedamage) { - - if (trace_ent.classname == "player") { - - if (((trace_ent.team_no == self.team_no) && - (self.team_no != 0)) || coop) { - - healam = 200; - te = find(world, classname, "timer"); - while (((te.owner != trace_ent) - || ((te.think != ConcussionGrenadeTimer) - && (te.think != OldConcussionGrenadeTimer))) - && (te != world)) - te = find(te, classname, "timer"); - - if (te != world) { - - if (old_grens == TRUE) - stuffcmd(trace_ent, "v_idlescale 0\nfov 90\n"); - - SpawnBlood(org, 20); - - bprint(PRINT_MEDIUM, self.netname, " cured ", - trace_ent.netname, "'s concussion\n"); - - if (te.team_no != self.team_no) - TF_AddFrags(self, 1); - - dremove(te); - } - if (trace_ent.tfstate & TFSTATE_HALLUCINATING) { - - te = find(world, classname, "timer"); - while (((te.owner != trace_ent) || - (te.think != HallucinationTimer)) && - (te != world)) - te = find(te, classname, "timer"); - - if (te != world) { - - trace_ent.tfstate = - trace_ent.tfstate - - (trace_ent.tfstate & TFSTATE_HALLUCINATING); - SpawnBlood(org, 20); - - bprint(PRINT_MEDIUM, self.netname, " healed ", - trace_ent.netname, - " of his hallucinations\n"); - - if (old_grens == TRUE) - stuffcmd(trace_ent, "v_cshift; wait; bf\n"); - - if (te.team_no != self.team_no) - TF_AddFrags(self, 1); - - dremove(te); - } else - dprint - ("Warning: Error in Hallucination Timer logic.\n"); - } - if (trace_ent.tfstate & TFSTATE_TRANQUILISED) { - - te = find(world, classname, "timer"); - while (((te.owner != trace_ent) || - (te.think != TranquiliserTimer)) && - (te != world)) - te = find(te, classname, "timer"); - - if (te != world) { - - trace_ent.tfstate = - trace_ent.tfstate - - (trace_ent.tfstate & TFSTATE_TRANQUILISED); - TeamFortress_SetSpeed(trace_ent); - SpawnBlood(org, 20); - - bprint(PRINT_MEDIUM, self.netname, " healed ", - trace_ent.netname, "'s tranquilisation\n"); - - if (te.team_no != self.team_no) - TF_AddFrags(self, 1); - - dremove(te); - } else - dprint - ("Warning: Error in Tranquilisation Timer logic.\n"); - } - if (trace_ent.FlashTime > 0) { - - te = find(world, netname, "flashtimer"); - while (((te.owner != trace_ent) || - (te.classname != "timer")) && (te != world)) - te = find(te, netname, "flashtimer"); - - if (te != world) { - - trace_ent.FlashTime = 0; - SpawnBlood(org, 20); - - bprint(PRINT_MEDIUM, self.netname, " cured ", - trace_ent.netname, "'s blindness\n"); - - if (te.team_no != self.team_no) - TF_AddFrags(self, 1); - - dremove(te); - } else { - dprint("Warning: Error in Flash Timer logic.\n"); - trace_ent.FlashTime = 0; - } - } - if (trace_ent.tfstate & TFSTATE_INFECTED) { - - healam = rint(trace_ent.health / 2); - trace_ent.tfstate = - trace_ent.tfstate - - (trace_ent.tfstate & TFSTATE_INFECTED); - deathmsg = DMSG_MEDIKIT; - T_Damage(trace_ent, self, self, healam); - SpawnBlood(org, 30); - - if (self.classname == "player") { - - bprint(PRINT_MEDIUM, self.netname, " cured ", - trace_ent.netname, "'s infection\n"); - - if (trace_ent.infection_team_no != self.team_no) - TF_AddFrags(self, 1); - } - return; - } - if (trace_ent.numflames > 0) { - - sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, - ATTN_NORM); - trace_ent.numflames = 0; - - if (self.classname == "player") { - bprint(PRINT_MEDIUM, self.netname, " put out ", - trace_ent.netname, "'s fire.\n"); - } - return; - } - if (old_medikit) { - - if (healam > 0 && trace_ent.health < trace_ent.max_health) { - - sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, ATTN_NORM); - trace_ent.axhitme = 1; - SpawnBlood(org, 20); - T_Heal(trace_ent, healam, 0); - - } else if (trace_ent.health >= trace_ent.max_health && trace_ent.health < (trace_ent.max_health + 50)) { - - healam = 5; - if (healam > (self.ammo_medikit * 5)) - healam = self.ammo_medikit * 5; - - if (healam > 0) { - sound(trace_ent, CHAN_ITEM, "items/r_item2.wav", 1, ATTN_NORM); - T_Heal(trace_ent, healam, 1); - self.ammo_medikit = self.ammo_medikit - rint(healam / 5); - - if (!(trace_ent.items & IT_SUPERHEALTH)) { - trace_ent.items = trace_ent.items | IT_SUPERHEALTH; - newmis = spawn(); - newmis.nextthink = time + 5; - newmis.think = item_megahealth_rot; - newmis.owner = trace_ent; - } - } - } - - } else if (trace_ent.health < (trace_ent.max_health + 50)) { - - if (self.ammo_cells >= 50) { - healam = trace_ent.max_health - trace_ent.health + 50; - self.ammo_cells = 0; - if (trace_ent.saveme_time <= (time - PC_MEDIC_SAVEME_GRACE)) - self.regen_cooldown = time + PC_MEDIC_CELL_REGEN_CD; - sound(trace_ent, CHAN_ITEM, "items/r_item2.wav", 1, ATTN_NORM); - } - else { - healam = min(10, ((trace_ent.max_health + 50) - trace_ent.health)); - self.ammo_cells = self.ammo_cells - min(self.ammo_cells, 5); - sound(trace_ent, CHAN_WEAPON, "items/r_item1.wav", 1, ATTN_NORM); - } - - if (self.current_weapon == WEAP_MEDIKIT) - self.currentammo = self.ammo_cells; - - Status_Refresh(self); - T_Heal(trace_ent, healam, 1); - - if (trace_ent.health > trace_ent.max_health) { - if (!(trace_ent.items & IT_SUPERHEALTH)) { - trace_ent.items = trace_ent.items | IT_SUPERHEALTH; - newmis = spawn(); - newmis.nextthink = time + 5; - newmis.think = item_megahealth_rot; - newmis.owner = trace_ent; - } - } - } - } else { - // musn't be on their team, so we infect them - trace_ent.axhitme = 1; - SpawnBlood(org, 20); - deathmsg = DMSG_BIOWEAPON_ATT; - T_Damage(trace_ent, self, self, 10); - - if (trace_ent.playerclass == PC_MEDIC) - return; - if (cb_prematch_time > time) - return; - if (trace_ent.tfstate & TFSTATE_INFECTED) - return; - - trace_ent.tfstate = trace_ent.tfstate | TFSTATE_INFECTED; - BioInfection = spawn(); - BioInfection.classname = "timer"; - BioInfection.owner = trace_ent; - BioInfection.nextthink = time + 1; - BioInfection.think = BioInfection_Decay; - BioInfection.enemy = self; - trace_ent.infection_team_no = self.team_no; - } - } else { - TF_T_Damage(trace_ent, self, self, 20, TF_TD_NOTTEAM, TF_TD_OTHER); - } - } else { - // hit wall - sound(self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_GUNSHOT); - WriteByte(MSG_MULTICAST, 3); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - multicast(org, MULTICAST_PVS); - } -}; - -vector()wall_velocity = -{ - local vector vel; - - vel = normalize(self.velocity); - vel = - normalize(vel + v_up * (random() - 0.5) + - v_right * (random() - 0.5)); - vel = vel + 2 * trace_plane_normal; - vel = vel * 200; - return (vel); -}; - -void (vector org, vector vel) SpawnMeatSpray = { - local entity missile; - - missile = spawn(); - missile.owner = self; - missile.movetype = MOVETYPE_BOUNCE; - missile.solid = SOLID_NOT; - makevectors(self.angles); - missile.velocity = vel; - missile.velocity_z = missile.velocity_z + 250 + 50 * random(); - missile.avelocity = '3000 1000 2000'; - missile.nextthink = time + 1; - missile.think = SUB_Remove; - setmodel(missile, "progs/zom_gib.mdl"); - setsize(missile, '0 0 0', '0 0 0'); - setorigin(missile, org); -}; - -void (vector org, float damage) SpawnBlood = { - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_BLOOD); - WriteByte(MSG_MULTICAST, 1); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - multicast(org, MULTICAST_PVS); -}; - -void (float damage) spawn_touchblood = { - local vector vel; - - vel = wall_velocity() * 0.2; - SpawnBlood(self.origin + vel * 0.01, damage); - -}; - -void (vector org, vector vel) SpawnChunk = { - particle(org, vel * 0.02, 0, 10); -}; - -entity multi_ent; -float multi_damage; -vector blood_org; -float blood_count; -vector puff_org; -float puff_count; - -void () ClearMultiDamage = { - multi_ent = world; - multi_damage = 0; - blood_count = 0; - puff_count = 0; -}; - -void () ApplyMultiDamage = { - if (!multi_ent) - return; - - TF_T_Damage(multi_ent, self, self, multi_damage, TF_TD_NOTTEAM, - TF_TD_SHOT); -}; - -void (entity hit, float damage) AddMultiDamage = { - if (!hit) - return; - - if (hit != multi_ent) { - ApplyMultiDamage(); - multi_damage = damage; - multi_ent = hit; - } else - multi_damage = multi_damage + damage; -}; - -void () Multi_Finish = { - if (puff_count) { - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_GUNSHOT); - WriteByte(MSG_MULTICAST, puff_count); - WriteCoord(MSG_MULTICAST, puff_org_x); - WriteCoord(MSG_MULTICAST, puff_org_y); - WriteCoord(MSG_MULTICAST, puff_org_z); - multicast(puff_org, MULTICAST_PVS); - } - if (blood_count) { - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_BLOOD); - WriteByte(MSG_MULTICAST, blood_count); - WriteCoord(MSG_MULTICAST, blood_org_x); - WriteCoord(MSG_MULTICAST, blood_org_y); - WriteCoord(MSG_MULTICAST, blood_org_z); - multicast(puff_org, MULTICAST_PVS); - } -}; - -void (float damage, vector dir) TraceAttack = { - local vector vel; - local vector org; - - vel = normalize(dir + v_up * crandom() + v_right * crandom()); - vel = vel + 2 * trace_plane_normal; - vel = vel * 200; - org = trace_endpos - dir * 4; - if (trace_ent.takedamage) { - - blood_count = blood_count + 1; - blood_org = org; - AddMultiDamage(trace_ent, damage); - - } else - puff_count = puff_count + 1; -}; - -void (float shotcount, vector dir, vector spread) FireBullets = { - local vector direction; - local vector src; - - makevectors(self.v_angle); - - src = self.origin + v_forward * 10; - src_z = self.absmin_z + self.size_z * 0.7; - - ClearMultiDamage(); - - traceline(src, src + dir * 2048, FALSE, self); - puff_org = trace_endpos - dir * 4; - - while (shotcount > 0) { - - direction = - dir + crandom() * spread_x * v_right + - crandom() * spread_y * v_up; - - traceline(src, (src + (direction * 2048)), 0, self); - if (trace_fraction != 1) { - if (self.current_weapon != WEAP_ASSAULT_CANNON) - TraceAttack(4, direction); - else - TraceAttack(8, direction); - } - shotcount = shotcount - 1; - - if (self.current_weapon == WEAP_ASSAULT_CANNON) { - puff_org = trace_endpos + direction; - Multi_Finish(); - } - } - ApplyMultiDamage(); - if (self.current_weapon != WEAP_ASSAULT_CANNON) - Multi_Finish(); -}; - -void () W_FireShotgun = { - local vector dir; - - sound(self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM); - - KickPlayer(-2, self); - self.ammo_shells = self.ammo_shells - 1; - self.currentammo = self.ammo_shells; - dir = aim(self, 100000); - deathmsg = DMSG_SHOTGUN; - FireBullets(6, dir, '0.04 0.04 0'); -}; - -void () W_FireSuperShotgun = { - local vector dir; - - if (self.currentammo == 1) { - W_FireShotgun(); - return; - } - sound(self, CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); - - KickPlayer(-4, self); - self.ammo_shells = self.ammo_shells - 2; - self.currentammo = self.ammo_shells; - dir = aim(self, 100000); - deathmsg = DMSG_SSHOTGUN; - FireBullets(14, dir, '0.14 0.08 0'); -}; - -void (vector direction, float damage) FireSniperBullet = { - local vector src; - - makevectors(self.v_angle); - src = self.origin + v_forward * 10; - src_z = self.absmin_z + self.size_z * 0.7; - ClearMultiDamage(); - - KickPlayer(-3, self); - - traceline(src, src + direction * 4096, FALSE, self); - if (trace_fraction != 1) - TraceAttack(damage, direction); - - FireBullets(1, direction, '0 0 0'); - - ApplyMultiDamage(); -}; - -void () W_FireSniperRifle = { - local vector dir; - local vector src; - local float dam_mult; - local float zdif; - local float use_this; - local float x; - local vector f; - local vector g; - local vector h; - dir = '0 0 0'; - - sound(self, CHAN_WEAPON, "weapons/sniper.wav", 1, ATTN_NORM); - KickPlayer(-2, self); - self.ammo_shells = self.ammo_shells - 1; - self.currentammo = self.ammo_shells; - - makevectors(self.v_angle); - src = self.origin + v_forward * 10; - src_z = self.absmin_z + self.size_z * 0.7; - - use_this = FALSE; - - if (old_sniperrange) - traceline(src, src + dir * 8092, FALSE, self); - else - traceline(src, src + dir * 9192, FALSE, self); - if (trace_fraction != 1) - if (trace_ent != world) - if (trace_ent.classname == "player") - use_this = TRUE; - - KickPlayer(-4, self); - - if (!use_this) { - dir = aim(self, 10000); - if (old_sniperrange) - traceline(src, src + dir * 3072, 0, self); - else - traceline(src, src + dir * 9192, 0, self); - } - deathmsg = DMSG_SNIPERRIFLE; - dam_mult = 1; - if (trace_ent) { - if (trace_ent.classname == "player") { - - f = trace_endpos - src; - - g_x = trace_endpos_x; - g_y = trace_endpos_y; - g_z = 0; - - h_x = trace_ent.origin_x; - h_y = trace_ent.origin_y; - h_z = 0; - - x = vlen(g - h); - f = (normalize(f) * x) + trace_endpos; - - zdif = f_z - trace_ent.origin_z; - deathmsg = DMSG_SNIPERRIFLE; - - trace_ent.head_shot_vector = '0 0 0'; - if (zdif < 0) { - - dam_mult = 0.5; - trace_ent.leg_damage = trace_ent.leg_damage + 1; - TeamFortress_SetSpeed(trace_ent); - deathmsg = DMSG_SNIPERLEGSHOT; - TF_T_Damage(trace_ent, self, self, self.heat * dam_mult, 2, - 1); - - if (trace_ent.health > 0) { - sprint(trace_ent, PRINT_LOW, "Leg injury!\n"); - sprint(self, PRINT_MEDIUM, - "Leg shot - that'll slow him down!\n"); - } - return; - - } else if (zdif > 20) { - - dam_mult = 2; - stuffcmd(trace_ent, "bf\n"); - trace_ent.head_shot_vector = - trace_ent.origin - self.origin; - deathmsg = DMSG_SNIPERHEADSHOT; - TF_T_Damage(trace_ent, self, self, (self.heat * dam_mult), - 2, 1); - - if (trace_ent.health > 0) { - sprint(trace_ent, PRINT_LOW, "Head injury!\n"); - sprint(self, PRINT_MEDIUM, - "Head shot - that's gotta hurt!\n"); - } - return; - - } else - deathmsg = DMSG_SNIPERRIFLE; - } - } - ClearMultiDamage(); - - if (trace_fraction != 1) - TraceAttack(self.heat * dam_mult, dir); - - FireBullets(1, dir, '0 0 0'); - - ApplyMultiDamage(); -}; - -void () W_FireAutoRifle = { - local vector dir; - - sound(self, CHAN_WEAPON, "weapons/sniper.wav", 1, ATTN_NORM); - - KickPlayer(-1, self); - self.ammo_shells = self.ammo_shells - 1; - self.currentammo = self.ammo_shells; - makevectors(self.v_angle); - dir = v_forward; - deathmsg = DMSG_AUTORIFLE; - FireSniperBullet(dir, 8); -}; - -void () W_FireAssaultCannon = { - local vector dir; - - KickPlayer(-4, self); - self.ammo_shells = self.ammo_shells - 1; - self.currentammo = self.ammo_shells; - dir = aim(self, 100000); - deathmsg = DMSG_ASSAULTCANNON; - - if (cannon_accuracy == 1) - FireBullets(5, dir, '0 0 0'); - else if (cannon_accuracy == 2) - FireBullets(5, dir, '0.1 0.1 0'); - else if (vlen(self.velocity) < 50) - FireBullets(5, dir, '0.075 0.075 0'); - else - FireBullets(5, dir, '0.15 0.15 0'); -}; - -void (entity pe_flame) CF_ExtinguishFlame = { - local float rn; - - rn = random(); - if (rn < 0.5) { - sound(pe_flame, CHAN_VOICE, "misc/water1.wav", 1, ATTN_NORM); - } else { - sound(pe_flame, CHAN_VOICE, "misc/water2.wav", 1, ATTN_NORM); - } - dremove(pe_flame); -}; - -void () s_explode1 =[0, s_explode2] { - if (!server_faithful && self.classname == "flamerflame") { - if (pointcontents(self.origin) < -1) { - CF_ExtinguishFlame(self); - } - } -}; - -void () s_explode2 =[1, s_explode3] { - if (!server_faithful && self.classname == "flamerflame") { - if (pointcontents(self.origin) < -1) { - CF_ExtinguishFlame(self); - } - } -}; - -void () s_explode3 =[2, s_explode4] { - if (!server_faithful && self.classname == "flamerflame") { - if (pointcontents(self.origin) < -1) { - CF_ExtinguishFlame(self); - } - } -}; - -void () s_explode4 =[3, s_explode5] { - if (!server_faithful && self.classname == "flamerflame") { - if (pointcontents(self.origin) < -1) { - CF_ExtinguishFlame(self); - } - } -}; - -void () s_explode5 =[4, s_explode6] { - if (!server_faithful && self.classname == "flamerflame") { - if (pointcontents(self.origin) < -1) { - CF_ExtinguishFlame(self); - } - } -}; - -void () s_explode6 =[5, SUB_Remove] { - if (!server_faithful && self.classname == "flamerflame") { - if (pointcontents(self.origin) < -1) { - CF_ExtinguishFlame(self); - } - } -}; - -void () BecomeExplosion = { - dremove(self); -}; - -void () T_MissileTouch = { - local float damg; - - if (self.voided) - return; - self.voided = 1; - - if (pointcontents(self.origin) == CONTENT_SKY) { - dremove(self); - return; - } - damg = 92 + random() * 20; - - deathmsg = self.weapon; - - if (other.health) - TF_T_Damage(other, self, self.owner, damg, TF_TD_NOTTEAM, - TF_TD_OTHER); - - if (self.owner.classname == "building_sentrygun") - T_RadiusDamage(self, self.owner, 150, other); - else - T_RadiusDamage(self, self.owner, 92, other); - - self.origin = self.origin - 8 * normalize(self.velocity); - - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); - - dremove(self); -}; - -void () W_FireRocket = { - self.ammo_rockets = self.ammo_rockets - 1; - self.currentammo = self.ammo_rockets; - sound(self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); - KickPlayer(-2, self); - - newmis = spawn(); - newmis.owner = self; - newmis.movetype = MOVETYPE_FLYMISSILE; - newmis.solid = SOLID_BBOX; - - makevectors(self.v_angle); - newmis.velocity = v_forward; - newmis.velocity = newmis.velocity * 900; - newmis.angles = vectoangles(newmis.velocity); - - newmis.touch = T_MissileTouch; - newmis.voided = 0; - - newmis.nextthink = time + 5; - newmis.think = SUB_Remove; - - newmis.weapon = DMSG_ROCKETL; - setmodel(newmis, "progs/missile.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin + v_forward * 8 + '0 0 16'); -}; - -void (entity from, float damage) LightningHit = { - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_LIGHTNINGBLOOD); - WriteCoord(MSG_MULTICAST, trace_endpos_x); - WriteCoord(MSG_MULTICAST, trace_endpos_y); - WriteCoord(MSG_MULTICAST, trace_endpos_z); - multicast(trace_endpos, MULTICAST_PVS); - - TF_T_Damage(trace_ent, from, from, damage, TF_TD_NOTTEAM, - TF_TD_ELECTRICITY); -}; - -void (vector p1, vector p2, entity from, float damage) LightningDamage = { - local entity e1; - local entity e2; - local vector f; - - f = p2 - p1; - normalize(f); - f_x = 0 - f_y; - f_y = f_x; - f_z = 0; - f = f * 16; - e2 = world; - e1 = world; - - traceline(p1, p2, 0, self); - if (trace_ent.takedamage) { - - LightningHit(from, damage); - if (self.classname == "player") - if (other.classname == "player") - trace_ent.velocity_z = trace_ent.velocity_z + 400; - } - e1 = trace_ent; - traceline(p1 + f, p2 + f, 0, self); - if ((trace_ent != e1) && trace_ent.takedamage) - LightningHit(from, damage); - - e2 = trace_ent; - traceline((p1 - f), (p2 - f), 0, self); - if (((trace_ent != e1) && (trace_ent != e2)) && trace_ent.takedamage) - LightningHit(from, damage); -}; - -void () W_FireLightning = { - local vector org; - local float cells; - - if (self.ammo_cells < 1) { - W_ChangeToBestWeapon(); - return; - } - if (self.waterlevel > 1) { - cells = self.ammo_cells; - self.ammo_cells = 0; - W_SetCurrentAmmo(self); - deathmsg = DMSG_LIGHTNING; - T_RadiusDamage(self, self, 35 * cells, world); - return; - } - if (self.t_width < time) { - sound(self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); - self.t_width = time + 0.6; - } - KickPlayer(-2, self); - - self.ammo_cells = self.ammo_cells - 1; - self.currentammo = self.ammo_cells; - - org = self.origin + '0 0 16'; - traceline(org, org + v_forward * 600, 1, self); - - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_LIGHTNING2); - WriteEntity(MSG_MULTICAST, self); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); - WriteCoord(MSG_MULTICAST, trace_endpos_x); - WriteCoord(MSG_MULTICAST, trace_endpos_y); - WriteCoord(MSG_MULTICAST, trace_endpos_z); - multicast(org, MULTICAST_PHS); - - LightningDamage(self.origin, trace_endpos + v_forward * 4, self, 30); -}; - -void () GrenadeExplode = { - local entity te; - - if (self.voided) - return; - self.voided = 1; - - if (self.classname == "pipebomb") { - if (!(self.flags & 512)) - self.weapon = 40; - } - if (self.owner.has_disconnected != 1) { - deathmsg = self.weapon; - T_RadiusDamage(self, self.owner, 120, world); - } - if (self.no_active_nail_grens != 0) { - - self.no_active_nail_grens = 0; - self.owner.no_active_nail_grens = - self.owner.no_active_nail_grens - 1; - - te = find(world, classname, "grenade"); - while (te) { - if ((te.owner == self.owner) && (te.no_active_nail_grens > 0)) - te.no_active_nail_grens = te.no_active_nail_grens - 1; - te = find(te, classname, "grenade"); - } - } - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_EXPLOSION); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); - - BecomeExplosion(); -}; - -void () GrenadeTouch = { - if (other == self.owner) - return; - - if (other.takedamage == DAMAGE_AIM) { - GrenadeExplode(); - return; - } - sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); - - if (self.velocity == '0 0 0') - self.avelocity = '0 0 0'; -}; - -void () ExplodeOldestPipebomb = { - local entity pipe; - local entity oldest = world; - local float numpipes = 0; - local float numpipes_total = 0; - - pipe = find(world, classname, "pipebomb"); - while (pipe != world) { - if (pipe.owner == self) { - numpipes = numpipes + 1; - if (oldest == world || oldest.nextthink > pipe.nextthink) - oldest = pipe; - } - if (pipe.owner.team_no == self.team_no) - numpipes_total = numpipes_total + 1; - pipe = find(pipe, classname, "pipebomb"); - } - - if (((numpipes >= detpipe_limit && detpipe_limit >= 0) - || (numpipes_total >= detpipe_limit_world && detpipe_limit_world >= 0)) - && oldest != world) { - oldest.nextthink = time + 0.5; - } -}; - -void () W_FireGrenade = { - self.ammo_rockets = self.ammo_rockets - 1; - self.currentammo = self.ammo_rockets; - sound(self, 1, "weapons/grenade.wav", 1, 1); - KickPlayer(-2, self); - newmis = spawn(); - newmis.voided = 0; - newmis.owner = self; - newmis.movetype = 10; - newmis.solid = 2; - if ((self.weaponmode == 0) || (cb_prematch_time > time)) { - newmis.weapon = 5; - newmis.classname = "grenade"; - newmis.skin = 1; - newmis.touch = GrenadeTouch; - newmis.nextthink = time + 2.5; - } else { - if (old_pipecooldown) - self.pipecooldown = time + 0.8; - else - self.pipecooldown = time + 0.5; - ExplodeOldestPipebomb(); - newmis.classname = "pipebomb"; - newmis.skin = 2; - newmis.touch = PipebombTouch; - newmis.nextthink = time + 120; - newmis.weapon = 11; - newmis.team_no = self.team_no; - } - makevectors(self.v_angle); - if (self.v_angle_x) { - newmis.velocity = - (((v_forward * 600) + (v_up * 200)) + - ((crandom() * v_right) * 10)) + ((crandom() * v_up) * 10); - } else { - newmis.velocity = aim(self, 10000); - newmis.velocity = newmis.velocity * 600; - newmis.velocity_z = 200; - } - newmis.avelocity = '300 300 300'; - newmis.angles = vectoangles(newmis.velocity); - newmis.think = GrenadeExplode; - setmodel(newmis, "progs/grenade2.mdl"); - setsize(newmis, '0 0 0', '0 0 0'); - setorigin(newmis, self.origin); -}; - -void () spike_touch; -void () superspike_touch; - -void (vector org, vector dir) launch_spike = { - newmis = spawn(); - newmis.voided = 0; - newmis.owner = self; - newmis.movetype = MOVETYPE_FLYMISSILE; - newmis.solid = SOLID_BBOX; - - newmis.angles = vectoangles(dir); - newmis.touch = spike_touch; - newmis.weapon = DMSG_NAILGUN; - newmis.classname = "spike"; - newmis.think = SUB_Remove; - newmis.nextthink = time + 6; - setmodel(newmis, "progs/spike.mdl"); - setsize(newmis, VEC_ORIGIN, VEC_ORIGIN); - setorigin(newmis, org); - - newmis.velocity = dir * 1000; -}; - -void () W_FireSuperSpikes = { - local vector dir; - - sound(self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); - - self.ammo_nails = self.ammo_nails - 4; - self.currentammo = self.ammo_nails; - dir = aim(self, 1000); - launch_spike(self.origin + '0 0 16', dir); - newmis.touch = superspike_touch; - newmis.weapon = DMSG_SNAILGUN; - setmodel(newmis, "progs/s_spike.mdl"); - setsize(newmis, VEC_ORIGIN, VEC_ORIGIN); - KickPlayer(-3, self); -}; - -void (float ox) W_FireSpikes = { - local vector dir; - - makevectors(self.v_angle); - if ((self.ammo_nails >= 4) && - (self.current_weapon == WEAP_SUPER_NAILGUN)) { - W_FireSuperSpikes(); - return; - } - if (self.ammo_nails < 1) { - W_ChangeToBestWeapon(); - return; - } - sound(self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); - if (self.ammo_nails == 1) { - self.ammo_nails = self.ammo_nails - 1; - self.currentammo = self.ammo_nails; - } else { - self.ammo_nails = self.ammo_nails - 2; - self.currentammo = self.ammo_nails; - } - dir = aim(self, 1000); - launch_spike(self.origin + '0 0 16' + v_right * ox, dir); - KickPlayer(-3, self); -}; - -.float hit_z; - -void () spike_touch = { - if (self.voided) - return; - self.voided = 1; - - if (other.solid == SOLID_TRIGGER) - return; - - if (pointcontents(self.origin) == CONTENT_SKY) { - dremove(self); - return; - } - if (other.takedamage) { - spawn_touchblood(9); - deathmsg = self.weapon; - if (self.owner.classname == "grenade") - TF_T_Damage(other, self, self.owner.owner, 9, TF_TD_NOTTEAM, - TF_TD_NAIL); - else - TF_T_Damage(other, self, self.owner, 18, TF_TD_NOTTEAM, - TF_TD_NAIL); - } else { - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - if (self.classname == "wizspike") - WriteByte(MSG_MULTICAST, TE_WIZSPIKE); - else if (self.classname == "knightspike") - WriteByte(MSG_MULTICAST, TE_KNIGHTSPIKE); - else - WriteByte(MSG_MULTICAST, TE_SPIKE); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); - } - dremove(self); -}; - -void () superspike_touch = { - local float ndmg; - - if (self.voided) - return; - self.voided = 1; - - if (other == self.owner) - return; - - if (other.solid == SOLID_TRIGGER) - return; - - if (pointcontents(self.origin) == CONTENT_SKY) { - dremove(self); - return; - } - if (other.takedamage) { - spawn_touchblood(18); - deathmsg = self.weapon; - if (deathmsg == DMSG_GREN_NAIL) - ndmg = 40; - else - ndmg = 26; - - if (self.owner.classname == "grenade") - TF_T_Damage(other, self, self.owner.owner, ndmg, TF_TD_NOTTEAM, - TF_TD_NAIL); - else - TF_T_Damage(other, self, self.owner, ndmg, TF_TD_NOTTEAM, - TF_TD_NAIL); - } else { - WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); - WriteByte(MSG_MULTICAST, TE_SUPERSPIKE); - WriteCoord(MSG_MULTICAST, self.origin_x); - WriteCoord(MSG_MULTICAST, self.origin_y); - WriteCoord(MSG_MULTICAST, self.origin_z); - multicast(self.origin, MULTICAST_PHS); - } - dremove(self); -}; - -void (entity pl) W_SetCurrentAmmo = { - entity oldself; - - if ((pl.health <= 0) || (pl.current_weapon == 0)) - return; - - if (!(self.tfstate & TFSTATE_AIMING) && !(self.tfstate & TFSTATE_CANT_MOVE)) - player_run(); - - pl.items = pl.items - (pl.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS)); - pl.weapon = 0; - - if (pl.current_weapon == WEAP_AXE) { - pl.currentammo = 0; - if (pl.playerclass == 8) { - if (pl.weaponmode == 0) - pl.weaponmodel = "progs/v_knife.mdl"; - else - pl.weaponmodel = "progs/v_knife2.mdl"; - } else - pl.weaponmodel = "progs/v_axe.mdl"; - pl.weaponframe = 0; - } else if (pl.current_weapon == WEAP_SPANNER) { - pl.currentammo = pl.ammo_cells; - pl.weaponmodel = "progs/v_span.mdl"; - pl.weaponframe = 0; - } else if (pl.current_weapon == WEAP_SHOTGUN) { - pl.currentammo = pl.ammo_shells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_shot.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_SHELLS; - pl.weapon = IT_SHOTGUN; - } else if (pl.current_weapon == WEAP_SUPER_SHOTGUN) { - pl.currentammo = pl.ammo_shells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_shot2.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_SHELLS; - pl.weapon = IT_SUPER_SHOTGUN; - } else if (pl.current_weapon == WEAP_NAILGUN) { - pl.currentammo = pl.ammo_nails; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_nail.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_NAILS; - pl.weapon = IT_NAILGUN; - } else if (pl.current_weapon == WEAP_SUPER_NAILGUN) { - pl.currentammo = pl.ammo_nails; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_nail2.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_NAILS; - pl.weapon = IT_SUPER_NAILGUN; - } else if (pl.current_weapon == WEAP_GRENADE_LAUNCHER) { - pl.currentammo = pl.ammo_rockets; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - if (pl.weaponmode == 0) - pl.weaponmodel = "progs/v_rock.mdl"; - else - pl.weaponmodel = "progs/v_pipe.mdl"; - pl.weaponframe = 0; - } - pl.weapon = IT_GRENADE_LAUNCHER; - pl.items = pl.items | IT_ROCKETS; - } else if (pl.current_weapon == WEAP_ROCKET_LAUNCHER) { - pl.currentammo = pl.ammo_rockets; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_rock2.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_ROCKETS; - pl.weapon = IT_ROCKET_LAUNCHER; - } else if (pl.current_weapon == WEAP_SNIPER_RIFLE) { - pl.currentammo = pl.ammo_shells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_srifle.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_SHELLS; - pl.weapon = IT_SHOTGUN; - } else if (pl.current_weapon == WEAP_AUTO_RIFLE) { - pl.currentammo = pl.ammo_shells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_srifle.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_SHELLS; - pl.weapon = IT_SUPER_SHOTGUN; - } else if (pl.current_weapon == WEAP_ASSAULT_CANNON) { - pl.currentammo = pl.ammo_shells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_asscan.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_SHELLS; - pl.weapon = IT_ROCKET_LAUNCHER; - } else if (pl.current_weapon == WEAP_FLAMETHROWER) { - pl.currentammo = pl.ammo_cells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_flame.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_CELLS; - pl.weapon = IT_GRENADE_LAUNCHER; - } else if (pl.current_weapon == WEAP_INCENDIARY) { - pl.currentammo = pl.ammo_rockets; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_irock.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_ROCKETS; - pl.weapon = IT_ROCKET_LAUNCHER; - } else if (pl.current_weapon == WEAP_MEDIKIT) { - if (!old_medikit) { - pl.currentammo = pl.ammo_cells; - pl.items = pl.items | IT_CELLS; - } else - pl.currentammo = 0; - pl.weaponmodel = "progs/v_medi.mdl"; - pl.weaponframe = 0; - } else if (pl.current_weapon == WEAP_TRANQ) { - pl.currentammo = pl.ammo_shells; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_tranq.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_SHELLS; - pl.weapon = IT_SHOTGUN; - } else if (pl.current_weapon == WEAP_LASER) { - pl.currentammo = pl.ammo_nails; - if (!(pl.tfstate & TFSTATE_RELOADING)) { - pl.weaponmodel = "progs/v_rail.mdl"; - pl.weaponframe = 0; - } - pl.items = pl.items | IT_NAILS; - pl.weapon = IT_SHOTGUN; - } else { - pl.currentammo = 0; - pl.weaponmodel = ""; - pl.weaponframe = 0; - } - - if (pl.is_feigning) { - pl.weaponmodel = ""; - pl.weaponframe = 0; - } - - // refresh engineer build menu when ammo updated - if (pl.menu_input == Menu_Engineer_Input) - Menu_Engineer(pl); - else if (pl.menu_input == Menu_Drop_Input) { - oldself = self; - self = pl; - Menu_Drop(); - self = oldself; - } -}; - -float () W_BestWeapon = { - local float it; - - it = self.weapons_carried; - if ((self.ammo_cells >= 1 && it & WEAP_LIGHTNING) && self.waterlevel <= 1) - return WEAP_LIGHTNING; - else if ((self.ammo_cells >= 7 && self.ammo_shells >= 1) && it & WEAP_ASSAULT_CANNON) - return WEAP_ASSAULT_CANNON; - else if (self.ammo_cells >= 1 && it & WEAP_FLAMETHROWER) - return WEAP_FLAMETHROWER; - else if (self.ammo_nails >= 2 && it & WEAP_SUPER_NAILGUN) - return WEAP_SUPER_NAILGUN; - else if (self.ammo_shells >= 2 && it & WEAP_SUPER_SHOTGUN) - return WEAP_SUPER_SHOTGUN; - else if (self.ammo_nails >= 1 && it & WEAP_LASER) - return WEAP_LASER; - else if (self.ammo_nails >= 1 && it & WEAP_NAILGUN) - return WEAP_NAILGUN; - else if (self.ammo_shells >= 1 && it & WEAP_SHOTGUN) - return WEAP_SHOTGUN; - else if (self.ammo_shells >= 1 && it & WEAP_TRANQ) - return WEAP_TRANQ; - else if (it & WEAP_MEDIKIT) - return WEAP_MEDIKIT; - else if (it & WEAP_SPANNER) - return WEAP_SPANNER; - else if (it & WEAP_AXE) - return WEAP_AXE; - - return 0; -}; - -float () W_BestWeaponSlot = { - local float weap; - - weap = W_BestWeapon(); - - return W_GetSlot(weap); -}; - -void () W_ChangeToBestWeapon = { - local float slot = W_BestWeaponSlot(); - - W_WeaponSlot(slot); - - //self.last_weaponslot = self.current_weaponslot; - //self.last_weapon = self.current_weapon; - //self.last_weaponmode = self.weaponmode; - self.current_weaponslot = slot; - self.current_weapon = self.next_weapon; - self.weaponmode = self.next_weaponmode; - W_SetCurrentAmmo(self); - W_WeaponState_Save(self); - W_PrintWeaponMessage(); -} - -float () W_CheckNoAmmo = { - if (self.current_weapon == WEAP_AXE || self.current_weapon == WEAP_SPANNER - || self.current_weapon == WEAP_MEDIKIT) - return 1; - else if (self.current_weapon == WEAP_SUPER_NAILGUN && self.currentammo >= 4) - return 1; - else if (self.current_weapon == WEAP_INCENDIARY && self.currentammo >= 3) - return 1; - else if ((self.current_weapon == WEAP_SUPER_SHOTGUN || self.current_weapon == WEAP_NAILGUN) - && self.currentammo >= 2) - return 1; - else if (self.currentammo > 0) - return 1; - - W_ChangeToBestWeapon(); - return 0; -}; - -void () W_Reload_shotgun = { - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_RELOADING); - self.owner.weaponmodel = "progs/v_shot.mdl"; - sprint(self.owner, PRINT_LOW, "Finished reloading\n"); - dremove(self); - W_WeaponState_Load(self.owner, 0); - Status_Refresh(self.owner); -}; - -void () W_Reload_super_shotgun = { - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_RELOADING); - self.owner.weaponmodel = "progs/v_shot2.mdl"; - sprint(self.owner, PRINT_LOW, "Finished reloading\n"); - dremove(self); - W_WeaponState_Load(self.owner, 0); - Status_Refresh(self.owner); -}; - -void (entity pl) W_Reload_sniper_rifle = { - pl.reload_sniper_ticks = 0; - pl.tfstate = pl.tfstate - (pl.tfstate & TFSTATE_RELOADING); - pl.weaponmodel = "progs/v_srifle.mdl"; - sprint(pl, PRINT_LOW, "Finished reloading\n"); - W_WeaponState_Load(pl, 0); - Status_Refresh(pl); -}; - -void () W_Reload_sniper_rifle_tick = { - self.owner.reload_sniper_ticks = self.owner.reload_sniper_ticks + 1; - - if (self.owner.reload_sniper_ticks >= 4) { - W_Reload_sniper_rifle(self.owner); - dremove(self); - return; - } - - Status_Refresh(self.owner); - self.nextthink = time + 1; -} - -void () W_Reload_grenade_launcher = { - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_RELOADING); - if (self.owner.weaponmode == 0) - self.owner.weaponmodel = "progs/v_rock.mdl"; - else - self.owner.weaponmodel = "progs/v_pipe.mdl"; - sprint(self.owner, PRINT_LOW, "Finished reloading\n"); - dremove(self); - W_WeaponState_Load(self.owner, 0); - Status_Refresh(self.owner); -}; - -void () W_Reload_rocket_launcher = { - self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_RELOADING); - self.owner.weaponmodel = "progs/v_rock2.mdl"; - sprint(self.owner, PRINT_LOW, "Finished reloading\n"); - dremove(self); - W_WeaponState_Load(self.owner, 0); - Status_Refresh(self.owner); -}; - -float () CheckForReload = { - if (self.current_weapon == WEAP_SHOTGUN) { - if (self.reload_shotgun >= 8 && self.ammo_shells > 0) { - TeamFortress_ReloadCurrentWeapon(); - return 1; - } - } else if (self.current_weapon == WEAP_SUPER_SHOTGUN) { - if (self.reload_super_shotgun >= 16 && self.ammo_shells > 0) { - TeamFortress_ReloadCurrentWeapon(); - return 1; - } - } else if (self.current_weapon == WEAP_SNIPER_RIFLE) { - if (self.reload_sniper_rifle >= 1 && self.ammo_shells > 0 && sniperreload) { - TeamFortress_ReloadCurrentWeapon(); - return 1; - } - } else if (self.current_weapon == WEAP_GRENADE_LAUNCHER) { - if ((self.reload_grenade_launcher >= 6) && (self.ammo_rockets > 0)) { - TeamFortress_ReloadCurrentWeapon(); - return 1; - } - } else if (self.current_weapon == WEAP_ROCKET_LAUNCHER) { - if ((self.reload_rocket_launcher >= 4) && (self.ammo_rockets > 0)) { - TeamFortress_ReloadCurrentWeapon(); - return 1; - } - } - return 0; -}; - -float (float weap) CheckForAmmo = { - if (weap == WEAP_SHOTGUN) { - if (self.reload_shotgun >= 1 && self.ammo_shells > self.reload_shotgun) - return 1; - } else if (weap == WEAP_SUPER_SHOTGUN) { - if (self.reload_super_shotgun >= 2 && self.ammo_shells > self.reload_super_shotgun) - return 1; - } else if (weap == WEAP_SNIPER_RIFLE) { - if (self.reload_sniper_rifle >= 1 && self.ammo_shells > self.reload_sniper_rifle && sniperreload) - return 1; - } else if (weap == WEAP_GRENADE_LAUNCHER) { - if (self.reload_grenade_launcher >= 1 && self.ammo_rockets > self.reload_grenade_launcher) - return 1; - } else if (weap == WEAP_ROCKET_LAUNCHER) { - if (self.reload_rocket_launcher >= 1 && self.ammo_rockets > self.reload_rocket_launcher) - return 1; - } - return 0; -}; - -void () player_axe1; -void () player_axeb1; -void () player_axec1; -void () player_axed1; - -void () player_shot1; -void () player_nail1; -void () player_light1; -void () player_rocket1; -void () player_autorifle1; - -void () player_assaultcannon1; -void () player_assaultcannonup1; -void () player_assaultcannondown1; - -void () player_medikit1; -void () player_medikitb1; -void () player_medikitc1; -void () player_medikitd1; - -void () W_Attack = { - local float r; - - if (!W_CheckNoAmmo()) - return; - - if (self.has_disconnected == TRUE) - return; - - if (self.tfstate & TFSTATE_RELOADING) - return; - - if ((self.is_undercover || (self.undercover_team != 0)) || - (self.undercover_skin != 0)) - Spy_RemoveDisguise(self); - - makevectors(self.v_angle); - self.show_hostile = time + 1; - - if (self.current_weapon == WEAP_AXE) { - Attack_Finished(0.5); - sound(self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); - r = random(); - if (r < 0.25) - player_axe1(); - else if (r < 0.5) - player_axeb1(); - else if (r < 0.75) - player_axec1(); - else - player_axed1(); - } else if (self.current_weapon == WEAP_SPANNER) { - Attack_Finished(0.5); - sound(self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); - player_axe1(); - } else if (self.current_weapon == WEAP_SHOTGUN) { - if (CheckForReload() == TRUE) - return; - player_shot1(); - W_FireShotgun(); - self.reload_shotgun = self.reload_shotgun + 1; - Status_Refresh(self); - CheckForReload(); - Attack_Finished(0.5); - } else if (self.current_weapon == WEAP_SUPER_SHOTGUN) { - if (CheckForReload() == TRUE) - return; - player_shot1(); - W_FireSuperShotgun(); - self.reload_super_shotgun = self.reload_super_shotgun + 2; - Status_Refresh(self); - CheckForReload(); - Attack_Finished(0.7); - } else if (self.current_weapon == WEAP_NAILGUN) { - player_nail1(); - } else if (self.current_weapon == WEAP_SUPER_NAILGUN) { - player_nail1(); - } else if (self.current_weapon == WEAP_GRENADE_LAUNCHER) { - if (CheckForReload() == TRUE) - return; - player_rocket1(); - W_FireGrenade(); - self.reload_grenade_launcher = self.reload_grenade_launcher + 1; - Status_Refresh(self); - CheckForReload(); - Attack_Finished(0.6); - } else if (self.current_weapon == WEAP_ROCKET_LAUNCHER) { - if (CheckForReload() == TRUE) - return; - player_rocket1(); - W_FireRocket(); - self.reload_rocket_launcher = self.reload_rocket_launcher + 1; - Status_Refresh(self); - CheckForReload(); - Attack_Finished(0.8); - } else if (self.current_weapon == WEAP_LIGHTNING) { - player_light1(); - Attack_Finished(0.1); - sound(self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); - } else if (self.current_weapon == WEAP_SNIPER_RIFLE) { - if (self.flags & FL_ONGROUND) { - if (CheckForReload() == TRUE) - return; - player_shot1(); - W_FireSniperRifle(); - if (sniperreload) { - self.reload_sniper_rifle = self.reload_sniper_rifle + 1; - Status_Refresh(self); - CheckForReload(); - } - Attack_Finished(1.5); - } - } else if (self.current_weapon == WEAP_AUTO_RIFLE) { - player_autorifle1(); - W_FireAutoRifle(); - Attack_Finished(0.1); - } else if (self.current_weapon == WEAP_ASSAULT_CANNON) { - if (self.ammo_cells < 7) { - if (time >= self.antispam_assault_cannon) { - sprint(self, PRINT_MEDIUM, "Not enough cells to power up the Assault Cannon\n"); - self.antispam_assault_cannon = time + 3; - } - W_ChangeWeapon(W_BestWeaponSlot()); - W_SetCurrentAmmo(self); - W_WeaponState_Save(self); - } else { - self.ammo_cells = self.ammo_cells - 7; - self.heat = 1; - self.immune_to_check = time + 5; - self.tfstate = self.tfstate | TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - player_assaultcannonup1(); - } - } else if (self.current_weapon == WEAP_FLAMETHROWER) { - player_shot1(); - W_FireFlame(); - if (self.waterlevel > 2) - Attack_Finished(1); - else - Attack_Finished(0.15); - } else if (self.current_weapon == WEAP_INCENDIARY) { - if (self.ammo_rockets >= 3) { - player_rocket1(); - W_FireIncendiaryCannon(); - Attack_Finished(1.2); - } else if (time >= self.antispam_incendiary_cannon) { - sprint(self, PRINT_HIGH, "Not enough ammo\n"); - self.antispam_incendiary_cannon = time + 3; - } - } else if (self.current_weapon == WEAP_MEDIKIT) { - sound(self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); - r = random(); - if (r < 0.25) - player_medikit1(); - else if (r < 0.5) - player_medikitb1(); - else if (r < 0.75) - player_medikitc1(); - else - player_medikitd1(); - Attack_Finished(0.5); - } else if (self.current_weapon == WEAP_TRANQ) { - sound(self, CHAN_WEAPON, "weapons/dartgun.wav", 1, ATTN_NORM); - player_shot1(); - W_FireTranq(); - Attack_Finished(1.5); - } else if (self.current_weapon == WEAP_LASER) { - sound(self, CHAN_WEAPON, "weapons/railgun.wav", 1, ATTN_NORM); - player_shot1(); - W_FireLaser(); - Attack_Finished(0.4); - } -}; - -float (entity pl) WeaponReady = { - if (time >= pl.attack_finished && !(pl.tfstate & TFSTATE_RELOADING)) { - return 1; - } - - return 0; -} - -void () W_PrintWeaponMessage = { - if (self.current_weapon == WEAP_GRENADE_LAUNCHER) { - if (self.weaponmode == GL_NORMAL) - sprint(self, PRINT_MEDIUM, "Normal grenade mode\n"); - else if (cb_prematch_time > time) - sprint(self, PRINT_MEDIUM, - "Pipebomb mode not available in prematch\n"); - else if (self.weaponmode == GL_PIPEBOMB) - sprint(self, PRINT_MEDIUM, "Pipebomb mode\n"); - } else if (self.current_weapon == WEAP_SNIPER_RIFLE) - sprint(self, PRINT_MEDIUM, "Sniper rifle ready\n"); - else if (self.current_weapon == WEAP_AUTO_RIFLE) - sprint(self, PRINT_MEDIUM, "Sniper rifle on fully automatic mode\n"); -}; - -float (float weap) W_GetSlot = { - if (weap == WEAP_ROCKET_LAUNCHER - || weap == WEAP_SUPER_NAILGUN - || weap == WEAP_SNIPER_RIFLE - || weap == WEAP_FLAMETHROWER - || weap == WEAP_TRANQ - || weap == WEAP_LASER - || weap == WEAP_ASSAULT_CANNON) { - return 1; - } else if (weap == WEAP_SUPER_SHOTGUN - || weap == WEAP_AUTO_RIFLE - || weap == WEAP_INCENDIARY) { - return 2; - } else if (weap == WEAP_SHOTGUN) { - if (self.playerclass == PC_SCOUT) - return 2; - else - return 3; - } else if (weap == WEAP_NAILGUN) { - if (self.playerclass == PC_SCOUT) - return 1; - else - return 3; - } else if (weap == WEAP_GRENADE_LAUNCHER) { - if (self.weaponmode == 0) - return 1; - else - return 2; - } - return 0; -}; - -float (float inp) W_AmmoSlot = { - self.noammo = 0; - - if (inp == 1) { - if (self.playerclass == PC_SCOUT && self.ammo_nails < 2) - self.noammo = 1; - else if (self.playerclass == PC_SNIPER && self.ammo_shells < 1) - self.noammo = 1; - else if (self.playerclass == PC_SOLDIER && self.ammo_rockets < 1) - self.noammo = 1; - else if (self.playerclass == PC_DEMOMAN && self.ammo_rockets < 1) - self.noammo = 1; - else if (self.playerclass == PC_MEDIC && self.ammo_nails < 4) - self.noammo = 1; - else if (self.playerclass == PC_HVYWEAP) { - if (self.ammo_shells < 1) - self.noammo = 1; - else if (self.ammo_cells < 7) - self.noammo = 2; - } else if (self.playerclass == PC_PYRO && self.ammo_cells < 1) - self.noammo = 1; - else if (self.playerclass == PC_SPY && self.ammo_shells < 1) - self.noammo = 1; - else if (self.playerclass == PC_ENGINEER && self.ammo_nails < 1) - self.noammo = 1; - } else if (inp == 2) { - if (self.playerclass == PC_SCOUT && self.ammo_shells < 1) - self.noammo = 1; - else if (self.playerclass == PC_SNIPER && self.ammo_shells < 1) - self.noammo = 1; - else if ((self.playerclass == PC_SOLDIER - || self.playerclass == PC_MEDIC - || self.playerclass == PC_HVYWEAP - || self.playerclass == PC_SPY - || self.playerclass == PC_ENGINEER) - && self.ammo_shells < 2) - self.noammo = 1; - else if (self.playerclass == PC_DEMOMAN && self.ammo_rockets < 1) - self.noammo = 1; - else if (self.playerclass == PC_PYRO && self.ammo_rockets < 3) - self.noammo = 1; - } else if (inp == 3) { - if (self.playerclass == PC_SCOUT - || self.playerclass == PC_ENGINEER) - return 0; - if ((self.playerclass == PC_SNIPER - || self.playerclass == PC_SPY) - && self.ammo_nails < 2) - self.noammo = 1; - else if ((self.playerclass == PC_SOLDIER - || self.playerclass == PC_DEMOMAN - || self.playerclass == PC_MEDIC - || self.playerclass == PC_HVYWEAP - || self.playerclass == PC_PYRO) - && self.ammo_shells < 1) - self.noammo = 1; - } else if (inp == 4) - return 1; - - if (self.noammo > 0) - return 0; - else - return 1; -}; - -float () W_WeaponSlot1 = { - if (self.playerclass == PC_SCOUT) - return WEAP_NAILGUN; - else if (self.playerclass == PC_SNIPER) - return WEAP_SNIPER_RIFLE; - else if (self.playerclass == PC_SOLDIER) - return WEAP_ROCKET_LAUNCHER; - else if (self.playerclass == PC_DEMOMAN) - return WEAP_GRENADE_LAUNCHER; - else if (self.playerclass == PC_MEDIC) - return WEAP_SUPER_NAILGUN; - else if (self.playerclass == PC_HVYWEAP) - return WEAP_ASSAULT_CANNON; - else if (self.playerclass == PC_PYRO) - return WEAP_FLAMETHROWER; - else if (self.playerclass == PC_SPY) - return WEAP_TRANQ; - else if (self.playerclass == PC_ENGINEER) - return WEAP_LASER; - return 0; -}; - -float () W_WeaponSlot2 = { - if (self.playerclass == PC_SCOUT) - return WEAP_SHOTGUN; - else if (self.playerclass == PC_SNIPER) - return WEAP_AUTO_RIFLE; - else if (self.playerclass == PC_SOLDIER - || self.playerclass == PC_MEDIC - || self.playerclass == PC_HVYWEAP - || self.playerclass == PC_SPY - || self.playerclass == PC_ENGINEER) - return WEAP_SUPER_SHOTGUN; - else if (self.playerclass == PC_DEMOMAN) { - return WEAP_GRENADE_LAUNCHER; // weaponmode = 1 (pipebomb launcher) - } else if (self.playerclass == PC_PYRO) - return WEAP_INCENDIARY; - return 0; -}; - -float () W_WeaponSlot3 = { - if (self.playerclass == PC_SCOUT - || self.playerclass == PC_ENGINEER) { - sprint(self, PRINT_HIGH, "No weapon\n"); - return 0; - } - - if (self.playerclass == PC_SNIPER - || self.playerclass == PC_SPY) - return WEAP_NAILGUN; - else if (self.playerclass == PC_SOLDIER - || self.playerclass == PC_DEMOMAN - || self.playerclass == PC_MEDIC - || self.playerclass == PC_HVYWEAP - || self.playerclass == PC_PYRO) - return WEAP_SHOTGUN; - return 0; -}; - -float () W_WeaponSlot4 = { - if (self.weapons_carried & WEAP_MEDIKIT) - return WEAP_MEDIKIT; - else if (self.weapons_carried & WEAP_SPANNER) - return WEAP_SPANNER; - else - return WEAP_AXE; -}; - -void (float slot) W_WeaponSlot = { - self.next_weaponmode = 0; - if (slot == 1) - self.next_weapon = W_WeaponSlot1(); - else if (slot == 2) { - self.next_weapon = W_WeaponSlot2(); - if (self.next_weapon == WEAP_GRENADE_LAUNCHER) - self.next_weaponmode = 1; - } - else if (slot == 3) - self.next_weapon = W_WeaponSlot3(); - else if (slot == 4) - self.next_weapon = W_WeaponSlot4(); -}; - -void () W_AmmoError = { - if (self.noammo == 1) - sprint(self, PRINT_HIGH, "Not enough ammo\n"); - else if (self.noammo == 2 && time >= self.antispam_assault_cannon) { - sprint(self, PRINT_HIGH, "Not enough cells to power the assault cannon\n"); - self.antispam_assault_cannon = time + 3; - } -}; - -void (entity pl) W_WeaponState_Save = { - if (!WeaponReady(pl)) - return; - - pl.weaponstate_current_weaponslot = pl.current_weaponslot; - pl.weaponstate_last_weaponslot = pl.last_weaponslot; - pl.weaponstate_current_weapon = pl.current_weapon; - pl.weaponstate_last_weapon = pl.last_weapon; - pl.weaponstate_weaponmode = pl.weaponmode; - pl.weaponstate_last_weaponmode = pl.last_weaponmode; -} - -void (entity pl, float swap) W_WeaponState_Load = { - if (!swap) { - pl.current_weaponslot = pl.weaponstate_current_weaponslot; - pl.last_weaponslot = pl.weaponstate_last_weaponslot; - pl.current_weapon = pl.weaponstate_current_weapon; - pl.last_weapon = pl.weaponstate_last_weapon; - pl.weaponmode = pl.weaponstate_weaponmode; - pl.last_weaponmode = pl.weaponstate_last_weaponmode; - } else { - pl.current_weaponslot = pl.weaponstate_last_weaponslot; - pl.last_weaponslot = pl.weaponstate_current_weaponslot; - pl.current_weapon = pl.weaponstate_last_weapon; - pl.last_weapon = pl.weaponstate_current_weapon; - pl.weaponmode = pl.weaponstate_last_weaponmode; - pl.last_weaponmode = pl.weaponstate_weaponmode; - } - pl.queue_weaponstate = 0; - - W_WeaponState_Save(pl); - W_SetCurrentAmmo(pl); - Status_Refresh(pl); -} - -float (entity pl) W_WeaponState_Check = { - if (pl.current_weaponslot == pl.weaponstate_current_weaponslot - && pl.last_weaponslot == pl.weaponstate_last_weaponslot) - return 1; - else - return 0; -} - -void (float inp) W_ChangeWeapon = { - if (inp < 1 || inp > 4) - return; - - if (self.playerclass == 0) - return; - - // queue next weapon if queue is not empty or has changed - if (!self.queue_weaponslot || inp != self.queue_weaponslot) - self.queue_weaponslot = inp; - - // halt if weapon is not ready to be fired - if (!WeaponReady(self)) - return; - - // check for ammo - if (! W_AmmoSlot(inp)) { - W_AmmoError(); - self.queue_weaponslot = 0; - return; - } - - W_WeaponSlot(inp); - - // don't update current/last weapon information if next weapon is the same as current - if (self.current_weapon != self.next_weapon || self.weaponmode != self.next_weaponmode) { - self.last_weaponslot = self.current_weaponslot; - self.current_weaponslot = self.queue_weaponslot; - self.last_weapon = self.current_weapon; - self.current_weapon = self.next_weapon; - self.last_weaponmode = self.weaponmode; - self.weaponmode = self.next_weaponmode; - - if (!self.is_quickfiring) - W_PrintWeaponMessage(); - } - - if (!self.is_quickfiring && !self.has_quickfired) - W_WeaponState_Save(self); - - W_SetCurrentAmmo(self); - Status_Refresh(self); - self.queue_weaponslot = 0; -}; - -void () CycleWeaponLast = { - if (!self.last_weaponslot) - return; - - if (!self.is_quickfiring) - W_ChangeWeapon(self.last_weaponslot); - else - W_WeaponState_Load(self, 1); -}; - -void () CycleWeaponNext = { - local float slot, next; - - if (self.weaponmodel == string_null || self.current_weapon == 0) - return; - - next = 0; - for (slot = 1; slot <= 4; slot++) { - next = (slot == 4) ? 1 : (slot + 1); - if (self.current_weaponslot == slot) { - while (! W_AmmoSlot(next)) - next = (next == 4) ? 1 : (next + 1); - break; - } - } - W_ChangeWeapon(next); -}; - -void () CycleWeaponPrev = { - local float slot, next; - - if (self.weaponmodel == string_null || self.current_weapon == 0) - return; - - next = 0; - for (slot = 1; slot <= 4; slot++) { - next = (slot == 1) ? 4 : (slot - 1); - if (self.current_weaponslot == slot) { - while (! W_AmmoSlot(next)) - next = (next == 1) ? 4 : (next - 1); - break; - } - } - W_ChangeWeapon(next); -}; - -void () PreMatchImpulses; -void () DeadImpulses; - -void () ImpulseCommands = { - if ((self.last_impulse == TF_DETPACK) && self.impulse) - TeamFortress_SetDetpack(self.impulse); - - if ((cb_prematch_time > time) || cease_fire) { - PreMatchImpulses(); - DeadImpulses(); - self.impulse = 0; - return; - } - if (self.impulse == TF_SPECIAL_SKILL) - UseSpecialSkill(); - - if (self.impulse == TF_NEXTTIP) { - self.display_tip = 0; - self.tip_type = 0; - self.tip_time = time; - Status_Refresh(self); - } - - if (self.impulse == TF_WEAPLAST) { - if (self.playerclass == PC_SPY && self.is_undercover == 2) - CF_Spy_DisguiseStop(); - else if (self.playerclass == PC_ENGINEER && self.is_building) { - TeamFortress_EngineerBuildStop(); - CycleWeaponLast(); - } else if (self.playerclass == PC_DEMOMAN && self.is_detpacking) { - TeamFortress_DetpackStop(); - CycleWeaponLast(); - } - } else if (self.impulse == TF_TOGGLEVOTE) - Vote_ToggleMenu(self); - - if (!self.is_feigning) { - if (self.impulse == TF_GRENADE_1) - TeamFortress_PrimeGrenade(1); - else if (self.impulse == TF_GRENADE_2) - TeamFortress_PrimeGrenade(2); - else if (self.impulse == TF_GRENADE_PT_1) - TeamFortress_PrimeThrowGrenade(1); - else if (self.impulse == TF_GRENADE_PT_2) - TeamFortress_PrimeThrowGrenade(2); - } - - if ((!self.is_building && !self.is_detpacking) && !self.is_feigning) { - if (self.impulse == TF_WEAPNEXT) - CycleWeaponNext(); - else if (self.impulse == TF_WEAPPREV) - CycleWeaponPrev(); - else if (self.impulse == TF_RELOAD) - TeamFortress_ReloadCurrentWeapon(); - else if (self.impulse == TF_RELOAD_SLOT1) - TeamFortress_ReloadSlot(1); - else if (self.impulse == TF_RELOAD_SLOT2) - TeamFortress_ReloadSlot(2); - else if (self.impulse == TF_RELOAD_SLOT3) - TeamFortress_ReloadSlot(3); - else if (self.impulse == TF_RELOAD_NEXT) - TeamFortress_ReloadNext(); - else if (self.impulse == TF_DETPACK_5) - TeamFortress_SetDetpack(5); - else if (self.impulse == TF_DETPACK_20) - TeamFortress_SetDetpack(20); - else if (self.impulse == TF_DETPACK_50) - TeamFortress_SetDetpack(50); - else if (self.impulse == TF_DROP_AMMO) { - Menu_Drop(); - } - } - - if (self.impulse == TF_INVENTORY) - TeamFortress_Inventory(); - else if ((self.playerclass != 0) && (self.impulse == TF_MEDIC_HELPME)) - TeamFortress_SaveMe(); - else if (self.impulse == TF_GRENADE_T) - TeamFortress_ThrowGrenade(); - else if (self.impulse == TF_GRENADE_SWITCH) - TeamFortress_GrenadeSwitch(); - else if (self.impulse == TF_ID) - CF_Identify(self, 1); - else if (self.impulse == TF_ID_TEAM) - CF_Identify(self, 2); - else if (self.impulse == TF_ID_ENEMY) - CF_Identify(self, 3); - else if (self.impulse == TF_SHOW_IDS) - TeamFortress_ShowIDs(); - else if ((self.playerclass != 0) && (self.impulse == TF_DROPFLAG)) - DropGoalItems(); - else if (self.impulse == TF_DISCARD) - TeamFortress_Discard(); - else if (self.impulse == TF_DASH) - CF_Scout_Dash(); - else if (self.impulse == TF_PB_DETONATE) - TeamFortress_DetonatePipebombs(0); - else if (self.impulse == TF_DETPACK_STOP) - TeamFortress_DetpackStop(); - else if (self.impulse == TF_MEDIC_AURA_TOGGLE) - CF_Medic_AuraToggle(); - else if ((self.impulse == TF_ENGINEER_DETSENTRY) && - (self.playerclass == PC_ENGINEER)) - DestroyBuilding(self, "building_sentrygun"); - else if ((self.impulse == TF_ENGINEER_DETDISP) && - (self.playerclass == PC_ENGINEER)) - DestroyBuilding(self, "building_dispenser"); - else if ((self.impulse == 196) && (self.playerclass == PC_ENGINEER)) - DestroyBuilding(self, "building_teleporter_exit"); - else if ((self.impulse == 197) && (self.playerclass == PC_ENGINEER)) - DestroyBuilding(self, "building_teleporter_entrance"); - else if ((self.impulse == TF_SPY_SPY) && (self.playerclass == PC_SPY)) { - if (invis_only) - CF_Spy_Invisible(); - else - Menu_Spy_Skin(); - } else if ((self.impulse == TF_SPY_DIE) && (self.playerclass == PC_SPY)) - CF_Spy_FeignDeath(0); - else if ((self.impulse == TF_SPY_SILENT_DIE) && - (self.playerclass == PC_SPY)) - CF_Spy_FeignDeath(1); - else if (self.impulse == TF_DISGUISE_RESET && self.playerclass == PC_SPY) { - CF_Spy_ChangeSkin(self, 8); - CF_Spy_ChangeColor(self, self.team_no); - } else if (self.impulse == TF_DISGUISE_SCOUT && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 1); - else if (self.impulse == TF_DISGUISE_SNIPER && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 2); - else if (self.impulse == TF_DISGUISE_SOLDIER && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 3); - else if (self.impulse == TF_DISGUISE_DEMOMAN && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 4); - else if (self.impulse == TF_DISGUISE_MEDIC && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 5); - else if (self.impulse == TF_DISGUISE_HWGUY && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 6); - else if (self.impulse == TF_DISGUISE_PYRO && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 7); - else if (self.impulse == TF_DISGUISE_ENGINEER && self.playerclass == PC_SPY) - CF_Spy_ChangeSkin(self, 9); - else if (self.impulse == TF_DISGUISE_BLUE && self.playerclass == PC_SPY) - CF_Spy_ChangeColor(self, 1); - else if (self.impulse == TF_DISGUISE_RED && self.playerclass == PC_SPY) - CF_Spy_ChangeColor(self, 2); - else if (self.impulse == TF_DISGUISE_YELLOW && self.playerclass == PC_SPY) - CF_Spy_ChangeColor(self, 3); - else if (self.impulse == TF_DISGUISE_GREEN && self.playerclass == PC_SPY) - CF_Spy_ChangeColor(self, 4); - else if (self.impulse == TF_DISGUISE_ENEMY && self.playerclass == PC_SPY) { - if (number_of_teams > 2) - Menu_Spy_Color(); - else if (self.team_no == 1) - CF_Spy_ChangeColor(self, 2); - else - CF_Spy_ChangeColor(self, 1); - } else if (self.impulse == TF_DISGUISE_LAST && self.playerclass == PC_SPY) - CF_Spy_DisguiseLast(); - else if ((self.impulse == TF_DEMOMAN_DETPACK) && - (self.playerclass == PC_DEMOMAN)) - TeamFortress_DetpackMenu(); - else if ((self.impulse == TF_ENGINEER_BUILD) && - (self.playerclass == PC_ENGINEER)) - TeamFortress_EngineerBuild(); - else if (self.impulse == FLAG_INFO) { - if (CTF_Map == 1) - TeamFortress_CTF_FlagInfo(); - else - TeamFortress_DisplayDetectionItems(); - } else if (self.impulse == TF_DISPLAYLOCATION) - display_location(); - else - DeadImpulses(); - - if (self.impulse == TF_DETPACK) - self.last_impulse = self.impulse; - - self.impulse = 0; -}; - -void () PreMatchImpulses = { - if (self.impulse == TF_WEAPNEXT) - CycleWeaponNext(); - else if (self.impulse == TF_WEAPPREV) - CycleWeaponPrev(); - - if (self.impulse == TF_INVENTORY) - TeamFortress_Inventory(); - else if (self.impulse == TF_ID) - CF_Identify(self, 0); - else if (self.impulse == FLAG_INFO) { - if (CTF_Map == TRUE) - TeamFortress_CTF_FlagInfo(); - else - TeamFortress_DisplayDetectionItems(); - } else if (self.impulse == TF_DISPLAYLOCATION) - display_location(); -}; - -void () DeadImpulses = { - if (self.impulse == TF_SHOWTF) - TeamFortress_ShowTF(); - else if (self.impulse == TF_CLASSHELP) - Help_Show(); - else if (self.impulse == TF_SHOWLEGALCLASSES) - TeamFortress_DisplayLegalClasses(); - else if ((self.impulse >= TF_CHANGEPC_SCOUT) && (self.impulse <= TF_CHANGEPC_RANDOM)) { - TeamFortress_ChangeClass(self.impulse - TF_CHANGEPC_SCOUT + 1); - } else if ((self.playerclass != 0) && (self.impulse == TF_CHANGETEAM) - && (deathmatch == 3) && (cb_prematch_time < time)) { - Menu_Team(0); - } else if ((self.playerclass != 0) && (self.impulse == TF_CHANGECLASS) - && (deathmatch == 3) && (cb_prematch_time < time)) { - Menu_Class(0); - } else if (self.is_admin == 1) { - if (self.impulse == 193) - Admin_CeaseFire(); - else if (self.impulse == 192) - Admin_CountPlayers(); - else if (self.impulse == 189) - Admin_CycleDeal(); - else if ((self.impulse == 190) && (self.admin_mode == 1)) - Admin_DoKick(); - else if ((self.impulse == 191) && (self.admin_mode == 1)) - Admin_DoBan(); - else if ((self.impulse == 195) && (self.admin_mode == 1)) - Admin_CycleDeal(); - else if (self.impulse == 198) - Admin_ListIPs(); - } - if (self.impulse == TF_HELP_MAP) - TeamFortress_HelpMap(); - else if (self.impulse == TF_STATUS_QUERY) - TeamFortress_StatusQuery(); - else if (self.impulse == TF_TEAM_1) - TeamFortress_TeamSet(1); - else if (self.impulse == TF_TEAM_2) - TeamFortress_TeamSet(2); - else if (self.impulse == TF_TEAM_3) - TeamFortress_TeamSet(3); - else if (self.impulse == TF_TEAM_4) - TeamFortress_TeamSet(4); - else if (self.impulse == TF_TEAM_SCORES) - TeamFortress_TeamShowScores(0); - else if (self.impulse == TF_TEAM_CLASSES) - TeamFortress_TeamShowMemberClasses(self); - else if ((self.playerclass == PC_SCOUT) && (self.impulse == TF_SCAN)) - ScannerSwitch(); - else if ((self.playerclass == PC_SCOUT) && - (self.impulse == TF_SCAN_SOUND)) { - sprint(self, PRINT_HIGH, "Scanner sound: "); - if (self.tf_items_flags & 4) { - self.tf_items_flags = self.tf_items_flags - 4; - sprint(self, PRINT_HIGH, "off\n"); - } else { - self.tf_items_flags = self.tf_items_flags | 4; - sprint(self, PRINT_HIGH, "on\n"); - } - } else if ((self.playerclass == PC_SCOUT) && (self.impulse == TF_SCAN_ENEMY)) { - sprint(self, PRINT_HIGH, "Scanning for: "); - if (self.tf_items_flags & NIT_SCANNER_ENEMY) { - self.tf_items_flags = self.tf_items_flags - NIT_SCANNER_ENEMY; - if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { - sprint(self, PRINT_HIGH, "Friendlies only\n"); - } else { - sprint(self, PRINT_HIGH, "Nothing\n"); - } - } else { - self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_ENEMY; - if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { - sprint(self, PRINT_HIGH, "Friendlies and enemies\n"); - } else { - sprint(self, PRINT_HIGH, "Enemies only\n"); - } - Status_Refresh(self); - } - } else if ((self.playerclass == PC_SCOUT) && (self.impulse == TF_SCAN_FRIENDLY)) { - sprint(self, PRINT_HIGH, "Scanning for: "); - if (self.tf_items_flags & NIT_SCANNER_FRIENDLY) { - self.tf_items_flags = self.tf_items_flags - NIT_SCANNER_FRIENDLY; - if (self.tf_items_flags & NIT_SCANNER_ENEMY) { - sprint(self, PRINT_HIGH, "Enemies only\n"); - } else { - sprint(self, PRINT_HIGH, "Nothing\n"); - } - } else { - self.tf_items_flags = self.tf_items_flags | NIT_SCANNER_FRIENDLY; - if (self.tf_items_flags & NIT_SCANNER_ENEMY) { - sprint(self, PRINT_HIGH, "Friendlies and enemies\n"); - } else { - sprint(self, PRINT_HIGH, "Friendlies only\n"); - } - } - Status_Refresh(self); - } else if (self.impulse == TF_ALIAS_CHECK) { - sprint(self, PRINT_HIGH, "Aliases checked\n"); - self.got_aliases = 1; - self.impulse = 0; - } -}; - -void () W_WeaponFrame = { - if (self.menu_input) { - if (self.impulse > 0 && self.impulse <= 10) { - Menu_Input(self.impulse); - return; - } - } - - if (self.impulse == TF_CLASSMENU) { - if (self.playerclass == PC_ENGINEER) - Menu_Engineer(self); - else if (self.playerclass == PC_SCOUT) - Menu_Scout(); - else if (self.playerclass == PC_SPY) - Menu_Spy(self); - else if (self.playerclass == PC_DEMOMAN) - TeamFortress_DetpackMenu(); - - self.impulse = 0; - return; - } - - if (self.is_feigning && self.impulse != TF_SPECIAL_SKILL && self.impulse != TF_MEDIC_HELPME) - return; - - if (self.impulse == TF_WEAPLAST && self.is_undercover != 2) { - - // change to last weapon now - if (WeaponReady(self)) - CycleWeaponLast(); - // change to weaponstate (prior to quick fire) when weapon is ready - else if (self.is_quickfiring) - self.queue_weaponstate = 2; - // change to last weapon when weapon is ready - else - self.queue_weaponslot = self.last_weaponslot; - } - - // when +slotX bind is released, this gets issued - if (self.impulse == TF_QUICKSTOP && self.is_quickfiring) { - - self.has_quickfired = 1; - self.impulse = 0; - - } - - // unset quick firing variables when quick weapon has finished firing - if (self.is_quickfiring && self.has_quickfired && WeaponReady(self)) { - - self.is_quickfiring = 0; - self.has_quickfired = 0; - - } - - // slot 1-4 binds - if (self.impulse >= 1 && self.impulse <= 4 && WeaponReady(self) - && !self.is_building && !self.is_detpacking) { - - // load weapon state if current state doesn't match stored state - if (!W_WeaponState_Check(self)) - W_WeaponState_Load(self, 0); - - // obviously not quickfiring if we're changing weapon - self.is_quickfiring = 0; - self.has_quickfired = 0; - W_ChangeWeapon(self.impulse); - - // regular attack (both +attack and -attack) - } else if (!self.impulse && !self.is_quickfiring) { - - // load weapon state if current state doesn't match - // stored state, if weapon is ready - if (!W_WeaponState_Check(self) && WeaponReady(self) && !(self.tfstate & TFSTATE_AIMING)) - W_WeaponState_Load(self, 0); - - // +slot1-4 quick fire - } else if (self.impulse >= TF_QUICKSLOT1 && self.impulse <= TF_QUICKSLOT4 - && !self.is_building && !self.is_detpacking) { - - self.is_quickfiring = 1; - self.has_quickfired = 0; - - if (WeaponReady(self)) { - if (self.impulse == TF_QUICKSLOT1) - W_ChangeWeapon(1); - else if (self.impulse == TF_QUICKSLOT2) - W_ChangeWeapon(2); - else if (self.impulse == TF_QUICKSLOT3) - W_ChangeWeapon(3); - else if (self.impulse == TF_QUICKSLOT4) - W_ChangeWeapon(4); - } - - // change weapon if queue_weaponslot has been set - } else if (self.queue_weaponslot > 0) { - - W_ChangeWeapon(self.queue_weaponslot); - - // load weapon state - } else if (self.queue_weaponstate && WeaponReady(self)) { - - // load weaponstate saved from before quick fire started - if (self.queue_weaponstate == 1) - W_WeaponState_Load(self, 0); - - // load swapped weaponstate - else - W_WeaponState_Load(self, 1); - - } - - if (self.impulse == TF_CHANGETEAM) { - Menu_Team(0); - self.impulse = 0; - return; - } else if (self.impulse == TF_CHANGECLASS) { - Menu_Class(0); - self.impulse = 0; - return; - } - - if (intermission_running || cease_fire) - return; - - // hwguy assault cannon special - if (self.playerclass == PC_HVYWEAP) { - if (cannon_lock && (self.impulse == TF_LOCKON || self.impulse == TF_LOCKOFF)) { - if (self.impulse == TF_LOCKON) - self.tfstate = self.tfstate | TFSTATE_LOCK; - else if (self.impulse == TF_LOCKOFF) - self.tfstate = self.tfstate - (self.tfstate & TFSTATE_LOCK); - Status_Refresh(self); - self.impulse = 0; - return; - } - } - - if (self.playerclass == PC_SNIPER) { - if (self.impulse == TF_SPECIAL_SKILL || self.impulse == TF_ZOOMTOGGLE) { - Sniper_ZoomToggle(); - self.impulse = 0; - return; - } else if (self.impulse == TF_ZOOMIN || (self.is_zooming && self.impulse == TF_WEAPPREV)) { - Sniper_ZoomAdjust(1); - self.impulse = 0; - return; - } else if (self.impulse == TF_ZOOMOUT || (self.is_zooming && self.impulse == TF_WEAPNEXT)) { - Sniper_ZoomAdjust(0); - self.impulse = 0; - return; - } - } - - // class specials and grenade impulses always possible - if (self.impulse == TF_SPECIAL_SKILL ||(self.impulse >= TF_GRENADE_1 && self.impulse <= TF_GRENADE_SWITCH)) { - ImpulseCommands(); - return; - } - - // /saveme, /dash, /discard, /dropflag - if (self.impulse == TF_MEDIC_HELPME || self.impulse == TF_DASH || self.impulse == TF_DISCARD - || self.impulse == TF_DROPFLAG) { - ImpulseCommands(); - return; - } - - if (!WeaponReady(self)) - return; - - if (self.impulse != 0 && self.has_disconnected == 0) - ImpulseCommands(); - - if (self.button0 && !self.fire_held_down) { - if (self.current_weapon == WEAP_SNIPER_RIFLE) { - if (self.tfstate & TFSTATE_AIMING) { - if (self.heat < 400) { - self.heat = self.heat + 3; - if ((sniperpower) && (self.power_time < (time - 0.3))) { - self.power_time = time; - Status_Refresh(self); - } - } else if ((sniperpower) && (!self.power_full)) { - self.power_full = 1; - Status_Refresh(self); - } - } else { - local vector tv = self.velocity; - tv_z = 0; - if (vlen(tv) <= 50) { - SniperSight_Create(); - self.heat = 50; - self.tfstate = self.tfstate | 2048; - self.power_full = 0; - TeamFortress_SetSpeed(self); - } - } - } else if (self.current_weapon == WEAP_ASSAULT_CANNON) { - if (self.flags & FL_ONGROUND || cannon_air) { - SuperDamageSound(); - W_Attack(); - } else if (self.antispam_cannon_air < time) { - sprint(self, PRINT_MEDIUM, "You cannot fire the assault cannon without your feet on the ground...\n"); - self.antispam_cannon_air = time + 3; - } - } else { - SuperDamageSound(); - W_Attack(); - } - } else if (self.playerclass == 0) { - self.weaponmode = 0; - } else if (self.tfstate & TFSTATE_AIMING) { - W_Attack(); - self.tfstate = self.tfstate - TFSTATE_AIMING; - TeamFortress_SetSpeed(self); - self.heat = 0; - } else if (self.tfstate & TFSTATE_CANT_MOVE && !self.is_feigning) { - self.tfstate = self.tfstate - TFSTATE_CANT_MOVE; - TeamFortress_SetSpeed(self); - self.heat = 0; - } -}; - -void () SuperDamageSound = { - if (self.super_damage_finished > time) { - if (self.super_sound < time) { - self.super_sound = time + 1; - sound(self, CHAN_BODY, "items/damage3.wav", 1, ATTN_NORM); - } - } - return; -}; - -void () RemoveGrenade = { - local entity te; - - if (self.no_active_napalm_grens > 0) { - - self.no_active_napalm_grens = 0; - self.owner.no_active_napalm_grens = - self.owner.no_active_napalm_grens - 1; - if (self.owner.no_active_napalm_grens < 0) - self.owner.no_active_napalm_grens = 0; - - te = find(world, classname, "grentimer"); - while (te) { - if ((te.owner == self.owner) && - (te.no_active_napalm_grens > 0)) - te.no_active_napalm_grens = te.no_active_napalm_grens - 1; - te = find(te, classname, "grentimer"); - } - dremove(self.enemy); - dremove(self); - } - if (self.no_active_gas_grens > 0) { - - self.no_active_gas_grens = 0; - self.owner.no_active_gas_grens = - self.owner.no_active_gas_grens - 1; - if (self.owner.no_active_gas_grens < 0) - self.owner.no_active_gas_grens = 0; - - te = find(world, classname, "grentimer"); - while (te) { - if ((te.owner == self.owner) && (te.no_active_gas_grens > 0)) - te.no_active_gas_grens = te.no_active_gas_grens - 1; - te = find(te, classname, "grentimer"); - } - dremove(self); - } -};